Kinvey/Kinvey/Query.swift
//
// Query.swift
// Kinvey
//
// Created by Victor Barros on 2015-12-08.
// Copyright © 2015 Kinvey. All rights reserved.
//
import Foundation
#if !os(watchOS)
import MapKit
#endif
#if canImport(os)
import os
#endif
#if canImport(MongoDBPredicateAdaptor)
import MongoDBPredicateAdaptor
#endif
extension NSPredicate {
internal var asString: String {
return String(describing: self)
}
}
/// Class that represents a query including filters and sorts.
public final class Query: NSObject, BuilderType {
/// Fields to be included in the results of the query.
public var fields: Set<String>?
internal var fieldsAsString: String? {
return fields?.sorted().joined(separator: ",")
}
/// `NSPredicate` used to filter records.
public var predicate: NSPredicate? {
get {
guard let predicateKind = predicateKind else {
return nil
}
switch predicateKind {
case .predicate(let predicate):
return predicate
case .booleanExpression(let booleanExpression):
return booleanExpression.expression.predicate
}
}
set {
predicateKind = newValue.predicateKind
}
}
private var predicateKind: PredicateKind?
/// Array of `NSSortDescriptor`s used to sort records.
public var sortDescriptors: [NSSortDescriptor]?
/// Skip a certain amount of records in the results of the query.
public var skip: Int?
/// Impose a limit of records in the results of the query.
public var limit: Int?
internal var emptyPredicateMustReturnNil = true
public override var description: String {
return "Fields: \(String(describing: fields))\nPredicate: \(String(describing: predicate))\nSort Descriptors: \(String(describing: sortDescriptors))\nSkip: \(String(describing: skip))\nLimit: \(String(describing: limit))"
}
internal func translate(expression: NSExpression, otherSideExpression: NSExpression) -> NSExpression {
switch expression.expressionType {
case .keyPath:
var keyPath = expression.keyPath
var type: AnyClass? = self.persistableType as? AnyClass
if keyPath.contains(".") {
var keyPaths = [String]()
for item in keyPath.components(separatedBy: ".") {
if let persistableType = type as? Persistable.Type,
let (keyPath, _) = persistableType.propertyMapping(item)
{
keyPaths.append(keyPath)
} else if let type = type, let objectType = type as? NSObject.Type, type is BaseMappable.Type {
let className = StringFromClass(cls: type)
if kinveyProperyMapping[className] == nil {
currentMappingClass = className
mappingOperationQueue.addOperation {
if kinveyProperyMapping[className] == nil {
kinveyProperyMapping[className] = PropertyMap()
}
let obj = objectType.init()
let _ = (obj as! BaseMappable).toJSON()
}
mappingOperationQueue.waitUntilAllOperationsAreFinished()
}
if let kinveyMappingClassType = kinveyProperyMapping[className],
let (keyPath, _) = kinveyMappingClassType[item]
{
keyPaths.append(keyPath)
} else {
keyPaths.append(item)
}
} else {
keyPaths.append(item)
}
if let _type = type {
type = ObjCRuntime.typeForPropertyName(_type, propertyName: item)
}
}
keyPath = keyPaths.joined(separator: ".")
} else if let persistableType = type as? Persistable.Type,
let (translatedKeyPath, _) = persistableType.propertyMapping(keyPath)
{
keyPath = translatedKeyPath
} else if let persistableType = type as? Persistable.Type,
let translatedKeyPath = try? persistableType.translate(property: keyPath)
{
keyPath = translatedKeyPath
}
return NSExpression(forKeyPath: keyPath)
case .constantValue:
#if !os(watchOS)
if otherSideExpression.expressionType == .keyPath,
let (_, optionalTransform) = persistableType?.propertyMapping(otherSideExpression.keyPath),
let transform = optionalTransform,
let constantValue = expression.constantValue,
!(constantValue is MKShape)
{
return NSExpression(forConstantValue: transform.transformToJSON(expression.constantValue))
}
#else
if otherSideExpression.expressionType == .keyPath,
let (_, optionalTransform) = persistableType?.propertyMapping(otherSideExpression.keyPath),
let transform = optionalTransform
{
return NSExpression(forConstantValue: transform.transformToJSON(expression.constantValue))
}
#endif
if let date = expression.constantValue as? Date {
return NSExpression(forConstantValue: date.toISO8601())
}
return expression
default:
return expression
}
}
fileprivate func translate(predicate: NSPredicate) -> NSPredicate {
signpost(.begin, log: osLog, name: "Translate Query")
defer {
signpost(.end, log: osLog, name: "Translate Query")
}
if let predicate = predicate as? NSComparisonPredicate {
return NSComparisonPredicate(
leftExpression: translate(expression: predicate.leftExpression, otherSideExpression: predicate.rightExpression),
rightExpression: translate(expression: predicate.rightExpression, otherSideExpression: predicate.leftExpression),
modifier: predicate.comparisonPredicateModifier,
type: predicate.predicateOperatorType,
options: predicate.options
)
} else if let predicate = predicate as? NSCompoundPredicate {
var subpredicates = [NSPredicate]()
for predicate in predicate.subpredicates as! [NSPredicate] {
subpredicates.append(translate(predicate: predicate))
}
return NSCompoundPredicate(type: predicate.compoundPredicateType, subpredicates: subpredicates)
}
return predicate
}
var isEmpty: Bool {
return predicate == nil &&
(sortDescriptors == nil || sortDescriptors!.isEmpty) &&
skip == nil &&
limit == nil &&
(fields == nil || fields!.isEmpty)
}
fileprivate var queryStringEncoded: String? {
guard let predicateKind = predicateKind else {
return emptyPredicateMustReturnNil ? nil : "{}"
}
let queryObj: [String : Any]
switch predicateKind {
case .predicate(let predicate):
let translatedPredicate = translate(predicate: predicate)
queryObj = translatedPredicate.mongoDBQuery!
case .booleanExpression(let booleanExpression):
queryObj = booleanExpression.expression.mongoDBQuery!
}
let data = try! JSONSerialize.data(queryObj)
let queryStr = String(data: data, encoding: String.Encoding.utf8)!
return queryStr.trimmingCharacters(in: CharacterSet.whitespaces)
}
internal var urlQueryItems: [URLQueryItem]? {
var queryParams = [URLQueryItem]()
if let queryParam = queryStringEncoded, !queryParam.isEmpty {
let queryItem = URLQueryItem(name: "query", value: queryParam)
queryParams.append(queryItem)
}
if let sortDescriptors = sortDescriptors {
var sorts = [String : Int]()
for sortDescriptor in sortDescriptors {
sorts[sortDescriptor.key!] = sortDescriptor.ascending ? 1 : -1
}
let data = try! JSONSerialize.data(sorts)
let queryItem = URLQueryItem(name: "sort", value: String(data: data, encoding: String.Encoding.utf8)!)
queryParams.append(queryItem)
}
if let fields = fields {
let queryItem = URLQueryItem(name: "fields", value: fields.joined(separator: ","))
queryParams.append(queryItem)
}
if let skip = skip {
let queryItem = URLQueryItem(name: "skip", value: String(skip))
queryParams.append(queryItem)
}
if let limit = limit {
let queryItem = URLQueryItem(name: "limit", value: String(limit))
queryParams.append(queryItem)
}
return queryParams.count > 0 ? queryParams : nil
}
var persistableType: Persistable.Type?
init(
predicate: NSPredicate? = nil,
sortDescriptors: [NSSortDescriptor]? = nil,
fields: Set<String>? = nil,
persistableType: Persistable.Type? = nil
) {
self.predicateKind = predicate.predicateKind
self.sortDescriptors = sortDescriptors
self.fields = fields
self.persistableType = persistableType
}
public var booleanExpression: BooleanExpression? {
get {
guard let predicateKind = predicateKind else {
return nil
}
switch predicateKind {
case .booleanExpression(let booleanExpression):
return booleanExpression
case .predicate:
return nil
}
}
set {
predicateKind = newValue.predicateKind
}
}
public init(_ booleanExpression: BooleanExpression) {
self.predicateKind = .booleanExpression(booleanExpression)
}
convenience init(query: Query, persistableType: Persistable.Type) {
self.init(query) {
$0.persistableType = persistableType
}
}
/// Default Constructor.
public override convenience required init() {
self.init(
predicate: nil,
sortDescriptors: nil,
fields: nil,
persistableType: nil
)
}
/// Constructor using a `NSPredicate` to filter records, an array of `NSSortDescriptor`s to sort records and the fields that should be returned.
public convenience init(
predicate: NSPredicate? = nil,
sortDescriptors: [NSSortDescriptor]? = nil,
fields: Set<String>? = nil
) {
self.init(
predicate: predicate,
sortDescriptors: sortDescriptors,
fields: fields,
persistableType: nil
)
}
/// Constructor using a similar way to construct a `NSPredicate`.
public convenience init(format: String, _ args: Any...) {
self.init(format: format, argumentArray: args)
}
/// Constructor using a similar way to construct a `NSPredicate`.
public convenience init(format: String, args: CVarArg) {
self.init(predicate: NSPredicate(format: format, args))
}
/// Constructor using a similar way to construct a `NSPredicate`.
public convenience init(format: String, argumentArray: [Any]?) {
self.init(predicate: NSPredicate(format: format, argumentArray: argumentArray))
}
/// Constructor using a similar way to construct a `NSPredicate`.
public convenience init(format: String, arguments: CVaListPointer) {
self.init(predicate: NSPredicate(format: format, arguments: arguments))
}
/// Copy Constructor.
public convenience init(_ query: Query) {
self.init() {
$0.fields = query.fields
$0.predicate = query.predicate
$0.sortDescriptors = query.sortDescriptors
$0.skip = query.skip
$0.limit = query.limit
$0.persistableType = query.persistableType
}
}
/// Copy Constructor.
public convenience init(_ query: Query, _ block: ((Query) -> Void)) {
self.init(query)
block(self)
}
@available(*, deprecated, message: "Deprecated in version 3.18.0. Please use Swift.Codable instead")
public init?(map: Map) {
return nil
}
let sortLock = NSLock()
fileprivate func addSort(_ property: String, ascending: Bool) {
sortLock.lock()
if sortDescriptors == nil {
sortDescriptors = []
}
sortLock.unlock()
sortDescriptors!.append(NSSortDescriptor(key: property, ascending: ascending))
}
/// Adds ascending properties to be sorted.
@discardableResult
public func ascending(_ properties: String...) -> Query {
for property in properties {
addSort(property, ascending: true)
}
return self
}
/// Adds descending properties to be sorted.
@discardableResult
public func descending(_ properties: String...) -> Query {
for property in properties {
addSort(property, ascending: false)
}
return self
}
@discardableResult
public func sort<Root, Value>(_ keyPath: KeyPath<Root, Value>, ascending: Bool) -> Query {
sortLock.lock()
if sortDescriptors == nil {
sortDescriptors = []
}
sortLock.unlock()
sortDescriptors!.append(NSSortDescriptor(key: keyPath.name, ascending: ascending))
return self
}
/// Adds ascending properties to be sorted.
@discardableResult
public func ascending<Root, Value>(_ keyPaths: KeyPath<Root, Value>...) -> Query {
for keyPath in keyPaths {
sort(keyPath, ascending: true)
}
return self
}
/// Adds ascending properties to be sorted.
@discardableResult
public func descending<Root, Value>(_ keyPaths: KeyPath<Root, Value>...) -> Query {
for keyPath in keyPaths {
sort(keyPath, ascending: false)
}
return self
}
}
extension KeyPath {
internal var name: String {
return NSExpression(forKeyPath: self).keyPath
}
}
@available(*, deprecated, message: "Deprecated in version 3.18.0. Please use Swift.Codable instead")
extension Query: Mappable {
public func mapping(map: Map) {
if map.mappingType == .toJSON, let predicate = predicate {
predicate.mapping(map: map)
}
}
}
public indirect enum Expression {
public typealias ExpressionProducer = () -> Expression
case column(String)
case and(lhs: Expression, rhs: Expression)
case or(lhs: Expression, rhs: Expression)
case equality(lhs: Expression, rhs: Expression)
case inequality(lhs: Expression, rhs: Expression)
case not(rhs: Expression)
case lessThan(lhs: Expression, rhs: Expression)
case lessThanEqual(lhs: Expression, rhs: Expression)
case greaterThan(lhs: Expression, rhs: Expression)
case greaterThanEqual(lhs: Expression, rhs: Expression)
case `in`(lhs: Expression, rhs: [Expression])
case like(lhs: Expression, wild1: Bool, String, wild2: Bool)
case geoIn(lhs: Expression, rhs: Expression)
case lazy(ExpressionProducer)
case keyPath(AnyKeyPath, NSExpression)
case integer(Int)
case uinteger(UInt)
case integer64(Int64)
case uinteger64(UInt64)
case integer32(Int32)
case uinteger32(UInt32)
case integer16(Int16)
case uinteger16(UInt16)
case integer8(Int8)
case uinteger8(UInt8)
case double(Double)
case float(Float)
case string(String)
case blob([UInt8])
case sblob([Int8])
case bool(Bool)
case uuid(UUID)
case date(Date)
case url(URL)
case null
#if canImport(MapKit) && !os(watchOS)
case circle(MKCircle)
case polygon(MKPolygon)
#endif
}
// MARK: - Mongo DB Query
extension Expression {
var mongoDBQuery: [String : Any]? {
return transform(expression: self)
}
var keyPath: String? {
switch self {
case .keyPath(_, let expression):
return expression.keyPath
default:
return nil
}
}
var value: Any? {
switch self {
case .integer(let value as Any),
.uinteger(let value as Any),
.integer64(let value as Any),
.uinteger64(let value as Any),
.integer32(let value as Any),
.uinteger32(let value as Any),
.integer16(let value as Any),
.uinteger16(let value as Any),
.integer8(let value as Any),
.uinteger8(let value as Any),
.double(let value as Any),
.float(let value as Any),
.string(let value as Any),
.blob(let value as Any),
.sblob(let value as Any),
.bool(let value as Any),
.uuid(let value as Any),
.date(let value as Any),
.url(let value as Any):
return value
#if canImport(MapKit) && !os(watchOS)
case .circle(let value as Any),
.polygon(let value as Any):
return value
#endif
case .null:
return NSNull()
default:
return nil
}
}
var lhsMongoDBOperatorRhs: (lhs: Expression, `operator`: MongoDBOperator, rhs: Expression)? {
switch self {
case .equality(let lhs, let rhs):
return (lhs: lhs, operator: .equalTo, rhs: rhs)
case .inequality(let lhs, let rhs):
return (lhs: lhs, operator: .notEqualTo, rhs: rhs)
case .greaterThan(let lhs, let rhs):
return (lhs: lhs, operator: .greaterThan, rhs: rhs)
case .greaterThanEqual(let lhs, let rhs):
return (lhs: lhs, operator: .greaterThanOrEqualTo, rhs: rhs)
case .lessThan(let lhs, let rhs):
return (lhs: lhs, operator: .lessThan, rhs: rhs)
case .lessThanEqual(let lhs, let rhs):
return (lhs: lhs, operator: .lessThanOrEqualTo, rhs: rhs)
default:
return nil
}
}
private func transform(lhs: Expression, operator: MongoDBOperator, rhs: Expression, optimize: Bool) -> [String : Any]? {
let keyPath = lhs.keyPath!
let value = rhs.value!
switch (`operator`, optimize) {
case (MongoDBOperator.equalTo, true):
return [keyPath : value]
default:
return [keyPath : [`operator`.rawValue : value]]
}
}
private func transform(expression: Expression, optimize: Bool = true) -> [String : Any]? {
switch expression {
case .equality(let lhs, let rhs):
return transform(lhs: lhs, operator: .equalTo, rhs: rhs, optimize: optimize)
case .inequality(let lhs, let rhs):
return transform(lhs: lhs, operator: .notEqualTo, rhs: rhs, optimize: optimize)
case .greaterThan(let lhs, let rhs):
return transform(lhs: lhs, operator: .greaterThan, rhs: rhs, optimize: optimize)
case .greaterThanEqual(let lhs, let rhs):
return transform(lhs: lhs, operator: .greaterThanOrEqualTo, rhs: rhs, optimize: optimize)
case .lessThan(let lhs, let rhs):
return transform(lhs: lhs, operator: .lessThan, rhs: rhs, optimize: optimize)
case .lessThanEqual(let lhs, let rhs):
return transform(lhs: lhs, operator: .lessThanOrEqualTo, rhs: rhs, optimize: optimize)
case .and(let lhs, let rhs):
let expressions = [
transform(expression: lhs, optimize: optimize),
transform(expression: rhs, optimize: optimize)
].compactMap { $0 }
if optimize, expressions.count <= 1 {
return expressions.first
}
if optimize,
let sequence = Optional(expressions.filter({ $0.count == 1 }).compactMap({ $0.first })),
expressions.count == Set(sequence.map{ $0.key }).count
{
return [String : Any](uniqueKeysWithValues: sequence)
}
return [MongoDBOperator.and.rawValue : expressions]
case .or(let lhs, let rhs):
return [
MongoDBOperator.or.rawValue : [
transform(expression: lhs, optimize: optimize),
transform(expression: rhs, optimize: optimize)
]
]
case .not(let rhs):
if let (lhs, `operator`, rhs) = rhs.lhsMongoDBOperatorRhs,
let keyPath = lhs.keyPath,
let value = rhs.value
{
return [
keyPath : [
MongoDBOperator.not.rawValue : [
`operator`.rawValue : value
]
]
]
}
return [MongoDBOperator.not.rawValue : [transform(expression:rhs, optimize: optimize)]]
case .like(let lhs, let wild1, let value, let wild2):
let keyPath: String
switch lhs {
case .keyPath(_, let expression):
keyPath = expression.keyPath
default:
return nil
}
let regexValue: String
switch (wild1, wild2) {
case (true, true):
regexValue = ".*\(value).*"
case (false, true):
regexValue = ".*\(value)"
case (true, false):
regexValue = "^\(value)"
default:
regexValue = value
}
return [
keyPath : [
MongoDBOperator.matches.rawValue : regexValue
]
]
case .in(let lhs, let rhs):
let keyPath: String
switch lhs {
case .keyPath(_, let expression):
keyPath = expression.keyPath
default:
return nil
}
let values = rhs.map { $0.value }
return [
keyPath : [
MongoDBOperator.in.rawValue : values
]
]
case .geoIn(let lhs, let rhs):
let keyPath: String
switch lhs {
case .keyPath(_, let expression):
keyPath = expression.keyPath
default:
return nil
}
return [
keyPath : [
MongoDBOperator.geoIn.rawValue : transform(expression: rhs, optimize: optimize)
]
]
#if canImport(MapKit) && !os(watchOS)
case .circle(let circle):
return [
"$centerSphere" : [
[
circle.coordinate.longitude,
circle.coordinate.latitude
],
circle.radius / 6371000.0
]
]
case .polygon(let polygon):
let pointCount = polygon.pointCount
var coordinates = [CLLocationCoordinate2D](repeating: CLLocationCoordinate2D(), count: pointCount)
polygon.getCoordinates(&coordinates, range: NSRange(location: 0, length: pointCount))
let coordinatesArray = coordinates.map { [$0.longitude, $0.latitude] }
return [
"$polygon" : coordinatesArray
]
#endif
default:
return [:]
}
}
}
// MARK: - Expression -> Predicate
extension Expression {
var predicate: NSPredicate? {
return transform(expression: self)
}
var expression: NSExpression? {
switch self {
case .keyPath(_, let expression):
return expression
case .integer(let value as Any),
.uinteger(let value as Any),
.integer64(let value as Any),
.uinteger64(let value as Any),
.integer32(let value as Any),
.uinteger32(let value as Any),
.integer16(let value as Any),
.uinteger16(let value as Any),
.integer8(let value as Any),
.uinteger8(let value as Any),
.double(let value as Any),
.float(let value as Any),
.string(let value as Any),
.blob(let value as Any),
.sblob(let value as Any),
.bool(let value as Any),
.uuid(let value as Any),
.date(let value as Any),
.url(let value as Any):
return NSExpression(forConstantValue: value)
#if canImport(MapKit) && !os(watchOS)
case .circle(let value as Any),
.polygon(let value as Any):
return NSExpression(forConstantValue: value)
#endif
case .null:
return NSExpression(forConstantValue: nil)
default:
return nil
}
}
private func transform(expression: Expression) -> NSPredicate? {
switch expression {
case .equality(let lhs, let rhs):
return transform(lhs: lhs.expression!, rhs: rhs.expression!, type: .equalTo)
case .inequality(let lhs, let rhs):
return transform(lhs: lhs.expression!, rhs: rhs.expression!, type: .notEqualTo)
case .greaterThan(let lhs, let rhs):
return transform(lhs: lhs.expression!, rhs: rhs.expression!, type: .greaterThan)
case .greaterThanEqual(let lhs, let rhs):
return transform(lhs: lhs.expression!, rhs: rhs.expression!, type: .greaterThanOrEqualTo)
case .lessThan(let lhs, let rhs):
return transform(lhs: lhs.expression!, rhs: rhs.expression!, type: .lessThan)
case .lessThanEqual(let lhs, let rhs):
return transform(lhs: lhs.expression!, rhs: rhs.expression!, type: .lessThanOrEqualTo)
case .and(let lhs, let rhs):
return NSCompoundPredicate(andPredicateWithSubpredicates: [lhs.predicate!, rhs.predicate!])
case .or(let lhs, let rhs):
return NSCompoundPredicate(orPredicateWithSubpredicates: [lhs.predicate!, rhs.predicate!])
case .not(let rhs):
return NSCompoundPredicate(notPredicateWithSubpredicate: rhs.predicate!)
case .like(let lhs, let wild1, let rhs, let wild2):
let type: NSComparisonPredicate.Operator
switch (wild1, wild2) {
case (true, true):
type = .contains
case (false, true):
type = .endsWith
case (true, false):
type = .beginsWith
default:
return nil
}
return transform(lhs: lhs.expression!, rhs: NSExpression(forConstantValue: rhs), type: type)
case .`in`(let lhs, let rhs):
return transform(lhs: lhs.expression!, rhs: NSExpression(forConstantValue: rhs.map { $0.expression!.constantValue }), type: .in)
default:
return nil
}
}
private func transform(
lhs: NSExpression,
rhs: NSExpression,
modifier: NSComparisonPredicate.Modifier = .direct,
type: NSComparisonPredicate.Operator,
options: NSComparisonPredicate.Options = []
) -> NSComparisonPredicate {
return NSComparisonPredicate(
leftExpression: lhs,
rightExpression: rhs,
modifier: modifier,
type: type,
options: options
)
}
}
// MARK: - BooleanExpression
public protocol BooleanExpression {
var expression: Expression { get }
}
struct BooleanExpressionWrapper: BooleanExpression {
let expression: Expression
init(_ e: Expression) {
expression = e
}
}
enum PredicateKind {
case predicate(NSPredicate)
case booleanExpression(BooleanExpression)
}
// MARK: - == Operator
public func == <Root, Value: ValueExpressionType>(lhs: KeyPath<Root, Value>, rhs: Value) -> BooleanExpression {
return BooleanExpressionWrapper(.equality(lhs: lhs.expression, rhs: rhs.expression))
}
public func == <Root, Value: ValueExpressionType>(lhs: KeyPath<Root, Value?>, rhs: Value) -> BooleanExpression {
return BooleanExpressionWrapper(.equality(lhs: lhs.expression, rhs: rhs.expression))
}
public func == <Root, Value: GeoValueExpressionType>(lhs: KeyPath<Root, GeoPoint>, rhs: Value) -> BooleanExpression {
return BooleanExpressionWrapper(.geoIn(lhs: lhs.expression, rhs: rhs.expression))
}
public func == <Root, Value: GeoValueExpressionType>(lhs: KeyPath<Root, GeoPoint?>, rhs: Value) -> BooleanExpression {
return BooleanExpressionWrapper(.geoIn(lhs: lhs.expression, rhs: rhs.expression))
}
// MARK: - != Operator
public func != <Root, Value: ValueExpressionType>(lhs: KeyPath<Root, Value>, rhs: Value) -> BooleanExpression {
return BooleanExpressionWrapper(.inequality(lhs: lhs.expression, rhs: rhs.expression))
}
public func != <Root, Value: ValueExpressionType>(lhs: KeyPath<Root, Value?>, rhs: Value) -> BooleanExpression {
return BooleanExpressionWrapper(.inequality(lhs: lhs.expression, rhs: rhs.expression))
}
// MARK: - <> Operator
infix operator <>: ComparisonPrecedence
public func <> <Root, Value: ValueExpressionType>(lhs: KeyPath<Root, Value>, rhs: Value) -> BooleanExpression {
return BooleanExpressionWrapper(.inequality(lhs: lhs.expression, rhs: rhs.expression))
}
public func <> <Root, Value: ValueExpressionType>(lhs: KeyPath<Root, Value?>, rhs: Value) -> BooleanExpression {
return BooleanExpressionWrapper(.inequality(lhs: lhs.expression, rhs: rhs.expression))
}
// MARK: - < Operator
public func < <Root, Value: ValueExpressionType>(lhs: KeyPath<Root, Value>, rhs: Value) -> BooleanExpression {
return BooleanExpressionWrapper(.lessThan(lhs: lhs.expression, rhs: rhs.expression))
}
public func < <Root, Value: ValueExpressionType>(lhs: KeyPath<Root, Value?>, rhs: Value) -> BooleanExpression {
return BooleanExpressionWrapper(.lessThan(lhs: lhs.expression, rhs: rhs.expression))
}
// MARK: - <= Operator
public func <= <Root, Value: ValueExpressionType>(lhs: KeyPath<Root, Value>, rhs: Value) -> BooleanExpression {
return BooleanExpressionWrapper(.lessThanEqual(lhs: lhs.expression, rhs: rhs.expression))
}
public func <= <Root, Value: ValueExpressionType>(lhs: KeyPath<Root, Value?>, rhs: Value) -> BooleanExpression {
return BooleanExpressionWrapper(.lessThanEqual(lhs: lhs.expression, rhs: rhs.expression))
}
// MARK: - > Operator
public func > <Root, Value: ValueExpressionType>(lhs: KeyPath<Root, Value>, rhs: Value) -> BooleanExpression {
return BooleanExpressionWrapper(.greaterThan(lhs: lhs.expression, rhs: rhs.expression))
}
public func > <Root, Value: ValueExpressionType>(lhs: KeyPath<Root, Value?>, rhs: Value) -> BooleanExpression {
return BooleanExpressionWrapper(.greaterThan(lhs: lhs.expression, rhs: rhs.expression))
}
// MARK: - >= Operator
public func >= <Root, Value: ValueExpressionType>(lhs: KeyPath<Root, Value>, rhs: Value) -> BooleanExpression {
return BooleanExpressionWrapper(.greaterThanEqual(lhs: lhs.expression, rhs: rhs.expression))
}
public func >= <Root, Value: ValueExpressionType>(lhs: KeyPath<Root, Value?>, rhs: Value) -> BooleanExpression {
return BooleanExpressionWrapper(.greaterThanEqual(lhs: lhs.expression, rhs: rhs.expression))
}
// MARK: - && Operator
public func && (lhs: BooleanExpression, rhs: BooleanExpression) -> BooleanExpression {
return BooleanExpressionWrapper(.and(lhs: lhs.expression, rhs: rhs.expression))
}
// MARK: - || Operator
public func || (lhs: BooleanExpression, rhs: BooleanExpression) -> BooleanExpression {
return BooleanExpressionWrapper(.or(lhs: lhs.expression, rhs: rhs.expression))
}
// MARK: - ! Operator
public prefix func ! (rhs: BooleanExpression) -> BooleanExpression {
return BooleanExpressionWrapper(.not(rhs: rhs.expression))
}
// MARK: - %=% Operator LIKE CONTAINS
infix operator %=%: ComparisonPrecedence // LIKE %v% . string or regexp or in array
public func %=% <Root>(lhs: KeyPath<Root, String>, rhs: String) -> BooleanExpression {
return BooleanExpressionWrapper(.like(lhs: lhs.expression, wild1: true, rhs, wild2: true))
}
public func %=% <Root>(lhs: KeyPath<Root, String?>, rhs: String) -> BooleanExpression {
return BooleanExpressionWrapper(.like(lhs: lhs.expression, wild1: true, rhs, wild2: true))
}
// MARK: - =% Operator LIKE BEGINS WITH
infix operator =%: ComparisonPrecedence // LIKE v% . string
public func =% <Root>(lhs: KeyPath<Root, String>, rhs: String) -> BooleanExpression {
return BooleanExpressionWrapper(.like(lhs: lhs.expression, wild1: true, rhs, wild2: false))
}
public func =% <Root>(lhs: KeyPath<Root, String?>, rhs: String) -> BooleanExpression {
return BooleanExpressionWrapper(.like(lhs: lhs.expression, wild1: true, rhs, wild2: false))
}
// MARK: - %= Operator LIKE ENDS WITH
public func %= <Root>(lhs: KeyPath<Root, String>, rhs: String) -> BooleanExpression {
return BooleanExpressionWrapper(.like(lhs: lhs.expression, wild1: false, rhs, wild2: true))
}
public func %= <Root>(lhs: KeyPath<Root, String?>, rhs: String) -> BooleanExpression {
return BooleanExpressionWrapper(.like(lhs: lhs.expression, wild1: false, rhs, wild2: true))
}
// MARK: - !~ Operator NOT LIKE
infix operator %!=%: ComparisonPrecedence // NOT LIKE %v% . string or regexp or array
public func %!=% <Root>(lhs: KeyPath<Root, String>, rhs: String) -> BooleanExpression {
return !(lhs %=% rhs)
}
public func %!=% <Root>(lhs: KeyPath<Root, String?>, rhs: String) -> BooleanExpression {
return !(lhs %=% rhs)
}
infix operator !=%: ComparisonPrecedence // NOT LIKE v% . string
public func !=% <Root>(lhs: KeyPath<Root, String>, rhs: String) -> BooleanExpression {
return !(lhs =% rhs)
}
public func !=% <Root>(lhs: KeyPath<Root, String?>, rhs: String) -> BooleanExpression {
return !(lhs =% rhs)
}
infix operator %!=: ComparisonPrecedence // NOT LIKE %v . string
public func %!= <Root>(lhs: KeyPath<Root, String>, rhs: String) -> BooleanExpression {
return !(lhs %= rhs)
}
public func %!= <Root>(lhs: KeyPath<Root, String?>, rhs: String) -> BooleanExpression {
return !(lhs %= rhs)
}
// MARK: - ~ Operator IN
infix operator ~: ComparisonPrecedence // IN, matches
public func ~ <Root, Value: ValueExpressionType>(lhs: KeyPath<Root, Value>, rhs: [Value]) -> BooleanExpression {
return BooleanExpressionWrapper(.in(lhs: lhs.expression, rhs: rhs.map { $0.expression }))
}
public func ~ <Root, Value: ValueExpressionType>(lhs: KeyPath<Root, Value?>, rhs: [Value]) -> BooleanExpression {
return BooleanExpressionWrapper(.in(lhs: lhs.expression, rhs: rhs.map { $0.expression }))
}
// MARK: - !~ Operator NOT IN
infix operator !~: ComparisonPrecedence // NOT IN, matches
public func !~ <Root, Value: ValueExpressionType>(lhs: KeyPath<Root, Value>, rhs: [Value]) -> BooleanExpression {
return !(lhs ~ rhs)
}
public func !~ <Root, Value: ValueExpressionType>(lhs: KeyPath<Root, Value?>, rhs: [Value]) -> BooleanExpression {
return !(lhs ~ rhs)
}
// MARK: - Expression Types
public protocol ExpressionType {
var expression: Expression { get }
}
extension KeyPath: ExpressionType {
public var expression: Expression {
return .keyPath(self, NSExpression(forKeyPath: self))
}
}
public protocol ValueExpressionType: ExpressionType {
}
public protocol GeoValueExpressionType: ExpressionType {
}
extension Int: ValueExpressionType {
public var expression: Expression {
return .integer(self)
}
}
extension UInt: ValueExpressionType {
public var expression: Expression {
return .uinteger(self)
}
}
extension Int64: ValueExpressionType {
public var expression: Expression {
return .integer64(self)
}
}
extension UInt64: ValueExpressionType {
public var expression: Expression {
return .uinteger64(self)
}
}
extension Int32: ValueExpressionType {
public var expression: Expression {
return .integer32(self)
}
}
extension UInt32: ValueExpressionType {
public var expression: Expression {
return .uinteger32(self)
}
}
extension Int16: ValueExpressionType {
public var expression: Expression {
return .integer16(self)
}
}
extension UInt16: ValueExpressionType {
public var expression: Expression {
return .uinteger16(self)
}
}
extension Int8: ValueExpressionType {
public var expression: Expression {
return .integer8(self)
}
}
extension UInt8: ValueExpressionType {
public var expression: Expression {
return .uinteger8(self)
}
}
extension Double: ValueExpressionType {
public var expression: Expression {
return .double(self)
}
}
extension Float: ValueExpressionType {
public var expression: Expression {
return .float(self)
}
}
extension String: ValueExpressionType {
public var expression: Expression {
return .string(self)
}
}
extension Array: ValueExpressionType, ExpressionType where Element == UInt8 {
public var expression: Expression {
return .blob(self)
}
}
extension Array /*: ValueExpressionType, ExpressionType */ where Element == Int8 {
public var expression: Expression {
return .sblob(self)
}
}
extension Bool: ValueExpressionType {
public var expression: Expression {
return .bool(self)
}
}
extension UUID: ValueExpressionType {
public var expression: Expression {
return .uuid(self)
}
}
extension Date: ValueExpressionType {
public var expression: Expression {
return .date(self)
}
}
extension URL: ValueExpressionType {
public var expression: Expression {
return .url(self)
}
}
#if canImport(MapKit) && !os(watchOS)
extension MKCircle: GeoValueExpressionType {
public var expression: Expression {
return .circle(self)
}
}
extension MKPolygon: GeoValueExpressionType {
public var expression: Expression {
return .polygon(self)
}
}
#endif
// MARK: - Optional Expression
extension Optional where Wrapped: ExpressionType {
public var expression: Expression {
if let value = self {
return value.expression
}
return .null
}
}
// MARK: - Optional PredicateKind
extension Optional where Wrapped == BooleanExpression {
var predicateKind: PredicateKind? {
switch self {
case .some(let wrapped):
return .booleanExpression(wrapped)
default:
return nil
}
}
}
extension Optional where Wrapped == NSPredicate {
var predicateKind: PredicateKind? {
switch self {
case .some(let wrapped):
return .predicate(wrapped)
default:
return nil
}
}
}