Skip to main content
GRDB ships with built-in support for storing and fetching various Swift and Foundation types in SQLite databases.

Supported Value Types

GRDB supports the following value types out of the box: Swift Standard Library:
  • Bool - Stored as INTEGER (0 or 1)
  • Double, Float - Stored as REAL
  • All signed and unsigned integer types (Int, Int8, Int16, Int32, Int64, UInt, UInt8, UInt16, UInt32, UInt64) - Stored as INTEGER
  • String - Stored as TEXT
  • Swift enums
Foundation:
  • Data - Stored as BLOB
  • Date - Stored as TEXT (“YYYY-MM-DD HH:MM:SS.SSS” in UTC)
  • DateComponents - Stored as TEXT (various formats)
  • Decimal - Stored as TEXT
  • NSNull - Stored as NULL
  • NSNumber - Stored as INTEGER or REAL
  • NSDecimalNumber - Stored as INTEGER or REAL
  • NSString - Stored as TEXT
  • URL - Stored as TEXT
  • UUID - Stored as 16-byte BLOB
CoreGraphics:
  • CGFloat - Stored as REAL

Using Values

Values can be used as statement arguments:
let url: URL = ...
let verified: Bool = ...
try db.execute(
    sql: "INSERT INTO link (url, verified) VALUES (?, ?)",
    arguments: [url, verified])
Extract values from rows:
let rows = try Row.fetchCursor(db, sql: "SELECT * FROM link")
while let row = try rows.next() {
    let url: URL = row["url"]
    let verified: Bool = row["verified"]
}
Fetch values directly:
let urls = try URL.fetchAll(db, sql: "SELECT url FROM link")  // [URL]
let count = try Int.fetchOne(db, sql: "SELECT COUNT(*) FROM link") // Int?

Working with Specific Types

Bool

Booleans are stored as integers (0 for false, 1 for true). Any non-zero value is considered true when reading:
try db.execute(sql: "INSERT INTO settings (enabled) VALUES (?)", arguments: [true])
let enabled: Bool = try Bool.fetchOne(db, sql: "SELECT enabled FROM settings")!

Integers

All Swift integer types are supported. SQLite stores integers as 64-bit signed values:
let score: Int = 1000
let playerId: Int64 = 42
let smallValue: UInt8 = 255

try db.execute(
    sql: "INSERT INTO game (score, playerId, level) VALUES (?, ?, ?)",
    arguments: [score, playerId, smallValue])
Be careful with unsigned types - values larger than Int64.max may cause conversion errors.

String

Strings are stored as UTF-8 TEXT:
let name = "Arthur"
try db.execute(sql: "INSERT INTO player (name) VALUES (?)", arguments: [name])

let names = try String.fetchAll(db, sql: "SELECT name FROM player")

Data

Data values suit BLOB SQLite columns:
let imageData: Data = ...
try db.execute(sql: "INSERT INTO photo (image) VALUES (?)", arguments: [imageData])

let row = try Row.fetchOne(db, sql: "SELECT image FROM photo")!
let data: Data = row["image"]

Memory Savings

You can avoid copying data with the withUnsafeData method:
let rows = try Row.fetchCursor(db, sql: "SELECT image FROM photo")
while let row = try rows.next() {
    try row.withUnsafeData(name: "image") { (data: Data?) in
        // Use data here - it doesn't outlive this closure
    }
}
The non-copied data does not live longer than the iteration step.

Date

Dates are stored using the format “YYYY-MM-DD HH:MM:SS.SSS” in UTC, precise to the millisecond:
let now = Date()
try db.execute(
    sql: "INSERT INTO player (creationDate) VALUES (?)",
    arguments: [now])

let row = try Row.fetchOne(db, sql: "SELECT creationDate FROM player")!
let creationDate: Date = row["creationDate"]
The range of valid years is 0000-9999. Years outside this range will cause issues.
This format was chosen because it is:
  • Comparable (ORDER BY works)
  • Compatible with SQLite’s CURRENT_TIMESTAMP
  • Compatible with SQLite date & time functions
  • Precise to the millisecond

Custom Date Formats

If you need a different date format, you can store dates as TimeInterval:
let date = Date()
let timeInterval = date.timeIntervalSinceReferenceDate
try db.execute(
    sql: "INSERT INTO event (timestamp) VALUES (?)",
    arguments: [timeInterval])

if let row = try Row.fetchOne(db, sql: "SELECT timestamp FROM event") {
    let timeInterval: TimeInterval = row["timestamp"]
    let date = Date(timeIntervalSinceReferenceDate: timeInterval)
}

DateComponents

Use DatabaseDateComponents to store date components:
var components = DateComponents()
components.year = 1973
components.month = 9
components.day = 18

