Skip to content

Commit

Permalink
change QueryPredicateOperation to a monoidal data structure
Browse files Browse the repository at this point in the history
  • Loading branch information
5d committed Mar 13, 2024
1 parent d07f4cd commit 21b5f20
Show file tree
Hide file tree
Showing 24 changed files with 367 additions and 393 deletions.
41 changes: 28 additions & 13 deletions Amplify/Categories/DataStore/Query/QueryField.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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 {
Expand All @@ -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 {
Expand All @@ -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 {
Expand All @@ -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 {
Expand All @@ -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 {
Expand All @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
149 changes: 0 additions & 149 deletions Amplify/Categories/DataStore/Query/QueryPredicate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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: QueryPredicate>(_ 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 })
}
}
Loading

0 comments on commit 21b5f20

Please sign in to comment.