Skip to content

Commit e65a3a0

Browse files
cherylEnkiduwu-hui
andauthored
Add checking for duplicate alias (#15449)
Co-authored-by: wu-hui <[email protected]>
1 parent e31449e commit e65a3a0

File tree

5 files changed

+369
-89
lines changed

5 files changed

+369
-89
lines changed

Firestore/Swift/Source/Helper/PipelineHelper.swift

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,17 @@
1313
// limitations under the License.
1414

1515
enum Helper {
16+
enum HelperError: Error, LocalizedError {
17+
case duplicateAlias(String)
18+
19+
public var errorDescription: String? {
20+
switch self {
21+
case let .duplicateAlias(message):
22+
return message
23+
}
24+
}
25+
}
26+
1627
static func sendableToExpr(_ value: Sendable?) -> Expression {
1728
guard let value = value else {
1829
return Constant.nil
@@ -31,14 +42,35 @@ enum Helper {
3142
}
3243
}
3344

34-
static func selectablesToMap(selectables: [Selectable]) -> [String: Expression] {
35-
let exprMap = selectables.reduce(into: [String: Expression]()) { result, selectable in
45+
static func selectablesToMap(selectables: [Selectable]) -> ([String: Expression], Error?) {
46+
var exprMap = [String: Expression]()
47+
for selectable in selectables {
3648
guard let value = selectable as? SelectableWrapper else {
3749
fatalError("Selectable class must conform to SelectableWrapper.")
3850
}
39-
result[value.alias] = value.expr
51+
let alias = value.alias
52+
if exprMap.keys.contains(alias) {
53+
return ([:], HelperError.duplicateAlias("Duplicate alias '\(alias)' found in selectables."))
54+
}
55+
exprMap[alias] = value.expr
56+
}
57+
return (exprMap, nil)
58+
}
59+
60+
static func aliasedAggregatesToMap(accumulators: [AliasedAggregate])
61+
-> ([String: AggregateFunction], Error?) {
62+
var accumulatorMap = [String: AggregateFunction]()
63+
for aliasedAggregate in accumulators {
64+
let alias = aliasedAggregate.alias
65+
if accumulatorMap.keys.contains(alias) {
66+
return (
67+
[:],
68+
HelperError.duplicateAlias("Duplicate alias '\(alias)' found in accumulators.")
69+
)
70+
}
71+
accumulatorMap[alias] = aliasedAggregate.aggregate
4072
}
41-
return exprMap
73+
return (accumulatorMap, nil)
4274
}
4375

4476
static func map(_ elements: [String: Sendable?]) -> FunctionExpression {
@@ -66,11 +98,11 @@ enum Helper {
6698
if let exprValue = value as? Expression {
6799
return exprValue.toBridge()
68100
} else if let aggregateFunctionValue = value as? AggregateFunction {
69-
return aggregateFunctionValue.toBridge()
101+
return aggregateFunctionValue.bridge
70102
} else if let dictionaryValue = value as? [String: Sendable?] {
71103
let mappedValue: [String: Sendable] = dictionaryValue.mapValues {
72104
if let aggFunc = $0 as? AggregateFunction {
73-
return aggFunc.toBridge()
105+
return aggFunc.bridge
74106
}
75107
return sendableToExpr($0).toBridge()
76108
}

Firestore/Swift/Source/Stages.swift

Lines changed: 58 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,14 @@ import Foundation
2626
protocol Stage {
2727
var name: String { get }
2828
var bridge: StageBridge { get }
29+
/// The `errorMessage` defaults to `nil`. Errors during stage construction are captured and thrown later when `execute()` is called.
30+
var errorMessage: String? { get }
31+
}
32+
33+
extension Stage {
34+
var errorMessage: String? {
35+
return nil
36+
}
2937
}
3038

3139
@available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
@@ -147,17 +155,19 @@ class AddFields: Stage {
147155
let name: String = "add_fields"
148156
let bridge: StageBridge
149157
private var selectables: [Selectable]
158+
let errorMessage: String?
150159

151160
init(selectables: [Selectable]) {
152161
self.selectables = selectables
153-
let objc_accumulators = selectables.reduce(into: [String: ExprBridge]()) {
154-
result,
155-
selectable
156-
in
157-
let selectableWrapper = selectable as! SelectableWrapper
158-
result[selectableWrapper.alias] = selectableWrapper.expr.toBridge()
162+
let (map, error) = Helper.selectablesToMap(selectables: selectables)
163+
if let error = error {
164+
errorMessage = error.localizedDescription
165+
bridge = AddFieldsStageBridge(fields: [:])
166+
} else {
167+
errorMessage = nil
168+
let objcAccumulators = map.mapValues { $0.toBridge() }
169+
bridge = AddFieldsStageBridge(fields: objcAccumulators)
159170
}
160-
bridge = AddFieldsStageBridge(fields: objc_accumulators)
161171
}
162172
}
163173

@@ -182,23 +192,37 @@ class RemoveFieldsStage: Stage {
182192
class Select: Stage {
183193
let name: String = "select"
184194
let bridge: StageBridge
195+
let errorMessage: String?
185196

186197
init(selections: [Selectable]) {
187-
let map = Helper.selectablesToMap(selectables: selections)
188-
bridge = SelectStageBridge(selections: map
189-
.mapValues { Helper.sendableToExpr($0).toBridge() })
198+
let (map, error) = Helper.selectablesToMap(selectables: selections)
199+
if let error = error {
200+
errorMessage = error.localizedDescription
201+
bridge = SelectStageBridge(selections: [:])
202+
} else {
203+
errorMessage = nil
204+
let objcSelections = map.mapValues { Helper.sendableToExpr($0).toBridge() }
205+
bridge = SelectStageBridge(selections: objcSelections)
206+
}
190207
}
191208
}
192209

193210
@available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
194211
class Distinct: Stage {
195212
let name: String = "distinct"
196213
let bridge: StageBridge
214+
let errorMessage: String?
197215

198216
init(groups: [Selectable]) {
199-
let map = Helper.selectablesToMap(selectables: groups)
200-
bridge = DistinctStageBridge(groups: map
201-
.mapValues { Helper.sendableToExpr($0).toBridge() })
217+
let (map, error) = Helper.selectablesToMap(selectables: groups)
218+
if let error = error {
219+
errorMessage = error.localizedDescription
220+
bridge = DistinctStageBridge(groups: [:])
221+
} else {
222+
errorMessage = nil
223+
let objcGroups = map.mapValues { Helper.sendableToExpr($0).toBridge() }
224+
bridge = DistinctStageBridge(groups: objcGroups)
225+
}
202226
}
203227
}
204228

@@ -208,18 +232,32 @@ class Aggregate: Stage {
208232
let bridge: StageBridge
209233
private var accumulators: [AliasedAggregate]
210234
private var groups: [String: Expression] = [:]
235+
let errorMessage: String?
211236

212237
init(accumulators: [AliasedAggregate], groups: [Selectable]?) {
213238
self.accumulators = accumulators
214-
if groups != nil {
215-
self.groups = Helper.selectablesToMap(selectables: groups!)
216-
}
217-
let accumulatorsMap = accumulators
218-
.reduce(into: [String: AggregateFunctionBridge]()) { result, accumulator in
219-
result[accumulator.alias] = accumulator.aggregate.bridge
239+
240+
if let groups = groups {
241+
let (map, error) = Helper.selectablesToMap(selectables: groups)
242+
if let error = error {
243+
errorMessage = error.localizedDescription
244+
bridge = AggregateStageBridge(accumulators: [:], groups: [:])
245+
return
220246
}
247+
self.groups = map
248+
}
249+
250+
let (accumulatorsMap, error) = Helper.aliasedAggregatesToMap(accumulators: accumulators)
251+
if let error = error {
252+
errorMessage = error.localizedDescription
253+
bridge = AggregateStageBridge(accumulators: [:], groups: [:])
254+
return
255+
}
256+
257+
errorMessage = nil
258+
let accumulatorBridgesMap = accumulatorsMap.mapValues { $0.bridge }
221259
bridge = AggregateStageBridge(
222-
accumulators: accumulatorsMap,
260+
accumulators: accumulatorBridgesMap,
223261
groups: self.groups.mapValues { Helper.sendableToExpr($0).toBridge() }
224262
)
225263
}

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

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,6 @@
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
1414

15-
extension AggregateFunction {
16-
func toBridge() -> AggregateFunctionBridge {
17-
return (self as AggregateBridgeWrapper).bridge
18-
}
19-
}
20-
2115
/// Represents an aggregate function in a pipeline.
2216
///
2317
/// An `AggregateFunction` is a function that computes a single value from a set of input values.

0 commit comments

Comments
 (0)