Skip to content

Commit b377edb

Browse files
committed
add ifAbsent and error
1 parent 45797e1 commit b377edb

File tree

4 files changed

+89
-0
lines changed

4 files changed

+89
-0
lines changed

Firestore/Swift/Source/ExpressionImplementation.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -890,6 +890,10 @@ public extension Expression {
890890
return FunctionExpression("if_error", [self, Helper.sendableToExpr(catchValue)])
891891
}
892892

893+
func ifAbsent(_ defaultValue: Sendable) -> FunctionExpression {
894+
return FunctionExpression("if_absent", [self, Helper.sendableToExpr(defaultValue)])
895+
}
896+
893897
// MARK: Sorting
894898

895899
func ascending() -> Ordering {

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

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1531,6 +1531,21 @@ public protocol Expression: Sendable {
15311531
/// - Returns: A new "FunctionExpression" representing the "ifError" operation.
15321532
func ifError(_ catchValue: Sendable) -> FunctionExpression
15331533

1534+
/// Creates an expression that returns the literal `defaultValue` if this expression is
1535+
/// absent (e.g., a field does not exist in a map).
1536+
/// Otherwise, returns the result of this expression.
1537+
///
1538+
/// - Note: This API is in beta.
1539+
///
1540+
/// ```swift
1541+
/// // If the "optionalField" is absent, return "default value".
1542+
/// Field("optionalField").ifAbsent("default value")
1543+
/// ```
1544+
///
1545+
/// - Parameter defaultValue: The literal `Sendable` value to return if this expression is absent.
1546+
/// - Returns: A new "FunctionExpression" representing the "ifAbsent" operation.
1547+
func ifAbsent(_ defaultValue: Sendable) -> FunctionExpression
1548+
15341549
// MARK: Sorting
15351550

15361551
/// Creates an `Ordering` object that sorts documents in ascending order based on this expression.
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
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+
import Foundation
16+
17+
/// An expression that produces an error with a custom error message.
18+
/// This is primarily used for debugging purposes.
19+
///
20+
/// Example:
21+
/// ```swift
22+
/// ErrorExpression("This is a custom error message").as("errorResult")
23+
/// ```
24+
public class ErrorExpression: FunctionExpression, @unchecked Sendable {
25+
public init(_ errorMessage: String) {
26+
super.init("error", [Constant(errorMessage)])
27+
}
28+
}

Firestore/Swift/Tests/Integration/PipelineTests.swift

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1677,6 +1677,30 @@ class PipelineIntegrationTests: FSTIntegrationTestCase {
16771677
TestHelper.compare(snapshot: snapshot, expected: expectedResults, enforceOrder: true)
16781678
}
16791679

1680+
func testIfAbsentWorks() async throws {
1681+
let collRef = collectionRef(withDocuments: [
1682+
"doc1": ["value": 1],
1683+
"doc2": ["value2": 2],
1684+
])
1685+
let db = collRef.firestore
1686+
1687+
let pipeline = db.pipeline()
1688+
.collection(collRef.path)
1689+
.select([
1690+
Field("value").ifAbsent(100).as("value"),
1691+
])
1692+
.sort([Field(FieldPath.documentID()).ascending()])
1693+
1694+
let snapshot = try await pipeline.execute()
1695+
1696+
let expectedResults: [[String: Sendable]] = [
1697+
["value": 100],
1698+
["value": 1],
1699+
]
1700+
1701+
TestHelper.compare(snapshot: snapshot, expected: expectedResults, enforceOrder: true)
1702+
}
1703+
16801704
// func testEquivalentWorks() async throws {
16811705
// let collRef = collectionRef(withDocuments: [
16821706
// "doc1": ["value": 1, "value2": 1],
@@ -3172,6 +3196,24 @@ class PipelineIntegrationTests: FSTIntegrationTestCase {
31723196
XCTAssertEqual(snapshot.results.count, 1)
31733197
}
31743198

3199+
func testErrorExpressionWorks() async throws {
3200+
let collRef = collectionRef(withDocuments: ["doc1": ["foo": 1]])
3201+
let db = collRef.firestore
3202+
3203+
let pipeline = db.pipeline()
3204+
.collection(collRef.path)
3205+
.select([
3206+
ErrorExpression("This is a test error").as("error"),
3207+
])
3208+
3209+
do {
3210+
let _ = try await pipeline.execute()
3211+
XCTFail("The pipeline should have thrown an error, but it did not.")
3212+
} catch {
3213+
XCTAssert(true, "Successfully caught expected error from ErrorExpression.")
3214+
}
3215+
}
3216+
31753217
func testSupportsByteLength() async throws {
31763218
let db = firestore()
31773219
let randomCol = collectionRef()

0 commit comments

Comments
 (0)