@@ -74,6 +74,35 @@ internal extension FetchRequest.Predicate.Comparison {
7474 predicate: FetchRequest . Predicate
7575 ) throws -> SQLFragment {
7676
77+ // `function(...) <operator> constant` comparisons compile to a SQL function call.
78+ if case let . function( function) = left {
79+ guard modifier == nil else {
80+ throw SQLiteDatabaseError . invalidPredicate ( predicate)
81+ }
82+ let functionFragment = try function. sqlFragment ( for: entity, predicate: predicate)
83+ switch type {
84+ case . lessThan, . lessThanOrEqualTo, . greaterThan, . greaterThanOrEqualTo:
85+ let value = try right. constantBinding ( predicate: predicate)
86+ return SQLFragment (
87+ sql: " \( functionFragment. sql) \( type. rawValue) ? " ,
88+ bindings: functionFragment. bindings + [ value]
89+ )
90+ case . equalTo, . notEqualTo:
91+ let value = try right. constantBinding ( predicate: predicate)
92+ let sqlOperator = ( type == . equalTo) ? " = " : " <> "
93+ guard let value else {
94+ let nullOperator = ( type == . equalTo) ? " IS NULL " : " IS NOT NULL "
95+ return SQLFragment ( sql: " \( functionFragment. sql) \( nullOperator) " , bindings: functionFragment. bindings)
96+ }
97+ return SQLFragment (
98+ sql: " \( functionFragment. sql) \( sqlOperator) ? " ,
99+ bindings: functionFragment. bindings + [ value]
100+ )
101+ default :
102+ throw SQLiteDatabaseError . invalidPredicate ( predicate)
103+ }
104+ }
105+
77106 // Only `keyPath <operator> constant` comparisons map directly to columns.
78107 guard case let . keyPath( keyPath) = left else {
79108 throw SQLiteDatabaseError . invalidPredicate ( predicate)
@@ -207,8 +236,43 @@ private extension SQLFragment {
207236 }
208237}
209238
239+ internal extension FetchRequest . Predicate . FunctionExpression {
240+
241+ /// Translate a function call expression into a SQL function-call fragment,
242+ /// e.g. `myFunction("lat", "lon", ?, ?)`.
243+ func sqlFragment(
244+ for entity: EntityDescription ,
245+ predicate: FetchRequest . Predicate
246+ ) throws -> SQLFragment {
247+ let argumentFragments = try arguments. map {
248+ try $0. argumentSQLFragment ( for: entity, predicate: predicate)
249+ }
250+ return SQLFragment (
251+ sql: " \( name) ( " + argumentFragments. map ( \. sql) . joined ( separator: " , " ) + " ) " ,
252+ bindings: argumentFragments. flatMap ( \. bindings)
253+ )
254+ }
255+ }
256+
210257private extension FetchRequest . Predicate . Expression {
211258
259+ /// The expression as a SQL fragment suitable for use as a function argument
260+ /// (a column reference, a constant placeholder, or a nested function call).
261+ func argumentSQLFragment(
262+ for entity: EntityDescription ,
263+ predicate: FetchRequest . Predicate
264+ ) throws -> SQLFragment {
265+ switch self {
266+ case let . keyPath( keyPath) :
267+ let column = try entity. validateColumn ( PropertyKey ( rawValue: keyPath. rawValue) , predicate: predicate)
268+ return SQLFragment ( sql: column, bindings: [ ] )
269+ case let . function( function) :
270+ return try function. sqlFragment ( for: entity, predicate: predicate)
271+ case . attribute, . relationship:
272+ return SQLFragment ( sql: " ? " , bindings: [ try constantBinding ( predicate: predicate) ] )
273+ }
274+ }
275+
212276 /// The expression as a single constant binding.
213277 func constantBinding( predicate: FetchRequest . Predicate ) throws -> Binding ? {
214278 switch self {
@@ -223,7 +287,7 @@ private extension FetchRequest.Predicate.Expression {
223287 case . toMany:
224288 throw SQLiteDatabaseError . invalidPredicate ( predicate)
225289 }
226- case . keyPath:
290+ case . keyPath, . function :
227291 throw SQLiteDatabaseError . invalidPredicate ( predicate)
228292 }
229293 }
0 commit comments