Hello @remcopoelstra,
GRDB does not provide built-in support for failable record initializers because the library has always focused on trustable databases, based on the premise that an SQLite managed by an app is not some rogue JSON downloaded from the internet, and that this premise provides some benefits.
Now, especially on macOS, where the user has access to the file system, I must admit that this premise shows some crack. And in the same time, the expected benefits are less clear as the library has matured over the years. But more analysis and work remains to be done: maybe we'll get a failable record initializer one day.
What is untrusted data, by the way?
-
Altered database schema? Missing tables, missing columns, modified relational constraints, foreign keys, column checks? This kind of attack can be checked when your app opens the database, by comparing the content of the sqlite_master
database with an expected snapshot.
-
Altered database values? SQLite has a very weak type system, so you can basically assume that any column of any row can contain any kind of value (null, integer, double, text, blob). Unwanted nulls should be prevented with NOT NULL constraints. Other unwanted values can be avoided with CHECK constraints, but this is a real chore and surely has performance implications.
There may be other nasty alterations, I don't know yet.
Today, dealing with untrusted databases with GRDB requires more work on the application side. Precisely speaking, only converting raw database values into Swift value types and record requires more work. Other fatal errors reveal a misuse and require a change in the code.
Decoding values throws a fatal error when the database contains unexpected values, such as Could not decode database value 256 into Int8
.
The fundamental GRDB defense against failed conversion is the DatabaseValue type, and the snippet below. It lets you handle null, valid values, and invalid values, without any fatal error, ever:
let dbValue: DatabaseValue = row[0]
if dbValue.isNull {
// Handle NULL
} else if let int8 = Int8.fromDatabaseValue(dbValue) {
// Handle valid Int8
} else {
// Handle invalid Int8
}
The bad news is that absolutely no high-level construct of GRDB builds on top of it 
That's enough for a first reply :-)