Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

sql.params

import sql.params

sql lifts part of this module out to its own top level; each name below is shown with the path that reaches it. Anything still spelled sql.params.* needs import sql.params.

Rewriting a statement’s placeholders into whatever the active adapter expects.

Engines disagree about how a parameter is spelled. PostgreSQL wants $1, SQLite accepts ?1, others want a bare ?. Without a translation step, switching adapters would mean rewriting every statement in a program, which is most of what makes changing database an ordeal.

So sql defines one spelling and translates. Statements are written with ? for positional parameters or :name for named ones, and each adapter’s driver declares which form it needs.

translate('select * from t where a = ? and b = ?', [1, 2], NUMBERED)
# { sql: 'select * from t where a = $1 and b = $2', values: [1, 2] }

The scanner understands enough SQL to know when a ? or a : is not a placeholder: inside a string, a quoted identifier, a bracketed identifier, a comment, or a PostgreSQL dollar-quoted body, and in the :: cast operator. A statement that needs the literal character rather than a placeholder writes ??, which becomes a single ?. That matters for PostgreSQL, where ? is a JSON containment operator.

Nothing here escapes a value or builds SQL out of one. Values are bound by the engine, always.

Constants

QUESTION

sql.QUESTION = 'question'

A bare ? for every parameter, in order. MySQL and MariaDB.

NUMBERED

sql.NUMBERED = 'numbered'

$1, $2 and so on, numbered from one in binding order. PostgreSQL.

INDEXED

sql.INDEXED = 'indexed'

?1, ?2 and so on. SQLite, which accepts a bare ? too but is given explicit indices so a repeated named parameter can be bound once rather than once per mention.

STYLES

sql.params.STYLES = [...]

Every style a driver may declare.

Functions

placeholder()

sql.placeholder(style: string, position: number) -> string

The placeholder text for the parameter at position, counting from one.

Used by the statement builders in crud, which generate SQL and so need to spell placeholders themselves rather than translate them.

Parameters

  • style (string) — One of QUESTION, NUMBERED or INDEXED.
  • position (number) — The parameter’s 1 based binding position.

Returns string

Raises QueryError if style is not one this module defines.

tokenize()

sql.params.tokenize(sql: string) -> list[dict]

Splits sql into the pieces that matter, in order.

Each token is a dictionary with a kind:

  • 'text' is SQL to copy through, text holding it.
  • 'opaque' is a run nothing inside counts in: a string, a quoted or bracketed identifier, a comment, or a dollar-quoted body. Also copied through, but never looked inside.
  • 'question' is a positional placeholder.
  • 'named' is a named one, name holding the name without its colon.
  • 'separator' is a semicolon at statement level.

Everything in this module reads the same statement the same way because everything in this module goes through here.

Parameters

  • sql (string)

Returns list[dict]

scan()

sql.scan(sql: string) -> dict

What placeholders sql uses, without needing any values.

prepare() needs this: it translates a statement before it has anything to bind, so it has to know what the statement asks for.

Parameters

  • sql (string)

Returns dict — { positional, names }, positional the count of ? and names the :name names in first-mention order.

split_statements()

sql.split_statements(sql: string) -> list[string]

Splits a script into its statements.

Only semicolons outside strings, identifiers and comments separate, so a semicolon inside a string literal or a trigger body does not split the statement it belongs to. Empty pieces, which a trailing semicolon leaves behind, are dropped.

Parameters

  • sql (string)

Returns list[string]

translate()

sql.translate(sql: string, params, style: string) -> dict

Rewrites sql into style and puts the values in binding order.

Accepts two forms, and only one of them per statement:

  • Positional. ? in the statement, params a list. Values bind in the order they appear.
  • Named. :name in the statement, params a dictionary. A name used more than once binds once under NUMBERED and INDEXED, and once per mention under QUESTION, which has no way to refer back.

Mixing the two in one statement raises, as does supplying a list for a named statement or a dictionary for a positional one. Each of those is a mistake with a silent wrong answer available, so none of them is guessed at.

Parameters

  • sql (string) — The statement, written with ? or :name.
  • params (list|dict|nil) — The values to bind. nil means none.
  • style (string) — The active driver’s placeholder style.

Returns dict — { sql, values }, where values is always a list in binding order.

Raises QueryError if the two forms are mixed, if params is the wrong shape for the form used, if a positional statement and its list disagree about how many values there are, or if a named statement mentions a name the dictionary does not hold.


2026, Richard Ore and Zuri contributors