I would like to write a Swift struct like this to describe a SQL table:
struct Person: Table {
@Column var name: String
@Column var color: String?
}
My goal is to write the Table
protocol and Column
property wrapper. The reason for using property wrappers is so that later on I can parameterize it like @Column(options: [.autoincrement])
(and more, like foreign keys, etc.).
The ultimate goal is for this call:
Person.createStatement
to return a string CREATE TABLE Person (name STRING NOT NULL, color STRING)
, and this call:
Person(name: "Bob", color: nil).insertStatement
to return a string INSERT INTO Person (name, color) values ("Bob", NULL)
I think I've gotten close a few times, but no luck.
I started by writing a property wrapper to hold values whose types can describe themselves for SQL:
protocol SQLType {
static var sqlType: String { get }
}
@propertyWrapper
struct Column<T: SQLType> {
var sqlColumnType: String {
return T.sqlType
}
private var _value: T
init(wrappedValue: T) {
_value = wrappedValue;
}
var wrappedValue: T {
return _value
}
}
This works fine, except for optionals.
struct MyTable {
@Column var name: String?
}
Gives the error: Property type 'String?' does not match that of the 'wrappedValue' property of its wrapper type 'Column'
I tried this:
extension Optional: SQLType where Wrapped: SQLType {
static var sqlType: String {
return Wrapped.sqlType
}
}
But it has no effect.
I tried extending Optional without the type constraint but there doesn't seem to be any way to "cast" Wrapped
to SQLType
(e.g., static var sqlType: String { return (Wrapped as! SQLType).sqlType }
). That's ugly anyway.
I tried creating an enum SQLOptional<T>
that behaves like the real Optional
but then initialization is ugly: MyTable(color: .some("red")
, and worse the property wrapper forgets that it is a property wrapper:
MyTable(name: SQLOptional.some("George"))
gives Cannot convert value of type 'SQLOptional<String>' to expected argument type 'Column<SQLOptional<String>>'
.
Maybe property wrappers don't work when when their generic is itself a generic? If so I think I won't be able to use them and I should find another approach.
Am I overlooking a much better solution?