FetchableRecord is a protocol for Swift types that can decode themselves from database rows.
Overview
Conform to FetchableRecord to fetch your custom types from the database:
struct Player: FetchableRecord {
var id: Int64
var name: String
var score: Int
init(row: Row) {
id = row["id"]
name = row["name"]
score = row["score"]
}
}
let players = try Player.fetchAll(db, sql: "SELECT * FROM player")
Decodable types get automatic FetchableRecord conformance with minimal setup.
Implement the init(row:) initializer:
struct Player: FetchableRecord {
var id: Int64
var name: String
var score: Int
init(row: Row) throws {
id = row["id"]
name = row["name"]
score = row["score"]
}
}
Decodable types automatically conform to FetchableRecord:
struct Player: FetchableRecord, Decodable {
var id: Int64
var name: String
var score: Int
}
// The init(row:) initializer is automatically provided
Required Methods
init(row:)
Creates a record from a database row.
The database row to decode
init(row: Row) throws {
id = row["id"]
name = row["name"]
score = row["score"]
}
The row may be reused during iteration. Call row.copy() if you need to store it.
Fetching Records
From SQL Queries
arguments
StatementArguments
default:"[]"
Query arguments
// Fetch all records
let players = try Player.fetchAll(db, sql: """
SELECT * FROM player WHERE score > ?
""", arguments: [1000])
// Fetch one record
let player = try Player.fetchOne(db, sql: """
SELECT * FROM player WHERE id = ?
""", arguments: [1])
// Fetch with a cursor
let cursor = try Player.fetchCursor(db, sql: "SELECT * FROM player")
while let player = try cursor.next() {
print(player.name)
}
From Prepared Statements
let statement = try db.makeStatement(sql: """
SELECT * FROM player WHERE score > ?
""")
let players = try Player.fetchAll(statement, arguments: [1000])
From Query Interface Requests
When combined with TableRecord, you can use the query interface:
struct Player: FetchableRecord, TableRecord, Decodable {
var id: Int64
var name: String
var score: Int
}
// Query interface methods
let players = try Player.filter(Column("score") > 1000).fetchAll(db)
let player = try Player.fetchOne(db, key: 1)
let topPlayers = try Player.order(Column("score").desc).limit(10).fetchAll(db)
Customizing Decodable Decoding
Column Name Conversion
struct Player: FetchableRecord, Decodable {
static let databaseColumnDecodingStrategy = DatabaseColumnDecodingStrategy.convertFromSnakeCase
var playerID: Int64 // Decoded from player_id column
var fullName: String // Decoded from full_name column
}
Date Decoding
struct Player: FetchableRecord, Decodable {
static func databaseDateDecodingStrategy(for column: String) -> DatabaseDateDecodingStrategy {
.timeIntervalSince1970
}
var createdAt: Date // Decoded from epoch timestamp
}
Data Decoding
struct Player: FetchableRecord, Decodable {
static func databaseDataDecodingStrategy(for column: String) -> DatabaseDataDecodingStrategy {
.custom { dbValue in
guard let data = Data.fromDatabaseValue(dbValue) else {
return nil
}
return Data(base64Encoded: data)
}
}
var avatarData: Data
}
JSON Decoding
struct Player: FetchableRecord, Decodable {
static func databaseJSONDecoder(for column: String) -> JSONDecoder {
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
return decoder
}
var metadata: Metadata // Decoded from JSON column
}
User Info
let decoderNameKey = CodingUserInfoKey(rawValue: "decoderName")!
struct Player: FetchableRecord, Decodable {
static var databaseDecodingUserInfo: [CodingUserInfoKey: Any] {
[decoderNameKey: "Database"]
}
init(from decoder: Decoder) throws {
print(decoder.userInfo[decoderNameKey]) // "Database"
// Decode properties...
}
}
Fetching Hashable Records
When your record is Hashable, you can fetch into a Set:
struct Player: FetchableRecord, Hashable, Decodable {
var id: Int64
var name: String
}
let players = try Player.fetchSet(db, sql: "SELECT * FROM player")
Fetching by Primary Key
When combined with TableRecord:
struct Player: FetchableRecord, TableRecord, Decodable {
var id: Int64
var name: String
}
// Fetch by primary key
let player = try Player.fetchOne(db, key: 1)
let players = try Player.fetchAll(db, keys: [1, 2, 3])
// Find (throws if not found)
let player = try Player.find(db, key: 1)
Using with Identifiable
struct Player: FetchableRecord, Identifiable, Decodable {
var id: Int64
var name: String
}
// Fetch by id
let player = try Player.fetchOne(db, id: 1)
let players = try Player.fetchAll(db, ids: [1, 2, 3])
Use cursors for large result sets to avoid loading everything into memory:let cursor = try Player.fetchCursor(db, sql: "SELECT * FROM player")
while let player = try cursor.next() {
// Process one player at a time
}
Cache prepared statements for repeated queries:let statement = try db.cachedStatement(sql: "SELECT * FROM player WHERE id = ?")
let player1 = try Player.fetchOne(statement, arguments: [1])
let player2 = try Player.fetchOne(statement, arguments: [2])
Error Handling
do {
let player = try Player.fetchOne(db, key: 999)
if let player {
print("Found: \(player.name)")
} else {
print("No player with that key")
}
} catch {
print("Database error: \(error)")
}
See Also