BooleanExpression.swift 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  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. import Foundation
  15. ///
  16. /// A `BooleanExpression` is a specialized `FunctionExpression` that evaluates to a boolean value.
  17. ///
  18. /// It is used to construct conditional logic within Firestore pipelines, such as in `where`
  19. /// clauses or `ConditionalExpression`. `BooleanExpression` instances can be combined using standard
  20. /// logical operators (`&&`, `||`, `!`, `^`) to create complex conditions.
  21. ///
  22. /// Example usage in a `where` clause:
  23. /// ```swift
  24. /// firestore.pipeline()
  25. /// .collection("products")
  26. /// .where(
  27. /// Field("price").greaterThan(100) &&
  28. /// (Field("category").equal("electronics") || Field("on_sale").equal(true))
  29. /// )
  30. /// ```
  31. public class BooleanExpression: FunctionExpression, @unchecked Sendable {
  32. override public init(_ functionName: String, _ agrs: [Expression]) {
  33. super.init(functionName, agrs)
  34. }
  35. /// Creates an aggregation that counts the number of documents for which this boolean expression
  36. /// evaluates to `true`.
  37. ///
  38. /// This is useful for counting documents that meet a specific condition without retrieving the
  39. /// documents themselves.
  40. ///
  41. /// ```swift
  42. /// // Count how many books were published after 1980
  43. /// let post1980Condition = Field("published").greaterThan(1980)
  44. /// firestore.pipeline()
  45. /// .collection("books")
  46. /// .aggregate([
  47. /// post1980Condition.countIf().as("modernBooksCount")
  48. /// ])
  49. /// ```
  50. ///
  51. /// - Returns: An `AggregateFunction` that performs the conditional count.
  52. func countIf() -> AggregateFunction {
  53. return AggregateFunction("count_if", [self])
  54. }
  55. /// Creates a conditional expression that returns one of two specified expressions based on the
  56. /// result of this boolean expression.
  57. ///
  58. /// This is equivalent to a ternary operator (`condition ? then : else`).
  59. ///
  60. /// ```swift
  61. /// // Create a new field "status" based on the "rating" field.
  62. /// // If rating > 4.5, status is "top_rated", otherwise "regular".
  63. /// firestore.pipeline()
  64. /// .collection("products")
  65. /// .addFields([
  66. /// Field("rating").greaterThan(4.5)
  67. /// .then(Constant("top_rated"), else: Constant("regular"))
  68. /// .as("status")
  69. /// ])
  70. /// ```
  71. ///
  72. /// - Parameters:
  73. /// - thenExpression: The `Expression` to evaluate if this boolean expression is `true`.
  74. /// - elseExpression: The `Expression` to evaluate if this boolean expression is `false`.
  75. /// - Returns: A new `FunctionExpression` representing the conditional logic.
  76. public func then(_ thenExpression: Expression,
  77. else elseExpression: Expression) -> FunctionExpression {
  78. return FunctionExpression("conditional", [self, thenExpression, elseExpression])
  79. }
  80. /// Combines two boolean expressions with a logical AND (`&&`).
  81. ///
  82. /// The resulting expression is `true` only if both the left-hand side (`lhs`) and the right-hand
  83. /// side (`rhs`) are `true`.
  84. ///
  85. /// ```swift
  86. /// // Find books in the "Fantasy" genre with a rating greater than 4.5
  87. /// firestore.pipeline()
  88. /// .collection("books")
  89. /// .where(
  90. /// Field("genre").equal("Fantasy") && Field("rating").greaterThan(4.5)
  91. /// )
  92. /// ```
  93. ///
  94. /// - Parameters:
  95. /// - lhs: The left-hand boolean expression.
  96. /// - rhs: The right-hand boolean expression.
  97. /// - Returns: A new `BooleanExpression` representing the logical AND.
  98. public static func && (lhs: BooleanExpression,
  99. rhs: @autoclosure () throws -> BooleanExpression) rethrows
  100. -> BooleanExpression {
  101. try BooleanExpression("and", [lhs, rhs()])
  102. }
  103. /// Combines two boolean expressions with a logical OR (`||`).
  104. ///
  105. /// The resulting expression is `true` if either the left-hand side (`lhs`) or the right-hand
  106. /// side (`rhs`) is `true`.
  107. ///
  108. /// ```swift
  109. /// // Find books that are either in the "Romance" genre or were published before 1900
  110. /// firestore.pipeline()
  111. /// .collection("books")
  112. /// .where(
  113. /// Field("genre").equal("Romance") || Field("published").lessThan(1900)
  114. /// )
  115. /// ```
  116. ///
  117. /// - Parameters:
  118. /// - lhs: The left-hand boolean expression.
  119. /// - rhs: The right-hand boolean expression.
  120. /// - Returns: A new `BooleanExpression` representing the logical OR.
  121. public static func || (lhs: BooleanExpression,
  122. rhs: @autoclosure () throws -> BooleanExpression) rethrows
  123. -> BooleanExpression {
  124. try BooleanExpression("or", [lhs, rhs()])
  125. }
  126. /// Combines two boolean expressions with a logical XOR (`^`).
  127. ///
  128. /// The resulting expression is `true` if the left-hand side (`lhs`) and the right-hand side
  129. /// (`rhs`) have different boolean values.
  130. ///
  131. /// ```swift
  132. /// // Find books that are in the "Dystopian" genre OR have a rating of 5.0, but not both.
  133. /// firestore.pipeline()
  134. /// .collection("books")
  135. /// .where(
  136. /// Field("genre").equal("Dystopian") ^ Field("rating").equal(5.0)
  137. /// )
  138. /// ```
  139. ///
  140. /// - Parameters:
  141. /// - lhs: The left-hand boolean expression.
  142. /// - rhs: The right-hand boolean expression.
  143. /// - Returns: A new `BooleanExpression` representing the logical XOR.
  144. public static func ^ (lhs: BooleanExpression,
  145. rhs: @autoclosure () throws -> BooleanExpression) rethrows
  146. -> BooleanExpression {
  147. try BooleanExpression("xor", [lhs, rhs()])
  148. }
  149. /// Negates a boolean expression with a logical NOT (`!`).
  150. ///
  151. /// The resulting expression is `true` if the original expression is `false`, and vice versa.
  152. ///
  153. /// ```swift
  154. /// // Find books that are NOT in the "Science Fiction" genre
  155. /// firestore.pipeline()
  156. /// .collection("books")
  157. /// .where(!Field("genre").equal("Science Fiction"))
  158. /// ```
  159. ///
  160. /// - Parameter lhs: The boolean expression to negate.
  161. /// - Returns: A new `BooleanExpression` representing the logical NOT.
  162. public static prefix func ! (lhs: BooleanExpression) -> BooleanExpression {
  163. return BooleanExpression("not", [lhs])
  164. }
  165. }