Skip to main content
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:
let row = 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)")
}

Row Information

count

count
Int
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

databaseValues
[DatabaseValue]
All column values as DatabaseValue
let values = row.databaseValues
for (index, value) in values.enumerated() {
    print("Column \(index): \(value)")
}

containsNonNullValue

containsNonNullValue
Bool
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

scopes
Row.ScopesView
A dictionary-like view of scoped rows
if let teamRow = row.scopes["team"] {
    let teamName: String = teamRow["name"]
}

unscoped

unscoped
Row
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

prefetchedRows
Row.PrefetchedRowsView
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"

Performance Tips

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

Build docs developers (and LLMs) love