Statement
A prepared statement.
final class Statement
Overview
Prepared statements let you execute an SQL query several times, with different arguments if needed.
Reusing prepared statements is a performance optimization technique because SQLite parses and analyses the SQL query only once, when the prepared statement is created.
Building Prepared Statements
Build a prepared statement with the makeStatement(sql:)
method:
try dbQueue.write { db in
let insertStatement = try db.makeStatement(sql: """
INSERT INTO player (name, score) VALUES (:name, :score)
""")
let selectStatement = try db.makeStatement(sql: """
SELECT * FROM player WHERE name = ?
""")
}
The ?
and colon-prefixed keys like :name
in the SQL query are the statement arguments. Set the values for those arguments with arrays or dictionaries of database values, or StatementArguments
instances:
insertStatement.arguments = ["name": "Arthur", "score": 1000]
selectStatement.arguments = ["Arthur"]
Alternatively, the makeStatement(literal:)
method creates prepared statements with support for SQL Interpolation:
let insertStatement = try db.makeStatement(literal: "INSERT ...")
let selectStatement = try db.makeStatement(literal: "SELECT ...")
// ~~~~~~~
The makeStatement
methods throw an error of code SQLITE_MISUSE
(21) if the SQL query contains multiple statements joined with a semicolon. See Parsing Multiple Prepared Statements from a Single SQL String below.
Executing Prepared Statements and Fetching Values
Prepared statements can be executed:
try insertStatement.execute()
To fetch rows and values from a prepared statement, use a fetching method of Row
, DatabaseValueConvertible
, or FetchableRecord
:
let players = try Player.fetchCursor(selectStatement) // A Cursor of Player
let players = try Player.fetchAll(selectStatement) // [Player]
let players = try Player.fetchSet(selectStatement) // Set<Player>
let player = try Player.fetchOne(selectStatement) // Player?
// ~~~~~~ or Row, Int, String, Date, etc.
Arguments can be set at the moment of the statement execution:
try insertStatement.execute(arguments: ["name": "Arthur", "score": 1000])
let player = try Player.fetchOne(selectStatement, arguments: ["Arthur"])
Caching Prepared Statements
When the same query will be used several times in the lifetime of an application, one may feel a natural desire to cache prepared statements.
Don’t cache statements yourself.
Instead, use the cachedStatement(sql:)
method. GRDB does all the hard caching and memory management:
let statement = try db.cachedStatement(sql: "INSERT ...")
The variant cachedStatement(literal:)
supports SQL Interpolation:
let statement = try db.cachedStatement(literal: "INSERT ...")
Should a cached prepared statement throw an error, don’t reuse it. Instead, reload one from the cache.
Parsing Multiple Prepared Statements from a Single SQL String
To build multiple statements joined with a semicolon, use allStatements(sql:arguments:)
:
let statements = try db.allStatements(sql: """
INSERT INTO player (name, score) VALUES (?, ?);
INSERT INTO player (name, score) VALUES (?, ?);
""", arguments: ["Arthur", 100, "O'Brien", 1000])
while let statement = try statements.next() {
try statement.execute()
}
The variant allStatements(literal:)
supports SQL Interpolation:
let statements = try db.allStatements(literal: """
INSERT INTO player (name, score) VALUES (\("Arthur"), \(100));
INSERT INTO player (name, score) VALUES (\("O'Brien"), \(1000));
""")
// An alternative way to iterate all statements
try statements.forEach { statement in
try statement.execute()
}
The results of multiple SELECT
statements can be joined into a single Cursor
. This is the GRDB version of the sqlite3_exec()
function:
let statements = try db.allStatements(sql: """
SELECT ...;
SELECT ...;
""")
let players = try statements.flatMap { statement in
try Player.fetchCursor(statement)
}
for let player = try players.next() {
print(player.name)
}
The SQLStatementCursor
returned from allStatements
can be turned into a regular Swift array, but in this case make sure all individual statements can compile even if the previous ones were not executed:
// OK: Array of statements
let statements = try Array(db.allStatements(sql: """
INSERT ...;
UPDATE ...;
"""))
// FAILURE: Can't build an array of statements since the INSERT won't
// compile until CREATE TABLE is executed.
let statements = try Array(db.allStatements(sql: """
CREATE TABLE player ...;
INSERT INTO player ...;
"""))
Executing a Prepared Statement
func execute(arguments: StatementArguments?
) throws Executes the prepared statement.
Arguments
var arguments: StatementArguments
The statement arguments.
func setArguments(StatementArguments
) throws Validates and sets the statement arguments.
func setUncheckedArguments(StatementArguments
) Set arguments without any validation. Trades safety for performance.
func validateArguments(StatementArguments
) throws Throws a
DatabaseError
of codeSQLITE_ERROR
if the provided arguments do not provide all values expected by the statement.struct StatementArguments
An instance of
StatementArguments
provides the values for argument placeholders in a preparedStatement
.
Statement Informations
var columnCount: Int
The number of columns in the resulting rows.
var columnNames: [String]
The column names, ordered from left to right.
var databaseRegion: DatabaseRegion
The database region that the statement looks into.
func index(ofColumn: String
) -> Int? Returns the index of the leftmost column with the given name.
var isReadonly: Bool
A boolean value indicating if the prepared statement makes no direct changes to the content of the database file.
var sql: String
The SQL query.
let sqliteStatement: SQLiteStatement
The raw SQLite statement, suitable for the SQLite C API.
typealias SQLiteStatement
A raw SQLite statement, suitable for the SQLite C API.