Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
84 commits
Select commit Hold shift + click to select a range
f1b5b5d
[realppl 3] Arithmetic and comparison expressions
wu-hui Apr 14, 2025
6294868
[realppl 4] Array, debug, field and logical expressions
wu-hui Apr 16, 2025
6c0a698
[realppl 5] map,string,timestamp and mirroring semantics
wu-hui Apr 17, 2025
8efed9e
[realppl 6] offline ppl evaluation and tests
wu-hui Apr 24, 2025
0a8821c
[realppl 7] realppl integration with remote/local and unit tests
wu-hui Apr 30, 2025
f79f655
Remove Fuzzer
wu-hui May 27, 2025
8b8d4a2
[realppl 8] realppl spec tests
wu-hui May 13, 2025
0d65b86
[realppl 9] realppl public api and integration tests
wu-hui Apr 30, 2025
a1ad8f9
[realppl 10] Add server timestamp support
wu-hui Jun 16, 2025
3a10495
[realppl 11] Add query to pipeline support
wu-hui Jul 9, 2025
5a50bd6
Hide realtime pipelines
wu-hui Sep 12, 2025
a54bd45
address API feedbacks
cherylEnkidu Sep 21, 2025
e76d2a2
add documentation
cherylEnkidu Sep 21, 2025
fac2f83
rename Firestore/Swift/Source/ExprImpl.swift
cherylEnkidu Sep 21, 2025
2951a6c
expand expr to expression
cherylEnkidu Sep 25, 2025
afa920e
move files
cherylEnkidu Sep 25, 2025
d77d6d5
move files
cherylEnkidu Sep 25, 2025
18fbbae
move file
cherylEnkidu Sep 25, 2025
7c7eb75
Separate PipelineSource and RealtimePipelineSource
cherylEnkidu Sep 25, 2025
caf8158
add abs()
cherylEnkidu Sep 25, 2025
fb482c7
add arrayReverse
cherylEnkidu Sep 25, 2025
62434c1
add ceil()
cherylEnkidu Sep 25, 2025
d5d5c9d
add collectionId()
cherylEnkidu Sep 26, 2025
11bf771
add countDistinct()
cherylEnkidu Sep 26, 2025
0e39951
[realppl 8] realppl spec tests
wu-hui May 13, 2025
016dac0
[realppl 9] realppl public api and integration tests
wu-hui Apr 30, 2025
00c6256
add exp()
cherylEnkidu Sep 29, 2025
854b6bb
[realppl 10] Add query to pipeline support
wu-hui Jul 9, 2025
814074f
make all bit operations internal
cherylEnkidu Sep 29, 2025
54461f1
add floor()
cherylEnkidu Sep 29, 2025
8e8557f
add length()
cherylEnkidu Sep 29, 2025
ead6684
hide manhattanDistance()
cherylEnkidu Sep 29, 2025
c720527
add ln()
cherylEnkidu Sep 30, 2025
b51b545
Hide realtime pipelines
wu-hui Sep 12, 2025
69ced35
add pow()
cherylEnkidu Sep 30, 2025
3d0a3d3
hide replace expression
cherylEnkidu Sep 30, 2025
617feb4
rename substring
cherylEnkidu Sep 30, 2025
5bc46e3
add more expressions
cherylEnkidu Sep 30, 2025
452875f
make internal impl of Order public
cherylEnkidu Sep 30, 2025
3a258b2
rename timestampSubtract
cherylEnkidu Sep 30, 2025
1c00eea
refactor snapshot
cherylEnkidu Sep 30, 2025
aaa7855
move file
cherylEnkidu Sep 30, 2025
d02c044
remove backpointer to parent
cherylEnkidu Sep 30, 2025
fd8ed65
equivalent()
cherylEnkidu Sep 30, 2025
418e3a6
merge in base branch
cherylEnkidu Sep 30, 2025
e42cce2
merge in base 2
cherylEnkidu Sep 30, 2025
2fb7a14
add concat()
cherylEnkidu Oct 1, 2025
8422b15
add currentTimestamp()
cherylEnkidu Oct 1, 2025
45797e1
hide equivalent
cherylEnkidu Oct 1, 2025
b377edb
add ifAbsent and error
cherylEnkidu Oct 1, 2025
903425b
add join()
cherylEnkidu Oct 1, 2025
50b673e
rename logical
cherylEnkidu Oct 1, 2025
41fcae2
add name tag for FunctionExpression
cherylEnkidu Oct 7, 2025
a4d33e4
add missing tags
cherylEnkidu Oct 9, 2025
91b0dec
add documentations
cherylEnkidu Oct 9, 2025
d8c5c81
add documentations 2
cherylEnkidu Oct 9, 2025
88586a1
rebase to feature branch
cherylEnkidu Oct 22, 2025
43fb9ef
fix rebase error 1
cherylEnkidu Oct 22, 2025
77e4556
resolve rebase error
cherylEnkidu Oct 22, 2025
dfce952
fix broken tests
cherylEnkidu Oct 23, 2025
8005a23
format code
cherylEnkidu Oct 23, 2025
7a357c2
add map_set
cherylEnkidu Oct 24, 2025
bc1a2b7
add equivalent
cherylEnkidu Oct 24, 2025
72b89fd
add TimestampTrunc
cherylEnkidu Oct 24, 2025
2e18c4a
Revert "add equivalent"
cherylEnkidu Oct 24, 2025
3475b66
solve rebase error
cherylEnkidu Oct 24, 2025
585c525
add trim()
cherylEnkidu Oct 27, 2025
b6ab3a0
remove Equivalent
cherylEnkidu Oct 27, 2025
688d164
add duplicate alias checking
cherylEnkidu Oct 28, 2025
62ea686
optimize aliasedAggregatesToMap
cherylEnkidu Oct 28, 2025
109a2f8
improve code
cherylEnkidu Oct 29, 2025
4eb402e
change API of timestampTruncate
cherylEnkidu Oct 29, 2025
857e71c
remove map_set
cherylEnkidu Oct 29, 2025
e19e9fd
add Split
cherylEnkidu Oct 30, 2025
d0f60c4
add trim()
cherylEnkidu Oct 31, 2025
80a79ae
add array maximun and minimum
cherylEnkidu Oct 31, 2025
e086ebd
add type
cherylEnkidu Oct 31, 2025
b0b2767
merge in base branch
cherylEnkidu Nov 10, 2025
71eb677
format code
cherylEnkidu Nov 10, 2025
ff9516c
solve merging issue
cherylEnkidu Nov 10, 2025
5c6f47b
add tests for arrayConcat
cherylEnkidu Nov 10, 2025
6ed21b5
enable tests for toUpper and lower
cherylEnkidu Nov 11, 2025
9d5b8ff
remove duplicate test
cherylEnkidu Nov 11, 2025
4454980
add Pagination Tests
cherylEnkidu Nov 11, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 25 additions & 15 deletions Firestore/Swift/Source/ExpressionImplementation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -523,6 +523,14 @@ public extension Expression {
return FunctionExpression(functionName: "array_get", args: [self, offsetExpression])
}

