Skip to main content

Prepared statements

Use a single method for all table creates, reads, and writes.


The Tableland SDK comes with a Database API that supports both static and prepared statements. A static statement is typical SQL where the values are part of the statement; prepared statement will bind values to the statement as part of a separate method.

const preparedStmt = db
.prepare("SELECT * FROM users WHERE name = ?1")
.bind("Bobby Tables");
const staticStmt = db.prepare(
'SELECT * FROM users WHERE name = "Bobby Tables"'
);

Parameter binding

Tableland follows the SQLite convention for prepared statement parameter binding. This includes Ordered (?NNNN) and Anonymous (?) parameters, as well as all three forms of Named parameters. To bind a parameter, simply use the method stmt.bind(). This *****only works for parameters*****; a table’s name cannot be passed and bound as a parameter (e.g., SELECT * FROM ? ...).

SyntaxTypeDescription
?NNNOrderedA question mark followed by a number NNN holds a spot for the NNN-th parameter.
?AnonymousA question mark that is not followed by a number creates a parameter with a number one greater than the largest parameter number already assigned. This parameter format is provided for compatibility with other database engines. But because it is easy to miscount the question marks, the use of this parameter format is discouraged. Programmers are encouraged to use one of the symbolic formats below or the ?NNN format above instead
:AAAANamedA colon followed by an identifier name holds a spot for a named parameter with the name :AAAA. To avoid confusion, it is best to avoid mixing named and numbered parameters.
@AAAANamedAn "at" sign works exactly like a colon, except that the name of the parameter created is @AAAA.
$AAAANamedA dollar-sign followed by an identifier name also holds a spot for a named parameter with the name $AAAA.

With anonymous binding, the order in which values are passed is the order assigned within the query. The ordered style allows you to specify a number that corresponds to position of the parameter within the statement, starting at 1.

const stmt = db
.prepare("SELECT * FROM users WHERE name = ? AND age = ?")
.bind("John Doe", 41);

const stmt = db
.prepare("SELECT * FROM users WHERE name = ?2 AND age = ?1")
.bind(41, "John Doe");

More complex binding is possible using named parameters and complex data types, which are converted to basic types on the fly. Named parameters are prefixed with @, :, or $, and it is best to not mix named and numbered parameters, as it can get a bit confusing.

const stmt = db
.prepare(
"INSERT INTO people VALUES (@first, ?, :first, ?, ?4, ?3, ?, $last);"
)
.bind(
45,
{ first: "Bobby", last: "Tables" },
[54, true, Uint8Array.from([1, 2, 3])],
null
);

Reusing prepared statements

Prepared statements can be reused with new bindings; there is no need to redefine the prepared statement:

const stmt = db.prepare("SELECT name, age FROM users WHERE age < ?1");
const young = await stmt.bind(20).all();
console.log(young);
/*
{
results: [...],
success: true
meta: {
duration: 31,
}
}
*/
const old = await stmt.bind(80).all();
console.log(old);
/*
{
results: [...],
success: true
meta: {
duration: 29,
}
}
*/

Return object

In the example above, the returned object from the prepare method is assigned to the stmt variable. Using the methods stmt.run(), stmt.all() and db.batch() will return an object that contains the results (if applicable), the success status, and a meta object with the internal duration of the operation in milliseconds, and any transaction information available.

{
results: [], // may be empty
success: boolean, // true if the operation was successful
error?: string,
meta: {
duration: number, // duration of operation in milliseconds
txn?: {
chainId: number,
tableId: string,
transactionHash: string,
blockNumber: number,
error?: string,
name?: string
wait(): Promise<{ ... }>
}
}
}

Recall that the Database API is compatible with Cloudflare is useful—because it matches the D1Database object, allowing any tools that are compatible with this object to also be compatible with Tableland’s object as well.