DatabaseValueConvertible

A type that can convert itself into and out of a database value.

DatabaseValueConvertible.swift:38DatabaseValueConvertible.md
protocol DatabaseValueConvertible : SQLExpressible, StatementBinding
Browse conforming types

Overview

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.

Conforming to the DatabaseValueConvertible Protocol

To conform to DatabaseValueConvertible, implement the two requirements fromDatabaseValue(_:) and databaseValue. Do not customize the fromMissingColumn 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
    }
}

Built-in RawRepresentable support

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 { }

Built-in Codable support

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
}

Adding support for the Tagged library

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)

Optimized Values

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!

Creating a Value

Accessing the DatabaseValue

Configuring the JSON format for the standard Decodable protocol

  • databaseJSONDecoder()-7zou9
  • databaseJSONEncoder()-37sff

Fetching Values from Raw SQL

Fetching Values from a Prepared Statement

Fetching Values from a Request

Supporting Types