From 21b5f20ad46ba1b6a011fd8ff654d9d216c12036 Mon Sep 17 00:00:00 2001 From: Di Wu Date: Mon, 11 Mar 2024 21:48:59 -0700 Subject: [PATCH] change QueryPredicateOperation to a monoidal data structure --- .../DataStore/Query/QueryField.swift | 41 +++-- .../Query/QueryOperator+Equatable.swift | 2 + .../Query/QueryPredicate+Equatable.swift | 24 +-- .../DataStore/Query/QueryPredicate.swift | 149 ------------------ .../Query/QueryPredicateOperation.swift | 134 ++++++++++++++++ .../Core/AppSyncListProvider.swift | 12 +- .../Operation/AWSRESTOperationTests.swift | 4 +- .../GraphQLRequest+AnyModelWithSync.swift | 14 +- .../Support/QueryPredicate+GraphQL.swift | 57 ++----- .../Query/QueryPredicateEvaluateTests.swift | 2 +- .../Core/DataStoreListProvider.swift | 12 +- .../Storage/CascadeDeleteOperation.swift | 21 +-- .../SQLite/QueryPredicate+SQLite.swift | 8 +- .../SQLite/SQLStatement+Condition.swift | 136 ++++++++++------ .../Storage/SQLite/SQLStatement+Delete.swift | 18 +-- .../Storage/SQLite/SQLStatement+Select.swift | 2 +- .../Storage/SQLite/SQLStatement+Update.swift | 2 +- .../SQLite/StorageEngineAdapter+SQLite.swift | 12 +- .../Sync/Support/MutationEvent+Query.swift | 11 +- .../Core/QueryPredicateTests.swift | 56 +++---- .../Core/SQLStatementTests.swift | 19 ++- .../SQLiteStorageEngineAdapterJsonTests.swift | 18 ++- .../InitialSyncOperationTests.swift | 2 +- .../DataStore/ModelIdentifierTests.swift | 4 +- 24 files changed, 367 insertions(+), 393 deletions(-) create mode 100644 Amplify/Categories/DataStore/Query/QueryPredicateOperation.swift diff --git a/Amplify/Categories/DataStore/Query/QueryField.swift b/Amplify/Categories/DataStore/Query/QueryField.swift index afde18a94d..14db836c51 100644 --- a/Amplify/Categories/DataStore/Query/QueryField.swift +++ b/Amplify/Categories/DataStore/Query/QueryField.swift @@ -63,23 +63,23 @@ public struct QueryField: QueryFieldOperation { // MARK: - attributeExists public func attributeExists(_ value: Bool) -> QueryPredicateOperation { - return QueryPredicateOperation(field: name, operator: .attributeExists(value)) + return .operation(name, .attributeExists(value)) } // MARK: - beginsWith public func beginsWith(_ value: String) -> QueryPredicateOperation { - return QueryPredicateOperation(field: name, operator: .beginsWith(value)) + return .operation(name, .beginsWith(value)) } // MARK: - between public func between(start: Persistable, end: Persistable) -> QueryPredicateOperation { - return QueryPredicateOperation(field: name, operator: .between(start: start, end: end)) + return .operation(name, .between(start: start, end: end)) } // MARK: - contains public func contains(_ value: String) -> QueryPredicateOperation { - return QueryPredicateOperation(field: name, operator: .contains(value)) + return .operation(name, .contains(value)) } public static func ~= (key: Self, value: String) -> QueryPredicateOperation { @@ -88,17 +88,24 @@ public struct QueryField: QueryFieldOperation { // MARK: - not contains public func notContains(_ value: String) -> QueryPredicateOperation { - return QueryPredicateOperation(field: name, operator: .notContains(value)) + return .operation(name, .notContains(value)) } // MARK: - eq public func eq(_ value: Persistable?) -> QueryPredicateOperation { - return QueryPredicateOperation(field: name, operator: .equals(value)) + if let value { + return .operation(name, .equals(value)) + } else { + return .or([ + .operation(name, .attributeExists(false)), + .operation(name, .equals(value)) + ]) + } } public func eq(_ value: EnumPersistable) -> QueryPredicateOperation { - return QueryPredicateOperation(field: name, operator: .equals(value.rawValue)) + return .operation(name, .equals(value.rawValue)) } public static func == (key: Self, value: Persistable?) -> QueryPredicateOperation { @@ -112,7 +119,7 @@ public struct QueryField: QueryFieldOperation { // MARK: - ge public func ge(_ value: Persistable) -> QueryPredicateOperation { - return QueryPredicateOperation(field: name, operator: .greaterOrEqual(value)) + return .operation(name, .greaterOrEqual(value)) } public static func >= (key: Self, value: Persistable) -> QueryPredicateOperation { @@ -122,7 +129,7 @@ public struct QueryField: QueryFieldOperation { // MARK: - gt public func gt(_ value: Persistable) -> QueryPredicateOperation { - return QueryPredicateOperation(field: name, operator: .greaterThan(value)) + return .operation(name, .greaterThan(value)) } public static func > (key: Self, value: Persistable) -> QueryPredicateOperation { @@ -132,7 +139,7 @@ public struct QueryField: QueryFieldOperation { // MARK: - le public func le(_ value: Persistable) -> QueryPredicateOperation { - return QueryPredicateOperation(field: name, operator: .lessOrEqual(value)) + return .operation(name, .lessOrEqual(value)) } public static func <= (key: Self, value: Persistable) -> QueryPredicateOperation { @@ -142,7 +149,7 @@ public struct QueryField: QueryFieldOperation { // MARK: - lt public func lt(_ value: Persistable) -> QueryPredicateOperation { - return QueryPredicateOperation(field: name, operator: .lessThan(value)) + return .operation(name, .lessThan(value)) } public static func < (key: Self, value: Persistable) -> QueryPredicateOperation { @@ -152,11 +159,19 @@ public struct QueryField: QueryFieldOperation { // MARK: - ne public func ne(_ value: Persistable?) -> QueryPredicateOperation { - return QueryPredicateOperation(field: name, operator: .notEqual(value)) + if let value { + return .operation(name, .notEqual(value)) + } else { + return .and([ + .operation(name, .attributeExists(true)), + .operation(name, .notEqual(value)) + ]) + } + } public func ne(_ value: EnumPersistable) -> QueryPredicateOperation { - return QueryPredicateOperation(field: name, operator: .notEqual(value.rawValue)) + return .operation(name, .notEqual(value.rawValue)) } public static func != (key: Self, value: Persistable?) -> QueryPredicateOperation { diff --git a/Amplify/Categories/DataStore/Query/QueryOperator+Equatable.swift b/Amplify/Categories/DataStore/Query/QueryOperator+Equatable.swift index 41ee77159b..c1907802b6 100644 --- a/Amplify/Categories/DataStore/Query/QueryOperator+Equatable.swift +++ b/Amplify/Categories/DataStore/Query/QueryOperator+Equatable.swift @@ -24,6 +24,8 @@ extension QueryOperator: Equatable { case let (.between(oneStart, oneEnd), .between(otherStart, otherEnd)): return PersistableHelper.isEqual(oneStart, otherStart) && PersistableHelper.isEqual(oneEnd, otherEnd) + case let (.attributeExists(one), .attributeExists(other)): + return one == other default: return false } diff --git a/Amplify/Categories/DataStore/Query/QueryPredicate+Equatable.swift b/Amplify/Categories/DataStore/Query/QueryPredicate+Equatable.swift index db140e5a0f..0134cb8eca 100644 --- a/Amplify/Categories/DataStore/Query/QueryPredicate+Equatable.swift +++ b/Amplify/Categories/DataStore/Query/QueryPredicate+Equatable.swift @@ -14,28 +14,6 @@ private func isEqual(_ one: QueryPredicate?, to other: QueryPredicate?) -> Bool if let one = one as? QueryPredicateOperation, let other = other as? QueryPredicateOperation { return one == other } - if let one = one as? QueryPredicateGroup, let other = other as? QueryPredicateGroup { - return one == other - } - return false -} - -extension QueryPredicateOperation: Equatable { - - public static func == (lhs: QueryPredicateOperation, rhs: QueryPredicateOperation) -> Bool { - return lhs.field == rhs.field && lhs.operator == rhs.operator - } - -} - -extension QueryPredicateGroup: Equatable { - - public static func == (lhs: QueryPredicateGroup, rhs: QueryPredicateGroup) -> Bool { - return lhs.type == rhs.type - && lhs.predicates.count == rhs.predicates.count - && lhs.predicates.enumerated().first { - !isEqual($0.element, to: rhs.predicates[$0.offset]) - } == nil - } + return false } diff --git a/Amplify/Categories/DataStore/Query/QueryPredicate.swift b/Amplify/Categories/DataStore/Query/QueryPredicate.swift index 222bd11c6e..92c82d3d96 100644 --- a/Amplify/Categories/DataStore/Query/QueryPredicate.swift +++ b/Amplify/Categories/DataStore/Query/QueryPredicate.swift @@ -9,152 +9,3 @@ import Foundation /// Protocol that indicates concrete types conforming to it can be used a predicate member. public protocol QueryPredicate: Evaluable, Encodable {} - -public enum QueryPredicateGroupType: String, Encodable { - case and - case or - case not -} - -/// The `not` function is used to wrap a `QueryPredicate` in a `QueryPredicateGroup` of type `.not`. -/// - Parameter predicate: the `QueryPredicate` (either operation or group) -/// - Returns: `QueryPredicateGroup` of type `.not` -public func not(_ predicate: Predicate) -> QueryPredicateGroup { - return QueryPredicateGroup(type: .not, predicates: [predicate]) -} - -/// The case `.all` is a predicate used as an argument to select all of a single modeltype. We -/// chose `.all` instead of `nil` because we didn't want to use the implicit nature of `nil` to -/// specify an action applies to an entire data set. -public enum QueryPredicateConstant: QueryPredicate, Encodable { - case all - public func evaluate(target: Model) -> Bool { - return true - } -} - -public class QueryPredicateGroup: QueryPredicate, Encodable { - public internal(set) var type: QueryPredicateGroupType - public internal(set) var predicates: [QueryPredicate] - - public init(type: QueryPredicateGroupType = .and, - predicates: [QueryPredicate] = []) { - self.type = type - self.predicates = predicates - } - - public func and(_ predicate: QueryPredicate) -> QueryPredicateGroup { - if case .and = type { - predicates.append(predicate) - return self - } - return QueryPredicateGroup(type: .and, predicates: [self, predicate]) - } - - public func or(_ predicate: QueryPredicate) -> QueryPredicateGroup { - if case .or = type { - predicates.append(predicate) - return self - } - return QueryPredicateGroup(type: .or, predicates: [self, predicate]) - } - - public static func && (lhs: QueryPredicateGroup, rhs: QueryPredicate) -> QueryPredicateGroup { - return lhs.and(rhs) - } - - public static func || (lhs: QueryPredicateGroup, rhs: QueryPredicate) -> QueryPredicateGroup { - return lhs.or(rhs) - } - - public static prefix func ! (rhs: QueryPredicateGroup) -> QueryPredicateGroup { - return not(rhs) - } - - public func evaluate(target: Model) -> Bool { - switch type { - case .or: - for predicate in predicates { - if predicate.evaluate(target: target) { - return true - } - } - return false - case .and: - for predicate in predicates { - if !predicate.evaluate(target: target) { - return false - } - } - return true - case .not: - let predicate = predicates[0] - return !predicate.evaluate(target: target) - } - } - - // MARK: - Encodable conformance - - private enum CodingKeys: String, CodingKey { - case type - case predicates - } - - struct AnyQueryPredicate: Encodable { - private let _encode: (Encoder) throws -> Void - - init(_ base: QueryPredicate) { - _encode = base.encode - } - - func encode(to encoder: Encoder) throws { - try _encode(encoder) - } - } - - public func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(type.rawValue, forKey: .type) - - let anyPredicates = predicates.map(AnyQueryPredicate.init) - try container.encode(anyPredicates, forKey: .predicates) - } - -} - -public class QueryPredicateOperation: QueryPredicate, Encodable { - - public let field: String - public let `operator`: QueryOperator - - public init(field: String, operator: QueryOperator) { - self.field = field - self.operator = `operator` - } - - public func and(_ predicate: QueryPredicate) -> QueryPredicateGroup { - let group = QueryPredicateGroup(type: .and, predicates: [self, predicate]) - return group - } - - public func or(_ predicate: QueryPredicate) -> QueryPredicateGroup { - let group = QueryPredicateGroup(type: .or, predicates: [self, predicate]) - return group - } - - public static func && (lhs: QueryPredicateOperation, rhs: QueryPredicate) -> QueryPredicateGroup { - return lhs.and(rhs) - } - - public static func || (lhs: QueryPredicateOperation, rhs: QueryPredicate) -> QueryPredicateGroup { - return lhs.or(rhs) - } - - public static prefix func ! (rhs: QueryPredicateOperation) -> QueryPredicateGroup { - return not(rhs) - } - - public func evaluate(target: Model) -> Bool { - return self.operator.evaluate(target: target[field]?.flatMap { $0 }) - } -} diff --git a/Amplify/Categories/DataStore/Query/QueryPredicateOperation.swift b/Amplify/Categories/DataStore/Query/QueryPredicateOperation.swift new file mode 100644 index 0000000000..d70f6f9602 --- /dev/null +++ b/Amplify/Categories/DataStore/Query/QueryPredicateOperation.swift @@ -0,0 +1,134 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + + +import Foundation + +public indirect enum QueryPredicateOperation { + case `true` + case `false` + case operation(String, QueryOperator) + case and([QueryPredicateOperation]) + case or([QueryPredicateOperation]) + case not(QueryPredicateOperation) + + public var `operator`: String { + switch self { + case .and: return "and" + case .or: return "or" + case .not: return "not" + default: return "" + } + } +} + +public class QueryPredicateConstant { + public static let all = QueryPredicateOperation.true +} + +extension QueryPredicateOperation { + public func and(_ rhs: QueryPredicateOperation) -> QueryPredicateOperation { + switch (self, rhs) { + case let (.and(lhsPredicates), .and(rhsPredicates)): + return .and(lhsPredicates + rhsPredicates) + case let (.and(predicates), _): + return .and(predicates + [rhs]) + case let (_, .and(predicates)): + return .and([self] + predicates) + default: + return .and([self, rhs]) + } + } + + public func or(_ rhs: QueryPredicateOperation) -> QueryPredicateOperation { + switch (self, rhs) { + case let (.or(lhsPredicates), .or(rhsPredicates)): + return .or(lhsPredicates + rhsPredicates) + case let (.or(predicates), _): + return .or(predicates + [rhs]) + case let (_, .or(predicates)): + return .or([self] + predicates) + default: + return .or([self, rhs]) + } + } + + public func not() -> QueryPredicateOperation { + .not(self) + } +} + +extension QueryPredicateOperation { + public static func && ( + lhs: QueryPredicateOperation, + rhs: QueryPredicateOperation + ) -> QueryPredicateOperation { + lhs.and(rhs) + } + + public static func || ( + lhs: QueryPredicateOperation, + rhs: QueryPredicateOperation + ) -> QueryPredicateOperation { + lhs.or(rhs) + } + + public static prefix func ! (rhs: QueryPredicateOperation) -> QueryPredicateOperation { + not(rhs) + } +} + +extension QueryPredicateOperation: QueryPredicate { + public func evaluate(target: Model) -> Bool { + switch self { + case .true: return true + case .false: return false + case let .operation(field, op): + return op.evaluate(target: target[field]?.flatMap { $0 }) + case let .and(predicates): + return predicates.reduce(true, { $0 && $1.evaluate(target: target) }) + case let .or(predicates): + return predicates.reduce(false, { $0 || $1.evaluate(target: target) }) + case let .not(op): + return !op.evaluate(target: target) + } + } +} + +extension QueryPredicateOperation: Equatable { + + public static func == (lhs: QueryPredicateOperation, rhs: QueryPredicateOperation) -> Bool { + switch (lhs, rhs) { + case (.true, .true): + return true + case (.false, .false): + return true + case let (.operation(lfield, lop), .operation(rfield, rop)): + return lfield == rfield && lop == rop + case let (.and(lpredicates), .and(rpredicates)): + return lpredicates == rpredicates + case let (.or(lpredicates), .or(rpredicates)): + return lpredicates == rpredicates + case let (.not(lpredicate), .not(rpredicate)): + return lpredicate == rpredicate + default: + return false + } + } + +} + +extension Array where Element == QueryPredicateOperation { + public func fold( + _ nextPartialResult: (QueryPredicateOperation, QueryPredicateOperation) -> QueryPredicateOperation + ) -> QueryPredicateOperation? { + if self.isEmpty { return nil } + if self.count == 1 { return self.first } + + return self.dropFirst().reduce(self.first!, nextPartialResult) + } +} diff --git a/AmplifyPlugins/API/Sources/AWSAPIPlugin/Core/AppSyncListProvider.swift b/AmplifyPlugins/API/Sources/AWSAPIPlugin/Core/AppSyncListProvider.swift index 2edf147c1b..58cb7275d5 100644 --- a/AmplifyPlugins/API/Sources/AWSAPIPlugin/Core/AppSyncListProvider.swift +++ b/AmplifyPlugins/API/Sources/AWSAPIPlugin/Core/AppSyncListProvider.swift @@ -112,16 +112,14 @@ public class AppSyncListProvider: ModelListProvider { let predicate: QueryPredicate = field(associatedField) == associatedId filter = predicate.graphQLFilter(for: Element.schema) } else { - var queryPredicates: [QueryPredicateOperation] = [] let columnNames = columnNames(fields: associatedFields, Element.schema) let predicateValues = zip(columnNames, associatedIdentifiers) - for (identifierName, identifierValue) in predicateValues { - queryPredicates.append(QueryPredicateOperation(field: identifierName, - operator: .equals(identifierValue))) - } - let groupedQueryPredicates = QueryPredicateGroup(type: .and, predicates: queryPredicates) - filter = groupedQueryPredicates.graphQLFilter(for: Element.schema) + let queryPredicate = predicateValues + .map { QueryPredicateOperation.operation($0.0, .equals($0.1)) } + .fold(&&) + + filter = queryPredicate?.graphQLFilter(for: Element.schema) ?? [:] } let request = GraphQLRequest.listQuery(responseType: JSONValue.self, diff --git a/AmplifyPlugins/API/Tests/AWSAPIPluginTests/Operation/AWSRESTOperationTests.swift b/AmplifyPlugins/API/Tests/AWSAPIPluginTests/Operation/AWSRESTOperationTests.swift index c65f1257ac..7a35bb8934 100644 --- a/AmplifyPlugins/API/Tests/AWSAPIPluginTests/Operation/AWSRESTOperationTests.swift +++ b/AmplifyPlugins/API/Tests/AWSAPIPluginTests/Operation/AWSRESTOperationTests.swift @@ -33,7 +33,7 @@ class AWSRESTOperationTests: OperationTestBase { } // TODO: Fix this test - func testGetReturnsOperation() throws { + func testGetReturnsOperation() async throws { try setUpPlugin(endpointType: .rest) // Use this as a semaphore to ensure the task is cleaned up before proceeding to the next test @@ -50,7 +50,7 @@ class AWSRESTOperationTests: OperationTestBase { XCTAssertNotNil(operation.request) - waitForExpectations(timeout: 1.00) + await fulfillment(of: [listenerWasInvoked], timeout: 3.00) } func testGetFailsWithBadAPIName() throws { diff --git a/AmplifyPlugins/Core/AWSPluginsCore/Model/GraphQLRequest/GraphQLRequest+AnyModelWithSync.swift b/AmplifyPlugins/Core/AWSPluginsCore/Model/GraphQLRequest/GraphQLRequest+AnyModelWithSync.swift index 44af846765..865088ce85 100644 --- a/AmplifyPlugins/Core/AWSPluginsCore/Model/GraphQLRequest/GraphQLRequest+AnyModelWithSync.swift +++ b/AmplifyPlugins/Core/AWSPluginsCore/Model/GraphQLRequest/GraphQLRequest+AnyModelWithSync.swift @@ -278,15 +278,11 @@ extension GraphQLRequest: ModelSyncGraphQLRequestFactory { /// If the provided group is of type AND, the optimization will occur. /// If the top level group is OR or NOT, the optimization is not possible anyway. private static func optimizePredicate(_ predicate: QueryPredicate?) -> QueryPredicate? { - guard let predicate = predicate else { - return nil + if let predicate = predicate as? QueryPredicateOperation, + case .operation = predicate { + return QueryPredicateOperation.and([predicate]) } - if predicate as? QueryPredicateGroup != nil { - return predicate - } else if let predicate = predicate as? QueryPredicateConstant, - predicate == .all { - return predicate - } - return QueryPredicateGroup(type: .and, predicates: [predicate]) + + return predicate } } diff --git a/AmplifyPlugins/Core/AWSPluginsCore/Model/Support/QueryPredicate+GraphQL.swift b/AmplifyPlugins/Core/AWSPluginsCore/Model/Support/QueryPredicate+GraphQL.swift index 02db005dcc..43ddb39495 100644 --- a/AmplifyPlugins/Core/AWSPluginsCore/Model/Support/QueryPredicate+GraphQL.swift +++ b/AmplifyPlugins/Core/AWSPluginsCore/Model/Support/QueryPredicate+GraphQL.swift @@ -75,10 +75,6 @@ extension QueryPredicate { public func graphQLFilter(for modelSchema: ModelSchema?) -> GraphQLFilter { if let operation = self as? QueryPredicateOperation { return operation.graphQLFilter(for: modelSchema) - } else if let group = self as? QueryPredicateGroup { - return group.graphQLFilter(for: modelSchema) - } else if let constant = self as? QueryPredicateConstant { - return constant.graphQLFilter(for: modelSchema) } return Fatal.preconditionFailure( @@ -91,8 +87,6 @@ extension QueryPredicate { public var graphQLFilter: GraphQLFilter { if let operation = self as? QueryPredicateOperation { return operation.graphQLFilter(for: nil) - } else if let group = self as? QueryPredicateGroup { - return group.graphQLFilter(for: nil) } return Fatal.preconditionFailure( @@ -100,26 +94,28 @@ extension QueryPredicate { } } -extension QueryPredicateConstant: GraphQLFilterConvertible { +extension QueryPredicateOperation: GraphQLFilterConvertible { func graphQLFilter(for modelSchema: ModelSchema?) -> GraphQLFilter { - if self == .all { + switch self { + case let .operation(field, op): + let filterValue = [op.graphQLOperator: op.value] + if let modelSchema { + return [columnName(modelSchema, field: field): filterValue] + } else { + return [field: filterValue] + } + case let .and(predicates), + let .or(predicates): + return [self.operator: predicates.map { $0.graphQLFilter(for: modelSchema) }] + case let .not(op): + return [self.operator: op.graphQLFilter(for: modelSchema)] + default: return [:] } - return Fatal.preconditionFailure("Could not find QueryPredicateConstant \(self)") } -} - -extension QueryPredicateOperation: GraphQLFilterConvertible { - func graphQLFilter(for modelSchema: ModelSchema?) -> GraphQLFilter { - let filterValue = [self.operator.graphQLOperator: self.operator.value] - guard let modelSchema = modelSchema else { - return [field: filterValue] - } - return [columnName(modelSchema): filterValue] - } + func columnName(_ modelSchema: ModelSchema, field: String) -> String { - func columnName(_ modelSchema: ModelSchema) -> String { guard let modelField = modelSchema.field(withName: field) else { return field } @@ -143,27 +139,6 @@ extension QueryPredicateOperation: GraphQLFilterConvertible { } } -extension QueryPredicateGroup: GraphQLFilterConvertible { - - func graphQLFilter(for modelSchema: ModelSchema?) -> GraphQLFilter { - let logicalOperator = type.rawValue - switch type { - case .and, .or: - var graphQLPredicateOperation = [logicalOperator: [Any]()] - predicates.forEach { predicate in - graphQLPredicateOperation[logicalOperator]?.append(predicate.graphQLFilter(for: modelSchema)) - } - return graphQLPredicateOperation - case .not: - if let predicate = predicates.first { - return [logicalOperator: predicate.graphQLFilter(for: modelSchema)] - } else { - return Fatal.preconditionFailure("Missing predicate for \(String(describing: self)) with type: \(type)") - } - } - } -} - extension QueryOperator { var graphQLOperator: String { switch self { diff --git a/AmplifyPlugins/Core/AWSPluginsCoreTests/Query/QueryPredicateEvaluateTests.swift b/AmplifyPlugins/Core/AWSPluginsCoreTests/Query/QueryPredicateEvaluateTests.swift index b897e1c1f0..ff1b3feb71 100644 --- a/AmplifyPlugins/Core/AWSPluginsCoreTests/Query/QueryPredicateEvaluateTests.swift +++ b/AmplifyPlugins/Core/AWSPluginsCoreTests/Query/QueryPredicateEvaluateTests.swift @@ -146,7 +146,7 @@ class QueryPredicateEvaluateTests: XCTestCase { XCTAssert(evaluation) } - func testConstantall() throws { + func testConstantAll() throws { let predicate = QueryPredicateConstant.all let instance = QPredGen(name: "test") diff --git a/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Core/DataStoreListProvider.swift b/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Core/DataStoreListProvider.swift index 59aee21603..4f4a82e13f 100644 --- a/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Core/DataStoreListProvider.swift +++ b/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Core/DataStoreListProvider.swift @@ -43,7 +43,7 @@ public class DataStoreListProvider: ModelListProvider { case .loaded(let elements): return elements case .notLoaded(let associatedIdentifiers, let associatedFields): - let predicate: QueryPredicate + let predicate: QueryPredicate? if associatedIdentifiers.count == 1, let associatedId = associatedIdentifiers.first, let associatedField = associatedFields.first { @@ -51,13 +51,11 @@ public class DataStoreListProvider: ModelListProvider { predicate = field(associatedField) == associatedId } else { let predicateValues = zip(associatedFields, associatedIdentifiers) - var queryPredicates: [QueryPredicateOperation] = [] - for (identifierName, identifierValue) in predicateValues { - queryPredicates.append(QueryPredicateOperation(field: identifierName, - operator: .equals(identifierValue))) - } + predicate = predicateValues + .map { .operation($0.0, .equals($0.1)) } + .fold(&&) + self.log.verbose("Loading List of \(Element.schema.name) by \(associatedFields) == \(associatedIdentifiers) ") - predicate = QueryPredicateGroup(type: .and, predicates: queryPredicates) } do { diff --git a/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Storage/CascadeDeleteOperation.swift b/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Storage/CascadeDeleteOperation.swift index 29687e390c..401888434a 100644 --- a/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Storage/CascadeDeleteOperation.swift +++ b/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Storage/CascadeDeleteOperation.swift @@ -220,11 +220,9 @@ public class CascadeDeleteOperation: AsynchronousOperation { let chunkedArrays = ids.chunked(into: SQLiteStorageEngineAdapter.maxNumberOfPredicates) for chunkedArray in chunkedArrays { // TODO: Add conveinence to queryPredicate where we have a list of items, to be all or'ed - var queryPredicates: [QueryPredicateOperation] = [] - for id in chunkedArray { - queryPredicates.append(QueryPredicateOperation(field: associatedField.name, operator: .equals(id))) - } - let groupedQueryPredicates = QueryPredicateGroup(type: .or, predicates: queryPredicates) + let groupedQueryPredicates = chunkedArray + .map { QueryPredicateOperation.operation(associatedField.name, .equals($0)) } + .fold(||) do { let models = try await withCheckedThrowingContinuation { continuation in @@ -519,8 +517,8 @@ public class CascadeDeleteOperation: AsynchronousOperation { // MARK: - Supporting types extension CascadeDeleteOperation { - struct QueryAndDeleteResult { - let deletedModels: [M] + struct QueryAndDeleteResult { + let deletedModels: [T] let associatedModels: [(ModelName, Model)] } @@ -535,9 +533,12 @@ extension CascadeDeleteOperation { case .withIdentifier(let identifier): return identifier.predicate case .withIdentifierAndCondition(let identifier, let predicate): - return QueryPredicateGroup(type: .and, - predicates: [identifier.predicate, - predicate]) + guard let identifierPredicate = identifier.predicate as? QueryPredicateOperation, + let predicate = predicate as? QueryPredicateOperation + else { + return identifier.predicate + } + return identifierPredicate && predicate case .withFilter(let predicate): return predicate } diff --git a/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Storage/SQLite/QueryPredicate+SQLite.swift b/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Storage/SQLite/QueryPredicate+SQLite.swift index 17da2d7ff2..0cef259e71 100644 --- a/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Storage/SQLite/QueryPredicate+SQLite.swift +++ b/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Storage/SQLite/QueryPredicate+SQLite.swift @@ -53,16 +53,16 @@ extension QueryOperator { .beginsWith(let value), .notContains(let value): return [value.asBinding()] - case .attributeExists: - return [] + case .attributeExists(let value): + return [value.asBinding()] } } } extension QueryPredicate { var isAll: Bool { - if let allPredicate = self as? QueryPredicateConstant, allPredicate == .all { - return true + if let predicate = self as? QueryPredicateOperation { + return predicate == QueryPredicateConstant.all } else { return false } diff --git a/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Storage/SQLite/SQLStatement+Condition.swift b/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Storage/SQLite/SQLStatement+Condition.swift index 6d8d55136c..ccd09efaeb 100644 --- a/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Storage/SQLite/SQLStatement+Condition.swift +++ b/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Storage/SQLite/SQLStatement+Condition.swift @@ -18,68 +18,112 @@ typealias SQLPredicate = (String, [Binding?]) /// - modelSchema: the model schema of the `Model` /// - predicate: the query predicate /// - Returns: a tuple containing the SQL string and the associated values -private func translateQueryPredicate(from modelSchema: ModelSchema, - predicate: QueryPredicate, - namespace: Substring? = nil) -> SQLPredicate { - var sql: [String] = [] - var bindings: [Binding?] = [] - let indentPrefix = " " - var indentSize = 1 - - func translate(_ pred: QueryPredicate, predicateIndex: Int, groupType: QueryPredicateGroupType) { - let indent = String(repeating: indentPrefix, count: indentSize) - if let operation = pred as? QueryPredicateOperation { - let column = resolveColumn(operation) - if predicateIndex == 0 { - sql.append("\(indent)\(operation.operator.sqlOperation(column: column))") - } else { - sql.append("\(indent)\(groupType.rawValue) \(operation.operator.sqlOperation(column: column))") - } - - bindings.append(contentsOf: operation.operator.bindings) - } else if let group = pred as? QueryPredicateGroup { - var shouldClose = false - - if predicateIndex == 0 { - sql.append("\(indent)(") - } else { - sql.append("\(indent)\(groupType.rawValue) (") - } - - indentSize += 1 - shouldClose = true +private func translateQueryPredicate( + from modelSchema: ModelSchema, + predicate: QueryPredicate, + namespace: Substring? = nil +) -> SQLPredicate { + let indentPrefix = " " + let indentSize = 2 + + func translate(_ predicate: QueryPredicateOperation, indentationLevel: Int) -> (String, [Binding?]) { + func padding(_ level: Int) -> String { + String(repeating: indentPrefix, count: indentSize * level) + } - for index in 0 ..< group.predicates.count { - translate(group.predicates[index], predicateIndex: index, groupType: group.type) + func statement(_ sqls: [String], predicate: QueryPredicateOperation) -> String { + switch sqls.count { + case 0: return "" + case 1: return "\(padding(indentationLevel))\(predicate.operator) \(sqls.joined())" + default: + let statements = sqls.joined(separator: "\n\(padding(indentationLevel + 1))\(predicate.operator) ") + return """ + ( + \(padding(indentationLevel + 1))\(statements) + \(padding(indentationLevel))) + """ } + } - if shouldClose { - indentSize -= 1 - sql.append("\(indent))") - } - } else if let constant = pred as? QueryPredicateConstant { - if case .all = constant { - sql.append("or 1 = 1") - } + switch predicate { + case let .operation(field, op): + let column = resolveColumn(field) + return (op.sqlOperation(column: column), op.bindings) + case .and(let predicates), + .or(let predicates): + let sqls = predicates.map { translate($0, indentationLevel: indentationLevel + 1) } + return ( + statement(sqls.map(\.0), predicate: predicate), + sqls.map(\.1).flatMap { $0 } + ) + case let .not(predicate): + let sql = translate(predicate, indentationLevel: indentationLevel + 1) + return ( + statement([sql.0], predicate: predicate), + sql.1 + ) + case .true: + return ("1 = 1", []) + case .false: + return ("1 = 0", []) } } - func resolveColumn(_ operation: QueryPredicateOperation) -> String { - let modelField = modelSchema.field(withName: operation.field) + func resolveColumn(_ field: String) -> String { + let modelField = modelSchema.field(withName: field) if let namespace = namespace, let modelField = modelField { return modelField.columnName(forNamespace: String(namespace)) } else if let modelField = modelField { return modelField.columnName() } else if let namespace = namespace { - return String(namespace).quoted() + "." + operation.field.quoted() + return String(namespace).quoted() + "." + field.quoted() + } + return field.quoted() + } + + func deduplicate(_ predicate: QueryPredicateOperation) -> QueryPredicateOperation { + func rewritePredicate(_ predicate: QueryPredicateOperation) -> QueryPredicateOperation { + switch predicate { + case let .operation(field, op): + if case .attributeExists(let bool) = op { + return bool ? .operation(field, .notEqual(nil)) + : .operation(field, .equals(nil)) + } + return predicate + case .and, .or: + return deduplicate(predicate) + default: + return predicate + } + } + + switch predicate { + case let .and(predicates): + let optimizedPredicates = predicates.map(rewritePredicate(_:)).reduce([]) { result, predicate in + result.contains(where: { predicate == $0 }) ? result : result + [predicate] + } + return optimizedPredicates.count == 1 + ? optimizedPredicates.first! + : .and(optimizedPredicates) + case let .or(predicates): + let optimizedPredicates = predicates.map(rewritePredicate(_:)).reduce([]) { result, predicate in + result.contains(where: { predicate == $0 }) ? result : result + [predicate] + } + return optimizedPredicates.count == 1 + ? optimizedPredicates.first! + : .or(optimizedPredicates) + default: + return predicate } - return operation.field.quoted() } // the very first `and` is always prepended, using -1 for if statement checking // the very first `and` is to connect `where` clause with translated QueryPredicate - translate(predicate, predicateIndex: -1, groupType: .and) - return (sql.joined(separator: "\n"), bindings) + + guard let predicate = predicate as? QueryPredicateOperation else { + return ("", []) + } + return translate(QueryPredicateOperation.and([deduplicate(predicate)]), indentationLevel: 0) } /// Represents a partial SQL statement with query conditions. This type can be used to diff --git a/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Storage/SQLite/SQLStatement+Delete.swift b/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Storage/SQLite/SQLStatement+Delete.swift index 26290d6968..5de255f905 100644 --- a/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Storage/SQLite/SQLStatement+Delete.swift +++ b/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Storage/SQLite/SQLStatement+Delete.swift @@ -39,14 +39,14 @@ struct DeleteStatement: SQLStatement { predicate: predicate) } - init(modelSchema: ModelSchema, - withIdentifier id: ModelIdentifierProtocol, - predicate: QueryPredicate? = nil) { - var queryPredicate: QueryPredicate = field(modelSchema.primaryKey.sqlName) - .eq(id.stringValue) - if let predicate = predicate { - queryPredicate = field(modelSchema.primaryKey.sqlName) - .eq(id.stringValue).and(predicate) + init( + modelSchema: ModelSchema, + withIdentifier id: ModelIdentifierProtocol, + predicate: QueryPredicate? = nil + ) { + var queryPredicate = field(modelSchema.primaryKey.sqlName).eq(id.stringValue) + if let predicate = predicate as? QueryPredicateOperation { + queryPredicate = queryPredicate.and(predicate) } self.init(modelSchema: modelSchema, predicate: queryPredicate) } @@ -64,7 +64,7 @@ struct DeleteStatement: SQLStatement { return """ \(sql) where 1 = 1 - \(conditionStatement.stringValue) + \(conditionStatement.stringValue) """ } return sql diff --git a/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Storage/SQLite/SQLStatement+Select.swift b/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Storage/SQLite/SQLStatement+Select.swift index 9a0b9e909a..5ca8209fa6 100644 --- a/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Storage/SQLite/SQLStatement+Select.swift +++ b/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Storage/SQLite/SQLStatement+Select.swift @@ -54,7 +54,7 @@ struct SelectStatementMetadata { sql = """ \(sql) where 1 = 1 - \(conditionStatement.stringValue) + \(conditionStatement.stringValue) """ } diff --git a/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Storage/SQLite/SQLStatement+Update.swift b/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Storage/SQLite/SQLStatement+Update.swift index 70df1854ec..e759dd4a4e 100644 --- a/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Storage/SQLite/SQLStatement+Update.swift +++ b/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Storage/SQLite/SQLStatement+Update.swift @@ -48,7 +48,7 @@ struct UpdateStatement: SQLStatement { if let conditionStatement = conditionStatement { sql = """ \(sql) - \(conditionStatement.stringValue) + \(conditionStatement.stringValue) """ } diff --git a/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Storage/SQLite/StorageEngineAdapter+SQLite.swift b/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Storage/SQLite/StorageEngineAdapter+SQLite.swift index d01c94bacb..d9feaa2fad 100644 --- a/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Storage/SQLite/StorageEngineAdapter+SQLite.swift +++ b/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Storage/SQLite/StorageEngineAdapter+SQLite.swift @@ -457,14 +457,10 @@ final class SQLiteStorageEngineAdapter: StorageEngineAdapter { var results = [MutationSyncMetadata]() let chunkedModelIdsArr = modelIds.chunked(into: SQLiteStorageEngineAdapter.maxNumberOfPredicates) for chunkedModelIds in chunkedModelIdsArr { - var queryPredicates: [QueryPredicateOperation] = [] - for id in chunkedModelIds { - let mutationSyncMetadataId = MutationSyncMetadata.identifier(modelName: modelName, - modelId: id) - queryPredicates.append(QueryPredicateOperation(field: fields.id.stringValue, - operator: .equals(mutationSyncMetadataId))) - } - let groupedQueryPredicates = QueryPredicateGroup(type: .or, predicates: queryPredicates) + let groupedQueryPredicates = chunkedModelIds + .map { MutationSyncMetadata.identifier(modelName: modelName, modelId: $0) } + .map { QueryPredicateOperation.operation(fields.id.stringValue, .equals($0)) } + .fold(||) let statement = SelectStatement(from: modelSchema, predicate: groupedQueryPredicates) let rows = try connection.prepare(statement.stringValue).run(statement.variables) let result = try rows.convert(to: modelType, diff --git a/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Sync/Support/MutationEvent+Query.swift b/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Sync/Support/MutationEvent+Query.swift index d84dad4ce7..e3bbf337f3 100644 --- a/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Sync/Support/MutationEvent+Query.swift +++ b/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Sync/Support/MutationEvent+Query.swift @@ -66,13 +66,10 @@ extension MutationEvent { let chunkedArrays = modelIds.chunked(into: SQLiteStorageEngineAdapter.maxNumberOfPredicates) var queriedMutationEvents: [MutationEvent] = [] for chunkedArray in chunkedArrays { - var queryPredicates: [QueryPredicateGroup] = [] - for (id, name) in chunkedArray { - let operation = fields.modelId == id && fields.modelName == name - queryPredicates.append(operation) - } - let groupedQueryPredicates = QueryPredicateGroup(type: .or, predicates: queryPredicates) - let final = QueryPredicateGroup(type: .and, predicates: [groupedQueryPredicates, predicate]) + let groupedQueryPredicates = chunkedArray + .map { fields.modelId == $0.0 && fields.modelName == $0.1 } + .fold(||) + let final = groupedQueryPredicates?.and(predicate) ?? predicate let sort = QuerySortDescriptor(fieldName: fields.createdAt.stringValue, order: .ascending) do { diff --git a/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Core/QueryPredicateTests.swift b/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Core/QueryPredicateTests.swift index 77bd4cea56..02f979f213 100644 --- a/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Core/QueryPredicateTests.swift +++ b/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Core/QueryPredicateTests.swift @@ -30,7 +30,7 @@ class QueryPredicateTests: XCTestCase { let predicate = post.draft.eq(true) XCTAssertEqual(predicate, post.draft == true) - XCTAssertEqual(predicate, QueryPredicateOperation(field: "draft", operator: .equals(true))) + XCTAssertEqual(predicate, QueryPredicateOperation.operation("draft", .equals(true))) } /// it should create a simple `QueryPredicateGroup` @@ -38,13 +38,11 @@ class QueryPredicateTests: XCTestCase { let post = Post.keys let predicate = post.draft.eq(true).and(post.id.ne(nil)) - let expected = QueryPredicateGroup( - type: .and, - predicates: [ - QueryPredicateOperation(field: "draft", operator: .equals(true)), - QueryPredicateOperation(field: "id", operator: .notEqual(nil)) - ] - ) + let expected = QueryPredicateOperation.and([ + .operation("draft", .equals(true)), + .operation("id", .attributeExists(true)), + .operation("id", .notEqual(nil)) + ]) XCTAssertEqual(predicate, expected) @@ -60,29 +58,19 @@ class QueryPredicateTests: XCTestCase { let predicate = post.draft.eq(true) .and(post.id.ne(nil)) .and(post.title.beginsWith("gelato").or(post.title.beginsWith("ice cream"))) - .and(not(post.content.contains("italia"))) - - let expected = QueryPredicateGroup( - type: .and, - predicates: [ - QueryPredicateOperation(field: "draft", operator: .equals(true)), - QueryPredicateOperation(field: "id", operator: .notEqual(nil)), - QueryPredicateGroup( - type: .or, - predicates: [ - QueryPredicateOperation(field: "title", operator: .beginsWith("gelato")), - QueryPredicateOperation(field: "title", operator: .beginsWith("ice cream")) - ] - ), - QueryPredicateGroup( - type: .not, - predicates: [ - QueryPredicateOperation(field: "content", operator: .contains("italia")) - ] - ) - ] - ) - XCTAssert(predicate == expected) + .and(.not(post.content.contains("italia"))) + + let expected = QueryPredicateOperation.and([ + .operation("draft", .equals(true)), + .operation("id", .attributeExists(true)), + .operation("id", .notEqual(nil)), + .or([ + .operation("title", .beginsWith("gelato")), + .operation("title", .beginsWith("ice cream")) + ]), + .not(.operation("content", .contains("italia"))) + ]) + XCTAssertEqual(predicate, expected) let predicateString = String(data: try! encoder.encode(predicate), encoding: .utf8)! let expectedString = String(data: try! encoder.encode(expected), encoding: .utf8)! @@ -113,7 +101,7 @@ class QueryPredicateTests: XCTestCase { post.id != nil || post.id == "id" ) XCTAssertEqual( - not(post.id.eq("id")), + .not(post.id.eq("id")), !(post.id == "id") ) } @@ -130,7 +118,7 @@ class QueryPredicateTests: XCTestCase { post.id != nil || post.id == "id" || post.rating >= 0 ) XCTAssertEqual( - not(post.id.eq("id").and(post.id.ne(nil))), + .not(post.id.eq("id").and(post.id.ne(nil))), !(post.id == "id" && post.id != nil) ) } @@ -155,7 +143,7 @@ class QueryPredicateTests: XCTestCase { let funcationPredicate = post.draft.eq(true) .and(post.id.ne(nil)) .and(post.title.beginsWith("gelato").or(post.title.beginsWith("ice cream"))) - .and(not(post.updatedAt.eq(nil))) + .and(.not(post.updatedAt.eq(nil))) let operatorPredicate = post.draft == true && post.id != nil diff --git a/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Core/SQLStatementTests.swift b/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Core/SQLStatementTests.swift index 19806595c8..11d1adb684 100644 --- a/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Core/SQLStatementTests.swift +++ b/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Core/SQLStatementTests.swift @@ -595,6 +595,7 @@ class SQLStatementTests: XCTestCase { and "root"."content" = ? ) """ + XCTAssertEqual(statement.stringValue, expectedStatement) let variables = statement.variables @@ -993,7 +994,7 @@ class SQLStatementTests: XCTestCase { let statement = ConditionStatement(modelSchema: Post.schema, predicate: predicate) XCTAssertEqual(""" - and "id" is not null + and "id" is not null """, statement.stringValue) XCTAssert(statement.variables.isEmpty) } @@ -1011,7 +1012,7 @@ class SQLStatementTests: XCTestCase { let statement = ConditionStatement(modelSchema: Post.schema, predicate: predicate, namespace: "root") XCTAssertEqual(""" - and "root"."id" is not null + and "root"."id" is not null """, statement.stringValue) XCTAssert(statement.variables.isEmpty) } @@ -1027,7 +1028,7 @@ class SQLStatementTests: XCTestCase { matches sql: String, bindings: [Binding?]? = nil) { let statement = ConditionStatement(modelSchema: Post.schema, predicate: predicate, namespace: "root") - XCTAssertEqual(statement.stringValue, " and \(sql)") + XCTAssertEqual(statement.stringValue, "and \(sql)") if let bindings = bindings { XCTAssertEqual(bindings.count, statement.variables.count) bindings.enumerated().forEach { @@ -1078,7 +1079,7 @@ class SQLStatementTests: XCTestCase { let statement = ConditionStatement(modelSchema: Post.schema, predicate: predicate) XCTAssertEqual(""" - and ( + and ( "id" is not null and "draft" = ? and "rating" > ? @@ -1278,10 +1279,8 @@ class SQLStatementTests: XCTestCase { and "root"."content" = ? ) ) - and ( - "root"."rating" > ? - and "root"."createdAt" = ? - ) + and "root"."rating" > ? + and "root"."createdAt" = ? ) """ XCTAssertEqual(statement.stringValue, expectedStatement) @@ -1312,7 +1311,7 @@ class SQLStatementTests: XCTestCase { let statement = ConditionStatement(modelSchema: Post.schema, predicate: predicate, namespace: "root") let expectedStatement = """ - and ( + and ( "root"."id" is not null and "root"."draft" = ? and "root"."rating" > ? @@ -1343,7 +1342,7 @@ class SQLStatementTests: XCTestCase { let variables = statement.variables let expectStatement = """ - and "root"."commentPostId" = ? + and "root"."commentPostId" = ? """ let expectedVariable = "postID" diff --git a/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Core/SQLiteStorageEngineAdapterJsonTests.swift b/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Core/SQLiteStorageEngineAdapterJsonTests.swift index d71082ff45..27b1fd6972 100644 --- a/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Core/SQLiteStorageEngineAdapterJsonTests.swift +++ b/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Core/SQLiteStorageEngineAdapterJsonTests.swift @@ -140,7 +140,7 @@ class SQLiteStorageEngineAdapterJsonTests: XCTestCase { storageAdapter.save(model, modelSchema: ModelRegistry.modelSchema(from: "Post")!) { saveResult in switch saveResult { case .success: - let predicate = QueryPredicateOperation(field: "title", operator: .equals(title)) + let predicate = QueryPredicateOperation.operation("title", .equals(title)) self.storageAdapter.query(Post.self, predicate: predicate) { queryResult in switch queryResult { case .success(let posts): @@ -316,7 +316,7 @@ class SQLiteStorageEngineAdapterJsonTests: XCTestCase { "content": .string(content), "createdAt": .string(createdAt)] as [String: JSONValue] let updatedModel = DynamicModel(id: model.id, values: updatedPost) - let condition = QueryPredicateOperation(field: "content", operator: .equals(content)) + let condition = QueryPredicateOperation.operation("content", .equals(content)) self.storageAdapter.save(updatedModel, modelSchema: schema, condition: condition) { updateResult in switch updateResult { case .success: @@ -351,7 +351,7 @@ class SQLiteStorageEngineAdapterJsonTests: XCTestCase { "createdAt": .string(createdAt)] as [String: JSONValue] let model = DynamicModel(values: post) let schema = ModelRegistry.modelSchema(from: "Post")! - let condition = QueryPredicateOperation(field: "content", operator: .equals(content)) + let condition = QueryPredicateOperation.operation("content", .equals(content)) storageAdapter.save(model, modelSchema: schema, condition: condition) { insertResult in switch insertResult { case .success: @@ -396,8 +396,10 @@ class SQLiteStorageEngineAdapterJsonTests: XCTestCase { "content": .string(content), "createdAt": .string(createdAt)] as [String: JSONValue] let updatedModel = DynamicModel(id: model.id, values: updatedPost) - let condition = QueryPredicateOperation(field: "content", - operator: .equals("content 2 does not match previous content")) + let condition = QueryPredicateOperation.operation( + "content", + .equals("content 2 does not match previous content") + ) self.storageAdapter.save(updatedModel, modelSchema: schema, condition: condition) { updateResult in @@ -442,8 +444,10 @@ class SQLiteStorageEngineAdapterJsonTests: XCTestCase { switch insertResult { case .success: saveExpectation.fulfill() - let predicate = QueryPredicateOperation(field: "createdAt", - operator: .greaterThan(dateTestStart.iso8601String)) + let predicate = QueryPredicateOperation.operation( + "createdAt", + .greaterThan(dateTestStart.iso8601String) + ) self.storageAdapter.delete(DynamicModel.self, modelSchema: Post.schema, filter: predicate) { result in diff --git a/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/InitialSync/InitialSyncOperationTests.swift b/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/InitialSync/InitialSyncOperationTests.swift index d1a8a464aa..7034da25da 100644 --- a/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/InitialSync/InitialSyncOperationTests.swift +++ b/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/InitialSync/InitialSyncOperationTests.swift @@ -182,7 +182,7 @@ class InitialSyncOperationTests: XCTestCase { func testDeltaSyncWhenLastSyncPredicateSameAsCurrentSyncPredicate() { let startDateSeconds = (Int64(Date().timeIntervalSince1970) - 100) let lastSyncTime: Int64 = startDateSeconds * 1_000 - let lastSyncPredicate: String? = "{\"field\":\"id\",\"operator\":{\"type\":\"equals\",\"value\":\"123\"}}" + let lastSyncPredicate: String? = "{\"operation\":{\"_0\":\"id\",\"_1\":{\"type\":\"equals\",\"value\":\"123\"}}}" let currentSyncPredicate: DataStoreConfiguration #if os(watchOS) currentSyncPredicate = DataStoreConfiguration.custom( diff --git a/AmplifyTests/CategoryTests/DataStore/ModelIdentifierTests.swift b/AmplifyTests/CategoryTests/DataStore/ModelIdentifierTests.swift index 8d8f607f21..4b9751eb1e 100644 --- a/AmplifyTests/CategoryTests/DataStore/ModelIdentifierTests.swift +++ b/AmplifyTests/CategoryTests/DataStore/ModelIdentifierTests.swift @@ -112,14 +112,12 @@ class ModelIdentifierTests: XCTestCase { let identifier = model.identifier(schema: ModelCompositePk.schema) - let predicate = (identifier.predicate as? QueryPredicateGroup)! + let predicate = (identifier.predicate as? QueryPredicateOperation)! let keys = ModelCompositePk.keys let expectedPredicate = keys.dob == model.dob && keys.id == model.id - XCTAssertEqual(predicate.type, expectedPredicate.type) - XCTAssertEqual(predicate.predicates.count, expectedPredicate.predicates.count) XCTAssertEqual(predicate, expectedPredicate) }