0

Context

I am attempting to take a Swift Predicate created with the #Predicate macro and parse it into a database query statement. (SwiftData does this under the hood to query the underlying SQLite database that powers Core Data).

Example:

class Foo
{
    var title: String = ""
}

let p = #Predicate<Foo> { $0.title == "test" }

Given that predicate, p, I want to turn it into this string-ified query statement:

SELECT * FROM `Foo` WHERE `title` = "test"

Question

The process for doing this is very sparsely documented. I'm following that example by declaring a custom protocol and then having each type of PredicateExpression that I support adopt that protocol. (This code is very proof-of-concept at the moment; it ignores errors and handles only this very simple Predicate for now):

protocol DBPredicateExpression {
    func dbQuery() -> String
}


// Handle the "==" operator
extension PredicateExpressions.Equal: DBPredicateExpression where LHS: DBPredicateExpression, RHS: DBPredicateExpression
{
    func dbQuery() -> String {
        return lhs.dbQuery() + " = " + rhs.dbQuery()
    }
} 


// Handle constant values
extension PredicateExpressions.Value: DBPredicateExpression
{
    func dbQuery() -> String {
        if let v = value as? String {
            return "\"\(v)\""
        }

        return ""  // TODO: handle other value types; throw for unsupported ones.
    }
}


// Handle KeyPaths
extension PredicateExpressions.Keypath: DBPredicateExpression
{
    func dbQuery() -> String
    {
        let rootType: Any.Type = type(of: root)      // String(describing:) --> Variable<Foo>
        let kpType: Any.Type = type(of: keyPath)     // String(describing:) --> ReferenceWritableKeyPath<Foo, String>

        // THIS IS WHERE I NEED HELP.
        // How do I introspect `root` and `keyPath` to get string versions of them?
        // This should return: "FROM `Foo` WHERE `title`"
        // But getting "Foo" and "title" from `root` and `keyPath` is the tricky bit.
    }
}

As stated in the comment: how do I transform the KeyPath and Root types into their String equivalents (title and Foo)? Using string(describing:) seems very fragile and non-performant. How does SwiftData take one of these KeyPath Predicates and transform it into the SQL statements that ultimately get run in Core Data?

5
  • 1
    My understanding is that SwiftData looks at the schemaMetadata (generated by @Model macro) to find the corresponding column for the key path. I suppose you could write a similar macro too. Commented Jan 4 at 21:04
  • @sweeper - ah, that’s helpful. I can have a macro generate a [KeyPath: String] mapping and add a static table name for the type, conform Foo to some protocol that defines those two things, then cast Root as that protocol in the keypath handler. (Just seems like Apple chose the absolute hardest, most convoluted way to implement Predicate.) Commented Jan 4 at 21:46
  • So you want to generate queries that you can run against the underlying sqlite database? Commented Jan 4 at 22:35
  • @JoakimDanielson No. I have an app that uses Couchbase and I’d like to write a translation layer that lets me use Predicate throughout that app and have those Predicates transformed to Couchbase SQL++ queries. (I’m essentially writing a Couchbase SDK.) Commented Jan 5 at 0:06
  • Ok, all the metadata for a model is defined in Schema.Entity so that’s where I would start Commented Jan 5 at 8:04

0

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.