func arrayMaximum() -> FunctionExpression {
return FunctionExpression(functionName: "maximum", args: [self])
}

func arrayMinimum() -> FunctionExpression {
return FunctionExpression(functionName: "minimum", args: [self])
}

func greaterThan(_ other: Expression) -> BooleanExpression {
return BooleanExpression(functionName: "greater_than", args: [self, other])
}
Expand Down Expand Up @@ -622,6 +630,14 @@ public extension Expression {
return FunctionExpression(functionName: "join", args: [self, Constant(delimiter)])
}

func split(delimiter: String) -> FunctionExpression {
return FunctionExpression(functionName: "split", args: [self, Constant(delimiter)])
}

func split(delimiter: Expression) -> FunctionExpression {
return FunctionExpression(functionName: "split", args: [self, delimiter])
}

func length() -> FunctionExpression {
return FunctionExpression(functionName: "length", args: [self])
}
Expand Down Expand Up @@ -709,6 +725,10 @@ public extension Expression {
return FunctionExpression(functionName: "trim", args: [self, value])
}

func trim() -> FunctionExpression {
return FunctionExpression(functionName: "trim", args: [self])
}

func stringConcat(_ strings: [Expression]) -> FunctionExpression {
return FunctionExpression(functionName: "string_concat", args: [self] + strings)
}
Expand Down Expand Up @@ -773,20 +793,6 @@ public extension Expression {
return FunctionExpression(functionName: "map_merge", args: [self] + maps)
}

func mapSet(key: Expression, value: Sendable) -> FunctionExpression {
return FunctionExpression(
functionName: "map_set",
args: [self, key, Helper.sendableToExpr(value)]
)
}

func mapSet(key: String, value: Sendable) -> FunctionExpression {
return FunctionExpression(
functionName: "map_set",
args: [self, Helper.sendableToExpr(key), Helper.sendableToExpr(value)]
)
}

// --- Added Aggregate Operations (on Expr) ---

