amplify-swift/Amplify/Categories/DataStore/Query/QueryPredicate.swift

161 lines
4.8 KiB
Swift

//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//
import Foundation
/// Protocol that indicates concrete types conforming to it can be used a predicate member.
public protocol QueryPredicate: Evaluable {}
public enum QueryPredicateGroupType: String {
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 {
case all
public func evaluate(target: Model) -> Bool {
return true
}
}
public class QueryPredicateGroup: QueryPredicate {
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)
}
}
}
public class QueryPredicateOperation: QueryPredicate {
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 {
guard let fieldValue = target[field] else {
return false
}
guard let value = fieldValue else {
return false
}
if let booleanValue = value as? Bool {
return self.operator.evaluate(target: booleanValue)
}
if let doubleValue = value as? Double {
return self.operator.evaluate(target: doubleValue)
}
if let intValue = value as? Int {
return self.operator.evaluate(target: intValue)
}
if let timeValue = value as? Temporal.Time {
return self.operator.evaluate(target: timeValue)
}
if let enumValue = value as? EnumPersistable {
return self.operator.evaluate(target: enumValue.rawValue)
}
return self.operator.evaluate(target: value)
}
}