From 580a9534810315b054dfca077ff7c6cdc5f3bc3e Mon Sep 17 00:00:00 2001 From: tanner0101 Date: Tue, 20 Mar 2018 18:38:37 -0400 Subject: [PATCH] database query filter type --- Sources/Fluent/Query/Filter/QueryFilter.swift | 16 ++- Sources/Fluent/Query/QuerySupporting.swift | 3 + Sources/FluentSQL/DatabaseQuery.swift | 2 +- Sources/FluentSQL/QueryComparison.swift | 115 +++++++++++++----- Sources/FluentSQL/QueryFilter.swift | 4 +- 5 files changed, 103 insertions(+), 37 deletions(-) diff --git a/Sources/Fluent/Query/Filter/QueryFilter.swift b/Sources/Fluent/Query/Filter/QueryFilter.swift index ee814df6..2b4a559e 100644 --- a/Sources/Fluent/Query/Filter/QueryFilter.swift +++ b/Sources/Fluent/Query/Filter/QueryFilter.swift @@ -24,7 +24,7 @@ public struct QueryFilter where Database: QuerySupporting { /// Supported filter comparison types. public struct QueryFilterType: Equatable where Database: QuerySupporting { - enum QueryFilterTypeStorage: Int { + enum QueryFilterTypeStorage: Equatable { case equals case notEquals case greaterThan @@ -33,11 +33,20 @@ public struct QueryFilterType: Equatable where Database: QuerySupporti case lessThanOrEquals case `in` case notIn + case custom(Database.QueryFilter) } /// Internal storage. let storage: QueryFilterTypeStorage + /// Returns the custom query filter if it is set. + public func custom() -> Database.QueryFilter? { + switch storage { + case .custom(let filter): return filter + default: return nil + } + } + /// == public static var equals: QueryFilterType { return .init(storage: .equals) } /// != @@ -54,6 +63,11 @@ public struct QueryFilterType: Equatable where Database: QuerySupporti public static var `in`: QueryFilterType { return .init(storage: .`in`) } /// not a part of public static var notIn: QueryFilterType { return .init(storage: .notIn) } + + /// Custom filter for this database type. + public static func custom(_ filter: Database.QueryFilter) -> QueryFilterType { + return .init(storage: .custom(filter)) + } } /// Describes the values a subset can have. The subset can be either an array of encodable diff --git a/Sources/Fluent/Query/QuerySupporting.swift b/Sources/Fluent/Query/QuerySupporting.swift index ad36a8c6..88c6082d 100644 --- a/Sources/Fluent/Query/QuerySupporting.swift +++ b/Sources/Fluent/Query/QuerySupporting.swift @@ -27,6 +27,9 @@ public protocol QuerySupporting: Database { /// Parses this db's `QueryDataConvertible` into a native type. static func queryDataParse(_ type: T.Type, from data: QueryData) throws -> T? + + /// This database's native filter types. + associatedtype QueryFilter: Equatable } public protocol FluentData { diff --git a/Sources/FluentSQL/DatabaseQuery.swift b/Sources/FluentSQL/DatabaseQuery.swift index 4982b204..6bb24c03 100644 --- a/Sources/FluentSQL/DatabaseQuery.swift +++ b/Sources/FluentSQL/DatabaseQuery.swift @@ -1,7 +1,7 @@ import Fluent import SQL -extension DatabaseQuery { +extension DatabaseQuery where Database.QueryFilter: DataPredicateComparisonConvertible { /// Create a SQL query from this database query. /// All Encodable values found while converting the query /// will be returned in an array in the order that placeholders diff --git a/Sources/FluentSQL/QueryComparison.swift b/Sources/FluentSQL/QueryComparison.swift index f68b92f8..9e4c800d 100644 --- a/Sources/FluentSQL/QueryComparison.swift +++ b/Sources/FluentSQL/QueryComparison.swift @@ -1,37 +1,58 @@ +import CodableKit import Fluent import SQL extension QueryFilterType { /// Convert query comparison to sql predicate comparison. - internal func makeDataPredicateComparison(for value: QueryFilterValue) -> DataPredicateComparison { - switch self { - case .greaterThan: return .greaterThan - case .greaterThanOrEquals: return .greaterThanOrEqual - case .lessThan: return .lessThan - case .lessThanOrEquals: return .lessThanOrEqual - case .equals: - if let _ = value.field() { - return .equal - } else if let data = value.data()?.first { - return data.isNull ? .isNull : .equal - } else { - return .none - } - case .notEquals: - if let _ = value.field() { - return .notEqual - } else if let data = value.data()?.first { - return data.isNull ? .isNotNull : .notEqual - } else { - return .none + internal func makeDataPredicateComparison(for filter: QueryFilter) -> DataPredicateComparison + where D.QueryFilter: DataPredicateComparisonConvertible + { + if let custom = filter.type.custom() { + return custom.convertToDataPredicateComparison() + } else { + switch self { + case .greaterThan: return .greaterThan + case .greaterThanOrEquals: return .greaterThanOrEqual + case .lessThan: return .lessThan + case .lessThanOrEquals: return .lessThanOrEqual + case .equals: + if let _ = filter.value.field() { + return .equal + } else if let data = filter.value.data()?.first { + return data.isNull ? .isNull : .equal + } else { + return .none + } + case .notEquals: + if let _ = filter.value.field() { + return .notEqual + } else if let data = filter.value.data()?.first { + return data.isNull ? .isNotNull : .notEqual + } else { + return .none + } + case .in: return .in + case .notIn: return .notIn + default: return .none } - case .in: return .in - case .notIn: return .notIn - default: return .none } } } +extension DataPredicateComparison: DataPredicateComparisonConvertible { + public func convertToDataPredicateComparison() -> DataPredicateComparison { + return self + } + public static func convertFromDataPredicateComparison(_ comparison: DataPredicateComparison) -> DataPredicateComparison { + return comparison + } +} + +public protocol DataPredicateComparisonConvertible { + func convertToDataPredicateComparison() -> DataPredicateComparison + static func convertFromDataPredicateComparison(_ comparison: DataPredicateComparison) -> Self +} + extension QueryFilterValue { /// Convert query comparison value to sql data predicate value. internal func makeDataPredicateValue() -> DataPredicateValue { @@ -42,14 +63,42 @@ extension QueryFilterValue { } else { return .none } - - /* - switch self { - case .array(let array): return (.placeholders(count: array.count), array) - case .subquery(let subquery): - let (dataQuery, values) = subquery.makeDataQuery() - return (.subquery(dataQuery), values) - } - */ } } + + +infix operator ~= +/// Has prefix +public func ~= (lhs: KeyPath, rhs: String) throws -> ModelFilter + where Value: KeyStringDecodable, Model.Database.QueryFilter: DataPredicateComparisonConvertible +{ + return try _contains(lhs, value: "%\(rhs)") +} + +infix operator =~ +/// Has suffix. +public func =~ (lhs: KeyPath, rhs: String) throws -> ModelFilter + where Value: KeyStringDecodable, Model.Database.QueryFilter: DataPredicateComparisonConvertible +{ + return try _contains(lhs, value: "\(rhs)%") +} + +infix operator ~~ +/// Contains. +public func ~~ (lhs: KeyPath, rhs: String) throws -> ModelFilter + where Value: KeyStringDecodable, Model.Database.QueryFilter: DataPredicateComparisonConvertible +{ + return try _contains(lhs, value: "%\(rhs)%") +} + +/// Operator helper func. +private func _contains(_ key: KeyPath, value: String) throws -> ModelFilter + where V: KeyStringDecodable, M.Database.QueryFilter: DataPredicateComparisonConvertible +{ + let filter = try QueryFilter( + field: key.makeQueryField(), + type: .custom(.convertFromDataPredicateComparison(.like)), + value: .data(value) + ) + return ModelFilter(filter: filter) +} diff --git a/Sources/FluentSQL/QueryFilter.swift b/Sources/FluentSQL/QueryFilter.swift index 5fbb0094..8129eae7 100644 --- a/Sources/FluentSQL/QueryFilter.swift +++ b/Sources/FluentSQL/QueryFilter.swift @@ -1,7 +1,7 @@ import Fluent import SQL -extension QueryFilterItem { +extension QueryFilterItem where Database.QueryFilter: DataPredicateComparisonConvertible { /// Convert query filter to sql data predicate and bind values. internal func makeDataPredicateItem() -> (DataPredicateItem, [Database.QueryData]) { let item: DataPredicateItem @@ -11,7 +11,7 @@ extension QueryFilterItem { case .single(let filter): let predicate = DataPredicate( column: filter.field.makeDataColumn(), - comparison: filter.type.makeDataPredicateComparison(for: filter.value), + comparison: filter.type.makeDataPredicateComparison(for: filter), value: filter.value.makeDataPredicateValue() ) if let array = filter.value.data() {