func countDistinct() -> AggregateFunction {
Expand Down Expand Up @@ -919,7 +925,7 @@ public extension Expression {
return FunctionExpression(functionName: "timestamp_to_unix_seconds", args: [self])
}

func timestampTruncate(granularity: TimeUnit) -> FunctionExpression {
func timestampTruncate(granularity: TimeGranularity) -> FunctionExpression {
return FunctionExpression(
functionName: "timestamp_trunc",
args: [self, Helper.sendableToExpr(granularity.rawValue)]
Expand Down Expand Up @@ -1001,4 +1007,8 @@ public extension Expression {
let exprs = [self] + values.map { Helper.sendableToExpr($0) }
return FunctionExpression(functionName: "concat", args: exprs)
}

func type() -> FunctionExpression {
return FunctionExpression(functionName: "type", args: [self])
}
}
3 changes: 2 additions & 1 deletion Firestore/Swift/Source/Stages.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ import Foundation
protocol Stage {
var name: String { get }
var bridge: StageBridge { get }
/// The `errorMessage` defaults to `nil`. Errors during stage construction are captured and thrown later when `execute()` is called.
/// The `errorMessage` defaults to `nil`. Errors during stage construction are captured and thrown
/// later when `execute()` is called.
var errorMessage: String? { get }
}

Expand Down
110 changes: 72 additions & 38 deletions Firestore/Swift/Source/SwiftAPI/Pipeline/Expressions/Expression.swift
Original file line number Diff line number Diff line change
Expand Up @@ -461,6 +461,30 @@ public protocol Expression: Sendable {
/// - Returns: A new `FunctionExpression` representing the "arrayGet" operation.
func arrayGet(_ offsetExpression: Expression) -> FunctionExpression

/// Creates an expression that returns the maximum element of an array.
///
/// Assumes `self` evaluates to an array.
///
/// ```swift
/// // Get the maximum value in the "scores" array.
/// Field("scores").arrayMaximum()
/// ```
///
/// - Returns: A new `FunctionExpression` representing the maximum element of the array.
func arrayMaximum() -> FunctionExpression

/// Creates an expression that returns the minimum element of an array.
///
/// Assumes `self` evaluates to an array.
///
/// ```swift
/// // Get the minimum value in the "scores" array.
/// Field("scores").arrayMinimum()
/// ```
///
/// - Returns: A new `FunctionExpression` representing the minimum element of the array.
func arrayMinimum() -> FunctionExpression

/// Creates a `BooleanExpression` that returns `true` if this expression is greater
/// than the given expression.
///
Expand Down Expand Up @@ -681,6 +705,18 @@ public protocol Expression: Sendable {
/// - Returns: A new `FunctionExpression` representing the joined string.
func join(delimiter: String) -> FunctionExpression

/// Creates an expression that splits a string into an array of substrings based on a delimiter.
///
/// - Parameter delimiter: The string to split on.
/// - Returns: A new `FunctionExpression` representing the array of substrings.
func split(delimiter: String) -> FunctionExpression

/// Creates an expression that splits a string into an array of substrings based on a delimiter.
///
/// - Parameter delimiter: An expression that evaluates to a string or bytes to split on.
/// - Returns: A new `FunctionExpression` representing the array of substrings.
func split(delimiter: Expression) -> FunctionExpression

/// Creates an expression that returns the length of a string.
///
/// ```swift
Expand Down Expand Up @@ -886,6 +922,18 @@ public protocol Expression: Sendable {
/// - Returns: A new `FunctionExpression` representing the uppercase string.
func toUpper() -> FunctionExpression

/// Creates an expression that removes leading and trailing whitespace from a string.
///
/// Assumes `self` evaluates to a string.
///
/// ```swift
/// // Trim leading/trailing whitespace from the "comment" field.
/// Field("comment").trim()
/// ```
///
/// - Returns: A new `FunctionExpression` representing the trimmed string.
func trim() -> FunctionExpression

/// Creates an expression that removes leading and trailing occurrences of specified characters
/// from a string (from `self`).
/// Assumes `self` evaluates to a string, and `value` evaluates to a string.
Expand Down Expand Up @@ -961,8 +1009,8 @@ public protocol Expression: Sendable {
/// - Returns: A new `FunctionExpression` representing the reversed string.
func stringReverse() -> FunctionExpression

/// Creates an expression that calculates the length of this expression in bytes.
/// Assumes `self` evaluates to a string.
/// Creates an expression that calculates the length of this string or bytes expression in bytes.
/// Assumes `self` evaluates to a string or bytes.
///
/// ```swift
/// // Calculate the length of the "myString" field in bytes.
Expand All @@ -975,9 +1023,9 @@ public protocol Expression: Sendable {
/// - Returns: A new `FunctionExpression` representing the length in bytes.
func byteLength() -> FunctionExpression

/// Creates an expression that returns a substring of this expression using
/// Creates an expression that returns a substring of this expression (String or Bytes) using
/// literal integers for position and optional length.
/// Indexing is 0-based. Assumes `self` evaluates to a string.
/// Indexing is 0-based. Assumes `self` evaluates to a string or bytes.
///
/// ```swift
/// // Get substring from index 5 with length 10
Expand All @@ -992,9 +1040,9 @@ public protocol Expression: Sendable {
/// - Returns: A new `FunctionExpression` representing the substring.
func substring(position: Int, length: Int?) -> FunctionExpression

/// Creates an expression that returns a substring of this expression using
/// Creates an expression that returns a substring of this expression (String or Bytes) using
/// expressions for position and optional length.
/// Indexing is 0-based. Assumes `self` evaluates to a string, and parameters evaluate to
/// Indexing is 0-based. Assumes `self` evaluates to a string or bytes, and parameters evaluate to
/// integers.
///
/// ```swift
Expand Down Expand Up @@ -1080,34 +1128,6 @@ public protocol Expression: Sendable {
/// - Returns: A new `FunctionExpression` representing the "map_merge" operation.
func mapMerge(_ maps: [Expression]) -> FunctionExpression

/// Creates an expression that adds or updates a specified field in a map.
/// Assumes `self` evaluates to a Map, `key` evaluates to a string, and `value` can be
/// any type.
///
/// ```swift
/// // Set a field using a key from another field
/// Field("config").mapSet(key: Field("keyName"), value: Field("keyValue"))
/// ```
///
/// - Parameter key: An `Expression` (evaluating to a string) representing the key of
/// the field to set or update.
/// - Parameter value: The `Expression` representing the value to set for the field.
/// - Returns: A new `FunctionExpression` representing the map with the updated field.
func mapSet(key: Expression, value: Sendable) -> FunctionExpression

/// Creates an expression that adds or updates a specified field in a map.
/// Assumes `self` evaluates to a Map.
///
/// ```swift
/// // Set the "status" field to "active" in the "order" map
/// Field("order").mapSet(key: "status", value: "active")
/// ```
///
/// - Parameter key: The literal string key of the field to set or update.
/// - Parameter value: The `Sendable` literal value to set for the field.
/// - Returns: A new `FunctionExpression` representing the map with the updated field.
func mapSet(key: String, value: Sendable) -> FunctionExpression

// MARK: Aggregations

/// Creates an aggregation that counts the number of distinct values of this expression.
Expand Down Expand Up @@ -1429,19 +1449,23 @@ public protocol Expression: Sendable {
/// Field("timestamp").timestampTruncate(granularity: .day)
/// ```
///
/// - Parameter granularity: A `TimeUnit` enum representing the truncation unit.
/// - Parameter granularity: A `TimeGranularity` representing the truncation unit.
/// - Returns: A new `FunctionExpression` representing the truncated timestamp.
func timestampTruncate(granularity: TimeUnit) -> FunctionExpression
func timestampTruncate(granularity: TimeGranularity) -> FunctionExpression

/// Creates an expression that truncates a timestamp to a specified granularity.
/// Assumes `self` evaluates to a Timestamp, and `granularity` is a literal string.
/// Assumes `self` evaluates to a Timestamp.
///
/// ```swift
/// // Truncate "timestamp" field to the nearest day using a literal string.
/// Field("timestamp").timestampTruncate(granularity: "day")
///
/// // Truncate "timestamp" field to the nearest day using an expression.
/// Field("timestamp").timestampTruncate(granularity: Field("granularity_field"))
/// ```
///
/// - Parameter granularity: A `Sendable` literal string specifying the truncation unit.
/// - Parameter granularity: A `Sendable` literal string or an `Expression` that evaluates to a
/// string, specifying the truncation unit.
/// - Returns: A new `FunctionExpression` representing the truncated timestamp.
func timestampTruncate(granularity: Sendable) -> FunctionExpression

Expand Down Expand Up @@ -1596,4 +1620,14 @@ public protocol Expression: Sendable {
/// - Parameter values: The values to concatenate.
/// - Returns: A new `FunctionExpression` representing the concatenated result.
func concat(_ values: [Sendable]) -> FunctionExpression

/// Creates an expression that returns the type of the expression.
///
/// ```swift
/// // Get the type of the "rating" field.
/// Field("rating").type()
/// ```
///
/// - Returns: A new `FunctionExpression` representing the type of the expression as a string.
func type() -> FunctionExpression
}
13 changes: 12 additions & 1 deletion Firestore/Swift/Source/SwiftAPI/Pipeline/Pipeline.swift
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,17 @@ public struct Pipeline: @unchecked Sendable {
}
}

/// Creates a new `Pipeline` instance in a faulted state.
///
/// This function is used to propagate an error through the pipeline chain. When a stage
/// fails to initialize or if a preceding stage has already failed, this method is called
/// to create a new pipeline that holds the error message. The `stages` array is cleared,
/// and the `errorMessage` is set.
///
/// The stored error is eventually thrown by the `execute()` method.
///
/// - Parameter message: The error message to store in the pipeline.
/// - Returns: A new `Pipeline` instance with the specified error message.
private func withError(_ message: String) -> Pipeline {
return Pipeline(stages: [], db: db, errorMessage: message)
}
Expand All @@ -127,7 +138,7 @@ public struct Pipeline: @unchecked Sendable {
/// - Throws: An error if the pipeline execution fails on the backend.
/// - Returns: A `Pipeline.Snapshot` containing the result of the pipeline execution.
public func execute() async throws -> Pipeline.Snapshot {
// Check if any Error exist during Stage contruction
// Check if any errors occurred during stage construction.
if let errorMessage = errorMessage {
throw NSError(
domain: "com.google.firebase.firestore",
Expand Down
82 changes: 82 additions & 0 deletions Firestore/Swift/Source/SwiftAPI/Pipeline/TimeGranularity.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

public struct TimeGranularity: Sendable, Equatable, Hashable {
enum Kind: String {
case microsecond
case millisecond
case second
case minute
case hour
case day
case week
case weekMonday = "week(monday)"
case weekTuesday = "week(tuesday)"
case weekWednesday = "week(wednesday)"
case weekThursday = "week(thursday)"
case weekFriday = "week(friday)"
case weekSaturday = "week(saturday)"
case weekSunday = "week(sunday)"
case isoweek
case month
case quarter
case year
case isoyear
}

public static let microsecond = TimeGranularity(kind: .microsecond)
public static let millisecond = TimeGranularity(kind: .millisecond)
public static let second = TimeGranularity(kind: .second)
public static let minute = TimeGranularity(kind: .minute)
public static let hour = TimeGranularity(kind: .hour)
/// The day in the Gregorian calendar year that contains the value to truncate.
public static let day = TimeGranularity(kind: .day)
/// The first day in the week that contains the value to truncate. Weeks begin on Sundays. WEEK is
/// equivalent to WEEK(SUNDAY).
public static let week = TimeGranularity(kind: .week)
/// The first day in the week that contains the value to truncate. Weeks begin on Monday.
public static let weekMonday = TimeGranularity(kind: .weekMonday)
/// The first day in the week that contains the value to truncate. Weeks begin on Tuesday.
public static let weekTuesday = TimeGranularity(kind: .weekTuesday)
/// The first day in the week that contains the value to truncate. Weeks begin on Wednesday.
public static let weekWednesday = TimeGranularity(kind: .weekWednesday)
/// The first day in the week that contains the value to truncate. Weeks begin on Thursday.
public static let weekThursday = TimeGranularity(kind: .weekThursday)
/// The first day in the week that contains the value to truncate. Weeks begin on Friday.
public static let weekFriday = TimeGranularity(kind: .weekFriday)
/// The first day in the week that contains the value to truncate. Weeks begin on Saturday.
public static let weekSaturday = TimeGranularity(kind: .weekSaturday)
/// The first day in the week that contains the value to truncate. Weeks begin on Sunday.
public static let weekSunday = TimeGranularity(kind: .weekSunday)
/// The first day in the ISO 8601 week that contains the value to truncate. The ISO week begins on
/// Monday. The first ISO week of each ISO year contains the first Thursday of the corresponding
/// Gregorian calendar year.
public static let isoweek = TimeGranularity(kind: .isoweek)
/// The first day in the month that contains the value to truncate.
public static let month = TimeGranularity(kind: .month)
/// The first day in the quarter that contains the value to truncate.
public static let quarter = TimeGranularity(kind: .quarter)
/// The first day in the year that contains the value to truncate.
public static let year = TimeGranularity(kind: .year)
/// The first day in the ISO 8601 week-numbering year that contains the value to truncate. The ISO
/// year is the Monday of the first week where Thursday belongs to the corresponding Gregorian
/// calendar year.
public static let isoyear = TimeGranularity(kind: .isoyear)

public let rawValue: String

init(kind: Kind) {
rawValue = kind.rawValue
}
}
Loading
Loading