vapor/sql-kit

Strongly type queries

miroslavkovac opened this issue · 4 comments

Is there a way to write strongly typed queries?

For example, instead of:

db.select()
    .column("size")
    .from("planets")
    .where("name", .equal, "Earth")

I would like to write something like this:

db.select()
    .column(\Plant.$size)
    .from(Planets.self)
    .where(\Planet.$name, .equal, "Earth")

I am migrating the project form Vapor 3 to Vapor 4 and couldn't find a way of doing this, while it was previously possible. Thank you.

0xTim commented

@miroslavkovac which bits aren't working?

@madsodgaard you showed something in Discord that looked like this should work

@0xTim @miroslavkovac is right in that is not supported natively by SQL-Kit. I added some extensions to my code to help with using Fluent models in SQLKit code

This won't work if you use @Group in Fluent

Here are the helpers:

// From https://github.com/vapor/fluent-kit/pull/343
extension FieldKey: SQLExpression {
    /// See `SQLExpression`.
    public func serialize(to serializer: inout SQLSerializer) {
        SQLIdentifier(self.string(for: serializer.dialect))
            .serialize(to: &serializer)
    }

    // Converts `FieldKey` to a string.
    //
    // `.description` is not used here since that isn't
    // _necessarily_ a SQL compatible value.
    //
    // SQLDialect is passed in case a specific dialect may
    // need to have special values for id / aggregate.
    private func string(for dialect: SQLDialect) -> String {
        switch self {
        case .id:
            return "id"
        case .aggregate:
            return "aggregate"
        case .prefix(let prefix, let key):
            return prefix.string(for: dialect) + key.string(for: dialect)
        case .string(let string):
            return string
        }
    }
}

extension KeyPath: SQLExpression where Root: Fields, Value: AnyQueryableProperty {
    /// See `SQLExpression`.
    public func serialize(to serializer: inout SQLSerializer) {
        Root()[keyPath: self].path.first!.serialize(to: &serializer)
    }
}

extension SQLPredicateBuilder {
    @discardableResult
    func `where`<E>(_ lhs: SQLExpression, _ op: SQLBinaryOperator, _ rhs: E) -> Self
        where E: Encodable
    {
        return self.where(lhs, op, SQLBind(rhs))
    }
}

This allows you to write the code you specified in your issue.

0xTim commented

@gwynne this going to be pushed into Fluent 5?