GRDB/Documentation.docc/Extension/DatabaseValueConvertible.md
GRDB/DatabaseValueConvertibleA type that can convert itself into and out of a database value.
A DatabaseValueConvertible type supports conversion to and from database values (null, integers, doubles, strings, and blobs). DatabaseValueConvertible is adopted by Bool, Int, String, Date, etc.
Note: Types that converts to and from multiple columns in a database row must not conform to the
DatabaseValueConvertibleprotocol. Those types are called record types, and should conform to record protocols instead. See doc:QueryInterface.
Note: Standard collections
Array,Set, andDictionarydo not conform toDatabaseValueConvertible. To store arrays, sets, or dictionaries in individual database values, wrap them as properties ofCodablerecord types. They will automatically be stored as JSON objects and arrays. See doc:QueryInterface.
To conform to DatabaseValueConvertible, implement the two requirements fromDatabaseValue(_:)-21zzv and databaseValue-1ob9k. Do not customize the fromMissingColumn()-7iamp requirement. If your type MyValue conforms, then the conformance of the optional type MyValue? is automatic.
The implementation of fromDatabaseValue must return nil if the type can not be decoded from the raw database value. This nil value will have GRDB throw a decoding error accordingly.
For example:
struct EvenInteger {
let value: Int // Guaranteed even
init?(_ value: Int) {
guard value.isMultiple(of: 2) else {
return nil // Not an even number
}
self.value = value
}
}
extension EvenInteger: DatabaseValueConvertible {
var databaseValue: DatabaseValue {
value.databaseValue
}
static func fromDatabaseValue(_ dbValue: DatabaseValue) -> Self? {
guard let value = Int.fromDatabaseValue(dbValue) else {
return nil // Not an integer
}
return EvenInteger(value) // Nil if not even
}
}
DatabaseValueConvertible implementation is ready-made for RawRepresentable types whose raw value is itself DatabaseValueConvertible, such as enums:
enum Grape: String {
case chardonnay, merlot, riesling
}
// Encodes and decodes `Grape` as a string in the database:
extension Grape: DatabaseValueConvertible { }
DatabaseValueConvertible is also ready-made for Codable types, which are automatically coded and decoded from JSON arrays and objects:
struct Color: Codable {
var red: Double
var green: Double
var blue: Double
}
// Encodes and decodes `Color` as a JSON object in the database:
extension Color: DatabaseValueConvertible { }
By default, such codable value types are encoded and decoded with the standard JSONEncoder and JSONDecoder. Data values are handled with the .base64 strategy, Date with the .millisecondsSince1970 strategy, and non conforming floats with the .throw strategy.
To customize the JSON format, provide an explicit implementation for the DatabaseValueConvertible requirements, or implement these two methods:
protocol DatabaseValueConvertible {
static func databaseJSONDecoder() -> JSONDecoder
static func databaseJSONEncoder() -> JSONEncoder
}
Tagged is a popular library that makes it possible to enhance the type-safety of our programs with dedicated wrappers around basic types. For example:
import Tagged
struct Player: Identifiable {
// Thanks to Tagged, Player.ID can not be mismatched with Team.ID or
// Award.ID, even though they all wrap strings.
typealias ID = Tagged<Player, String>
var id: ID
var name: String
var score: Int
}
Applications that use both Tagged and GRDB will want to add those lines somewhere:
import GRDB
import Tagged
// Add database support to Tagged values
extension Tagged: @retroactive SQLExpressible where RawValue: SQLExpressible { }
extension Tagged: @retroactive StatementBinding where RawValue: StatementBinding { }
extension Tagged: @retroactive StatementColumnConvertible where RawValue: StatementColumnConvertible { }
extension Tagged: @retroactive DatabaseValueConvertible where RawValue: DatabaseValueConvertible { }
This makes it possible to use Tagged values in all the expected places:
let id: Player.ID = ...
let player = try Player.find(db, id: id)
For extra performance, custom value types can conform to both DatabaseValueConvertible and StatementColumnConvertible. This extra protocol grants raw access to the low-level C SQLite interface when decoding values.
For example:
extension EvenInteger: StatementColumnConvertible {
init?(sqliteStatement: SQLiteStatement, index: CInt) {
let int64 = sqlite3_column_int64(sqliteStatement, index)
guard let value = Int(exactly: int64) else {
return nil // Does not fit Int (probably a 32-bit architecture)
}
self.init(value) // Nil if not even
}
}
This extra conformance is not required: only aim at the low-level C interface if you have identified a performance issue after profiling your application!
fromDatabaseValue(_:)-21zzvfromMissingColumn()-7iampdatabaseValue-1ob9kdatabaseJSONDecoder()-7zou9databaseJSONEncoder()-37sfffetchCursor(_:sql:arguments:adapter:)-6elczfetchAll(_:sql:arguments:adapter:)-1cqybfetchSet(_:sql:arguments:adapter:)-5jenefetchOne(_:sql:arguments:adapter:)-qvqpfetchCursor(_:arguments:adapter:)-4l6affetchAll(_:arguments:adapter:)-3abucfetchSet(_:arguments:adapter:)-6y54nfetchOne(_:arguments:adapter:)-3d7axfetchCursor(_:_:)-8q4r6fetchAll(_:_:)-9hkqsfetchSet(_:_:)-1fokefetchOne(_:_:)-o6yjDatabaseValueCursorStatementBinding