// Store as "1973-09-18"
let dbComponents = DatabaseDateComponents(components, format: .YMD)
try db.execute(
    sql: "INSERT INTO player (birthDate) VALUES (?)",
    arguments: [dbComponents])

// Read back
let row = try Row.fetchOne(db, sql: "SELECT birthDate FROM player")!
let stored: DatabaseDateComponents = row["birthDate"]
stored.format         // .YMD
stored.dateComponents // DateComponents

Decimal and NSNumber

NSNumber stores as INTEGER or REAL:
// Stores as INTEGER
try db.execute(sql: "INSERT INTO transfer VALUES (?)", 
    arguments: [NSNumber(value: 10)])

// Stores as REAL
try db.execute(sql: "INSERT INTO transfer VALUES (?)", 
    arguments: [NSNumber(value: 10.5)])
Decimal stores as TEXT to preserve precision:
// Stores as '10.5'
try db.execute(sql: "INSERT INTO transfer VALUES (?)", 
    arguments: [Decimal(string: "10.5")!])

let amount = try Decimal.fetchOne(db, sql: "SELECT amount FROM transfer")
Use Decimal for financial calculations to avoid floating-point precision issues.

URL

URLs are stored as TEXT:
let url = URL(string: "https://github.com/groue/GRDB.swift")!
try db.execute(sql: "INSERT INTO link (url) VALUES (?)", arguments: [url])

let urls = try URL.fetchAll(db, sql: "SELECT url FROM link")

UUID

UUIDs are stored as 16-byte BLOB and can be decoded from both BLOB and TEXT:
let id = UUID()
try db.execute(sql: "INSERT INTO session (id) VALUES (?)", arguments: [id])

let sessionId: UUID = try UUID.fetchOne(db, sql: "SELECT id FROM session")!
GRDB can also decode UUIDs from string representations like “E621E1F8-C36C-495A-93FC-0C247A3E6E5F”.

Swift Enums

Swift enums that conform to RawRepresentable can be stored using their raw values:
enum Color: Int {
    case red, white, rose
}

enum Grape: String {
    case chardonnay, merlot, riesling
}

// Declare DatabaseValueConvertible adoption
extension Color: DatabaseValueConvertible { }
extension Grape: DatabaseValueConvertible { }

// Store
try db.execute(
    sql: "INSERT INTO wine (grape, color) VALUES (?, ?)",
    arguments: [Grape.merlot, Color.red])

// Read
let row = try Row.fetchOne(db, sql: "SELECT * FROM wine")!
let grape: Grape = row["grape"]  // .merlot
let color: Color = row["color"]  // .red

Handling Unknown Values

When a database value doesn’t match any enum case, you get a fatal error. Use DatabaseValue to handle this gracefully:
let row = try Row.fetchOne(db, sql: "SELECT 'syrah'")!

let dbValue: DatabaseValue = row[0]
if dbValue.isNull {
    // Handle NULL
} else if let grape = Grape.fromDatabaseValue(dbValue) {
    // Handle valid grape
} else {
    // Handle unknown grape
}

Custom Value Types

To support your own types, conform to the DatabaseValueConvertible protocol:
struct Color {
    let red: Double
    let green: Double
    let blue: Double
}

extension Color: DatabaseValueConvertible {
    var databaseValue: DatabaseValue {
        "\(red),\(green),\(blue)".databaseValue
    }
    
    static func fromDatabaseValue(_ dbValue: DatabaseValue) -> Color? {
        guard let string = String.fromDatabaseValue(dbValue) else {
            return nil
        }
        let components = string.split(separator: ",").compactMap { Double($0) }
        guard components.count == 3 else { return nil }
        return Color(red: components[0], green: components[1], blue: components[2])
    }
}

NULL Handling

Use optional types to handle NULL values:
// NULL-safe reading
let name: String? = row["name"]  // nil if NULL
let name: String = row["name"]   // fatal error if NULL

// Writing NULL
try db.execute(
    sql: "INSERT INTO player (name, email) VALUES (?, ?)",
    arguments: ["Arthur", nil])  // email will be NULL

Type Conversions

GRDB follows SQLite’s type system and allows these conversions: Successful conversions:
  • All numeric SQLite values to all numeric Swift types and Bool
  • TEXT values to String
  • BLOB values to Data
NULL behavior:
  • NULL converts to nil for optional types
  • NULL causes a fatal error for non-optional types
Invalid conversions cause fatal errors:
let row = try Row.fetchOne(db, sql: "SELECT 'not a date'")!
row[0] as String  // "not a date" ✓
row[0] as Date    // fatal error ✗
Use DatabaseValue to handle conversions safely:
let dbValue: DatabaseValue = row[0]
if let date = Date.fromDatabaseValue(dbValue) {
    // Valid date
} else {
    // Invalid date
}

See Also

Build docs developers (and LLMs) love