Skip to content

Commit ed1a19b

Browse files
committed
API changes
1 parent 454893b commit ed1a19b

12 files changed

+200
-69
lines changed

Firestore/Swift/Source/Helper/PipelineHelper.swift

-4
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,6 @@
1313
// limitations under the License.
1414

1515
enum Helper {
16-
static func exprConvertFrom(_ value: Any) -> any Expr {
17-
return Field("PLACEHOLDER")
18-
}
19-
2016
static func valueToDefaultExpr(_ value: Any) -> any Expr {
2117
return Field("PLACEHOLDER")
2218
}

Firestore/Swift/Source/SwiftAPI/Pipeline/AggregateOption.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@
1414

1515
public struct AggregateOption {
1616
public let accumulators: [AggregateWithAlias]
17-
public let groups: [SelectableOrFieldName]?
17+
public let groups: [Selectable]?
1818

19-
public init(accumulators: [AggregateWithAlias], groups: [SelectableOrFieldName]? = nil) {
19+
public init(accumulators: [AggregateWithAlias], groups: [Selectable]? = nil) {
2020
self.accumulators = accumulators
2121
self.groups = groups
2222
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// Copyright 2025 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
public class ArrayContains: BooleanExpr, @unchecked Sendable {
16+
public init(_ values: Any...) {
17+
super.init("array_concat", values as! [any Expr])
18+
}
19+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// Copyright 2025 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
public class Ascending: Ordering, @unchecked Sendable {
16+
public init(_ fieldName: String) {
17+
super.init(expr: Field(fieldName), direction: .ascending)
18+
}
19+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// Copyright 2025 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
public class Descending: Ordering, @unchecked Sendable {
16+
public init(_ fieldName: String) {
17+
super.init(expr: Field(fieldName), direction: .descending)
18+
}
19+
}

Firestore/Swift/Source/SwiftAPI/Pipeline/Expr.swift

+21-2
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
// limitations under the License.
1414

1515
public protocol Expr: Sendable {
16-
func alias(_ name: String) -> ExprWithAlias
16+
func `as`(_ name: String) -> ExprWithAlias
1717

1818
// MARK: Comparison Operators
1919

@@ -221,7 +221,7 @@ public protocol Expr: Sendable {
221221
}
222222

223223
public extension Expr {
224-
func alias(_ name: String) -> ExprWithAlias {
224+
func `as`(_ name: String) -> ExprWithAlias {
225225
return ExprWithAlias(self, name)
226226
}
227227

@@ -794,3 +794,22 @@ public extension Expr {
794794
return Ordering(expr: self, direction: .descending)
795795
}
796796
}
797+
798+
// protocal cannot overwrite operator, since every inheritated class will have this function
799+
// it will lead to error: Generic parameter 'Self' could not be inferred
800+
801+
public func > (lhs: Expr, rhs: @autoclosure () throws -> Any) rethrows -> BooleanExpr {
802+
try BooleanExpr("gt", [lhs, Helper.valueToDefaultExpr(rhs())])
803+
}
804+
805+
public func < (lhs: Expr, rhs: @autoclosure () throws -> Any) rethrows -> BooleanExpr {
806+
try BooleanExpr("lt", [lhs, Helper.valueToDefaultExpr(rhs())])
807+
}
808+
809+
public func <= (lhs: Expr, rhs: @autoclosure () throws -> Any) rethrows -> BooleanExpr {
810+
try BooleanExpr("lte", [lhs, Helper.valueToDefaultExpr(rhs())])
811+
}
812+
813+
public func == (lhs: Expr, rhs: @autoclosure () throws -> Any) rethrows -> BooleanExpr {
814+
try BooleanExpr("eq", [lhs, Helper.valueToDefaultExpr(rhs())])
815+
}

Firestore/Swift/Source/SwiftAPI/Pipeline/Expr/FunctionExpr/BooleanExpr.swift

+19
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,23 @@ public class BooleanExpr: FunctionExpr, @unchecked Sendable {
1616
public static func and(_ value: Expr ...) -> BooleanExpr {
1717
return BooleanExpr("and", value)
1818
}
19+
20+
override init(_ functionName: String, _ agrs: [any Expr]) {
21+
super.init(functionName, agrs)
22+
}
23+
24+
public static func && (lhs: BooleanExpr,
25+
rhs: @autoclosure () throws -> BooleanExpr) rethrows -> BooleanExpr {
26+
try BooleanExpr("and", [lhs, rhs()])
27+
}
28+
29+
public static func || (lhs: BooleanExpr,
30+
rhs: @autoclosure () throws -> BooleanExpr) rethrows -> BooleanExpr {
31+
try BooleanExpr("or", [lhs, rhs()])
32+
}
33+
34+
// not
35+
public static prefix func ! (lhs: BooleanExpr) -> BooleanExpr {
36+
return BooleanExpr("not", [lhs])
37+
}
1938
}

Firestore/Swift/Source/SwiftAPI/Pipeline/Ordering.swift

+6-1
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,14 @@
1414
* limitations under the License.
1515
*/
1616

17-
public struct Ordering {
17+
public class Ordering: @unchecked Sendable {
1818
let expr: Expr
1919
let direction: Direction
20+
21+
init(expr: Expr, direction: Direction) {
22+
self.expr = expr
23+
self.direction = direction
24+
}
2025
}
2126

2227
public struct Direction: Sendable, Equatable, Hashable {

Firestore/Swift/Source/SwiftAPI/Pipeline/Pipeline.swift

+55-7
Original file line numberDiff line numberDiff line change
@@ -64,9 +64,27 @@ public struct Pipeline {
6464
/// only additions are desired.
6565
///
6666
/// - Parameter selections: The fields to include in the output documents, specified as
67-
/// `Selectable` expressions or `String` values representing field names.
67+
/// `Selectable` expressions.
6868
/// - Returns: A new `Pipeline` object with this stage appended to the stage list.
69-
public func select(_ selections: SelectableOrFieldName...) -> Pipeline {
69+
public func select(_ selections: Selectable...) -> Pipeline {
70+
// Implementation
71+
return self
72+
}
73+
74+
/// Selects or creates a set of fields from the outputs of previous stages.
75+
///
76+
/// The selected fields are defined using `Selectable` expressions, which can be:
77+
///
78+
/// - `String`: Name of an existing field.
79+
/// - `Field`: References an existing field.
80+
/// - `Function`: Represents the result of a function with an assigned alias name using `Expr#as`.
81+
///
82+
/// If no selections are provided, the output of this stage is empty. Use `addFields` instead if
83+
/// only additions are desired.
84+
///
85+
/// - Parameter selections: `String` values representing field names.
86+
/// - Returns: A new `Pipeline` object with this stage appended to the stage list.
87+
public func select(_ selections: String...) -> Pipeline {
7088
// Implementation
7189
return self
7290
}
@@ -87,7 +105,7 @@ public struct Pipeline {
87105
///
88106
/// - Parameter condition: The `BooleanExpr` to apply.
89107
/// - Returns: A new `Pipeline` object with this stage appended to the stage list.
90-
public func `where`(_ condition: BooleanExpr) -> Pipeline {
108+
public func `where`(_ condition: () -> BooleanExpr) -> Pipeline {
91109
return self
92110
}
93111

@@ -131,8 +149,26 @@ public struct Pipeline {
131149
/// `Expr.alias(_:)`.
132150
///
133151
/// - Parameter selections: The fields to include in the output documents, specified as
134-
/// `Selectable` expressions or `String` values representing field names.
135-
public func distinct(_ groups: SelectableOrFieldName...) -> Pipeline {
152+
/// `String` values representing field names.
153+
public func distinct(_ groups: String...) -> Pipeline {
154+
return self
155+
}
156+
157+
/// Returns a set of distinct `Expr` values from the inputs to this stage.
158+
///
159+
/// This stage processes the results from previous stages, ensuring that only unique
160+
/// combinations of `Expr` values (such as `Field` and `Function`) are included.
161+
///
162+
/// The parameters to this stage are defined using `Selectable` expressions or field names:
163+
///
164+
/// - `String`: The name of an existing field.
165+
/// - `Field`: A reference to an existing document field.
166+
/// - `Function`: Represents the result of a function with an assigned alias using
167+
/// `Expr.alias(_:)`.
168+
///
169+
/// - Parameter selections: The fields to include in the output documents, specified as
170+
/// `Selectable` expressions.
171+
public func distinct(_ groups: Selectable...) -> Pipeline {
136172
return self
137173
}
138174

@@ -188,7 +224,7 @@ public struct Pipeline {
188224
///
189225
/// - Parameter orderings: One or more `Ordering` instances specifying the sorting criteria.
190226
/// - Returns: A new `Pipeline` object with this stage appended to the stage list.
191-
public func sort(_ orderings: [Ordering]) -> Pipeline {
227+
public func sort(_ orderings: Ordering...) -> Pipeline {
192228
// Implementation
193229
return self
194230
}
@@ -200,7 +236,19 @@ public struct Pipeline {
200236
///
201237
/// - Parameter field: The `Selectable` field containing the nested map.
202238
/// - Returns: A new `Pipeline` object with this stage appended to the stage list.
203-
public func replace(_ field: SelectableOrFieldName) -> Pipeline {
239+
public func replace(_ field: Selectable) -> Pipeline {
240+
// Implementation
241+
return self
242+
}
243+
244+
/// Fully overwrites all fields in a document with those coming from a nested map.
245+
///
246+
/// This stage allows you to emit a map value as a document. Each key of the map becomes a
247+
/// field on the document that contains the corresponding value.
248+
///
249+
/// - Parameter fieldName: The field containing the nested map.
250+
/// - Returns: A new `Pipeline` object with this stage appended to the stage list.
251+
public func replace(_ fieldName: String) -> Pipeline {
204252
// Implementation
205253
return self
206254
}

Firestore/Swift/Source/SwiftAPI/Pipeline/PipelineSource.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,11 @@ public struct PipelineSource {
4040
return Pipeline(db)
4141
}
4242

43-
public func createFrom(_ query: Query) -> Pipeline {
43+
public func create(from query: Query) -> Pipeline {
4444
return Pipeline(db)
4545
}
4646

47-
public func createFrom(_ aggregateQuery: AggregateQuery) -> Pipeline {
47+
public func create(from aggregateQuery: AggregateQuery) -> Pipeline {
4848
return Pipeline(db)
4949
}
5050
}

Firestore/Swift/Source/SwiftAPI/Pipeline/SelectableOrFieldName.swift

-41
This file was deleted.

Firestore/Swift/Tests/Integration/PipelineApiTests.swift

+38-10
Original file line numberDiff line numberDiff line change
@@ -29,22 +29,19 @@ final class PipelineTests: FSTIntegrationTestCase {
2929
let _: Pipeline = pipelineSource.database()
3030

3131
let query: Query = db.collection("foo").limit(to: 2)
32-
let _: Pipeline = pipelineSource.createFrom(query)
32+
let _: Pipeline = pipelineSource.create(from: query)
3333

3434
let aggregateQuery = db.collection("foo").count
35-
let _: Pipeline = pipelineSource.createFrom(aggregateQuery)
35+
let _: Pipeline = pipelineSource.create(from: aggregateQuery)
3636

3737
let _: PipelineSnapshot = try await pipeline.execute()
3838
}
3939

4040
func testWhereStage() async throws {
4141
_ = db.pipeline().collection("books")
42-
.where(
43-
BooleanExpr.and(
44-
Field("rating").gt(4.0), // Filter for ratings greater than 4.0
45-
Field("genre").eq("Science Fiction")
46-
)
47-
)
42+
.where {
43+
Field("rating") > 4.0 && Field("genre") == "Science Fiction" || ArrayContains("rating")
44+
}
4845
}
4946

5047
func testAddFieldStage() async throws {
@@ -58,7 +55,7 @@ final class PipelineTests: FSTIntegrationTestCase {
5855

5956
// An expression becomes a Selectable when given an alias. In this case
6057
// the alias is 'salePrice'
61-
let priceSelectableExpr: Selectable = priceExpr.alias("salePrice")
58+
let priceSelectableExpr: Selectable = priceExpr.as("salePrice")
6259

6360
_ = db.pipeline().collection("books")
6461
.addFields(
@@ -71,12 +68,43 @@ final class PipelineTests: FSTIntegrationTestCase {
7168
// is to inline the Expr definition
7269
_ = db.pipeline().collection("books")
7370
.addFields(
74-
Field("msrp").multiply(Field("discount")).alias("salePrice")
71+
Field("msrp").multiply(Field("discount")).as("salePrice")
7572
)
7673

7774
// Output
7875
// { title: 'title1', price: 10, discount: 0.8, salePrice: 8.0},
7976
// { title: 'title2', price: 12, discount: 1.0, salePrice: 12.0 },
8077
// { title: 'title3', price: 5, discount: 0.66, salePrice: 3.30 }
8178
}
79+
80+
func testSelectStage() async throws {
81+
// Input
82+
// { title: 'title1', price: 10, discount: 0.8 },
83+
// { title: 'title2', price: 12, discount: 1.0 },
84+
// { title: 'title3', price: 5, discount: 0.66 }
85+
86+
// Overload for string and Selectable
87+
_ = db.pipeline().collection("books")
88+
.select(
89+
Field("title"), // Field class inheritates Selectable
90+
Field("msrp").multiply(Field("discount")).as("salePrice")
91+
)
92+
93+
_ = db.pipeline().collection("books").select("title", "author")
94+
95+
// Output
96+
// { title: 'title1', salePrice: 8.0},
97+
// { title: 'title2', salePrice: 12.0 },
98+
// { title: 'title3', salePrice: 3.30 }
99+
}
100+
101+
func testSortStage() async throws {
102+
// Sort books by rating in descending order, and then by title in ascending order for books
103+
// with the same rating
104+
_ = db.pipeline().collection("books")
105+
.sort(
106+
Field("rating").descending(),
107+
Ascending("title") // alternative API offered
108+
)
109+
}
82110
}

0 commit comments

Comments
 (0)