Row represents a row of data fetched from the database, providing access to column values.
Overview
Fetch and decode rows from the database:
try dbQueue.read { db in
let rows = try Row.fetchAll(db, sql: "SELECT * FROM player")
for row in rows {
let id: Int64 = row["id"]
let name: String = row["name"]
let score: Int = row["score"]
print("\(name): \(score)")
}
}
Rows are lightweight references to database data. For permanent storage, decode into your own types or call row.copy().
Creating Rows
init()
Creates an empty row:
init(_:)
Creates a row from a dictionary:
let row = Row(["id": 1, "name": "Arthur", "score": 100])
Fetching from Database
See Fetching Rows below.
Accessing Column Values
By Column Name
Access values using subscript with column name:
let row = try Row.fetchOne(db, sql: "SELECT * FROM player WHERE id = 1")!
let id: Int64 = row["id"]
let name: String = row["name"]
let score: Int = row["score"]
By Column Index
Access values by index:
let id: Int64 = row[0] // First column
let name: String = row[1] // Second column
By Column
Use Column for type-safe access:
let id: Int64 = row[Column("id")]
let name: String = row[Column("name")]
Optional Values
Handle NULL values with optionals:
let deletedAt: Date? = row["deletedAt"] // nil if NULL
let email: String? = row["email"] // nil if NULL
decode(_:forColumn:)
Explicit decoding with error handling:
do {
let name: String = try row.decode(String.self, forColumn: "name")
} catch {
print("Failed to decode: \(error)")
}
count
The number of columns in the row
let row = try Row.fetchOne(db, sql: "SELECT id, name FROM player")!
print(row.count) // 2
columnNames
columnNames
LazyMapCollection<Row, String>
The names of all columns
let row = try Row.fetchOne(db, sql: "SELECT * FROM player")!
print(Array(row.columnNames)) // ["id", "name", "score"]
hasColumn(_:)
Checks if a column exists:
if row.hasColumn("email") {
let email: String? = row["email"]
}
hasNull(atIndex:)
Checks if a column contains NULL:
if row.hasNull(atIndex: 0) {
print("First column is NULL")
}
databaseValues
All column values as DatabaseValue
let values = row.databaseValues
for (index, value) in values.enumerated() {
print("Column \(index): \(value)")
}
containsNonNullValue
Whether the row contains at least one non-NULL value
if row.containsNonNullValue {
print("Row has data")
}
Fetching Rows
From SQL Queries
fetchAll
let rows = try Row.fetchAll(db, sql: """
SELECT * FROM player WHERE score > ?
""", arguments: [1000])
fetchOne
let row = try Row.fetchOne(db, sql: """
SELECT * FROM player WHERE id = ?
""", arguments: [1])
fetchCursor
let cursor = try Row.fetchCursor(db, sql: "SELECT * FROM player")
while let row = try cursor.next() {
let name: String = row["name"]
print(name)
}
From Prepared Statements
let statement = try db.makeStatement(sql: """
SELECT * FROM player WHERE score > ?
""")
let rows = try Row.fetchAll(statement, arguments: [1000])
From Requests
let request = Player.filter(Column("score") > 1000)
let rows = try Row.fetchAll(db, request)
Row Scopes
When using associations, related rows are available through scopes:
struct Player: TableRecord {
static let team = belongsTo(Team.self)
}
let request = Player.including(required: Player.team)
let row = try Row.fetchOne(db, request)!
let playerRow = row.scopes["player"]!
let teamRow = row.scopes["team"]!
let playerName: String = playerRow["name"]
let teamName: String = teamRow["name"]
scopes
A dictionary-like view of scoped rows
if let teamRow = row.scopes["team"] {
let teamName: String = teamRow["name"]
}
unscoped
The row without any scope
let unscopedRow = row.unscoped
Prefetched Rows
When using including(all:) associations, prefetched rows are available:
struct Author: TableRecord {
static let books = hasMany(Book.self)
}
let request = Author.including(all: Author.books)
let row = try Row.fetchOne(db, request)!
let authorName: String = row["name"]
let bookRows = row.prefetchedRows["books"]!
for bookRow in bookRows {
let title: String = bookRow["title"]
print(title)
}
prefetchedRows
A dictionary of prefetched row arrays
Copying Rows
copy()
Creates an immutable copy:
let cursor = try Row.fetchCursor(db, sql: "SELECT * FROM player")
var savedRows: [Row] = []
while let row = try cursor.next() {
savedRows.append(row.copy()) // Copy to keep the row
}
Rows fetched from cursors are reused during iteration. Always copy rows if you need to store them.
Decoding Custom Types
Decode your types from rows:
struct Player {
var id: Int64
var name: String
var score: Int
init(row: Row) {
id = row["id"]
name = row["name"]
score = row["score"]
}
}
let rows = try Row.fetchAll(db, sql: "SELECT * FROM player")
let players = rows.map { Player(row: $0) }
Or use FetchableRecord for automatic decoding.
Hashable Rows
When rows conform to Hashable, you can fetch into a Set:
let rows = try Row.fetchSet(db, sql: "SELECT DISTINCT name FROM player")
RandomAccessCollection
Rows conform to RandomAccessCollection:
let row = try Row.fetchOne(db, sql: "SELECT * FROM player")!
for (column, value) in row {
print("\(column): \(value)")
}
let firstValue = row[row.startIndex]
let lastValue = row[row.endIndex - 1]
Data Types
Rows decode standard Swift types:
Integers
let int: Int = row["value"]
let int8: Int8 = row["value"]
let int16: Int16 = row["value"]
let int32: Int32 = row["value"]
let int64: Int64 = row["value"]
let uint: UInt = row["value"]
// ... other integer types
Floating Point
let float: Float = row["value"]
let double: Double = row["value"]
Strings
let string: String = row["value"]
Data
let data: Data = row["value"]
Bool
let bool: Bool = row["value"] // 0 = false, non-zero = true
Date
let date: Date = row["createdAt"]
UUID
let uuid: UUID = row["id"]
URL
let url: URL = row["website"]
Working with NULL
Optional Decoding
let name: String? = row["name"] // nil if NULL
let score: Int? = row["score"] // nil if NULL
Checking for NULL
if row.hasNull(atIndex: 0) {
print("First column is NULL")
}
let value: DatabaseValue = row[0]
if value.isNull {
print("Value is NULL")
}
Default Values
let score: Int = row["score"] ?? 0
let name: String = row["name"] ?? "Unknown"
Use cursors for large result sets:let cursor = try Row.fetchCursor(db, sql: "SELECT * FROM player")
while let row = try cursor.next() {
// Process one row at a time
}
Decode to custom types instead of working with rows directly:// Better
let players = try Player.fetchAll(db)
// Less efficient
let rows = try Row.fetchAll(db, sql: "SELECT * FROM player")
let players = rows.map { Player(row: $0) }
Rows from cursors are reused. Always copy rows if you need to store them:let cursor = try Row.fetchCursor(db, sql: "SELECT * FROM player")
var rows: [Row] = []
while let row = try cursor.next() {
rows.append(row.copy()) // Don't forget to copy!
}
Error Handling
Missing Columns
do {
let value: String = try row.decode(String.self, forColumn: "nonexistent")
} catch let error as RowDecodingError {
print("Column not found: \(error)")
}
Type Mismatches
do {
let value: Int = row["name"] // name is a string
} catch {
print("Type mismatch: \(error)")
}
See Also