Formatting

The format() classmethod on each dialect class converts a query object into a SQL string with proper indentation and line wrapping.

Basic Usage

from sqlexpr import PG as SQL

qb = (SQL.select()
      .from_table('users')
      .column('name', 'email')
      .where('active', '=', True))
fmt = SQL.format(qb)

sql = fmt.get_sql()
args = fmt.get_args()
sql, args = fmt.get_sqlargs()
SELECT name, email
FROM users
WHERE active = %s

Options

Right margin (default 80):

fmt = SQL.format(qb, right_margin=120)

Indent size (default 4):

fmt = SQL.format(qb, indent=2)

Line Wrapping

The formatter automatically wraps lines that exceed the right margin. It tracks potential break points (marks) and splits at the most recent mark when a line gets too long.

For example, a SELECT with many columns:

SELECT
    col1, col2, col3, col4, col5, col6, col7, col8, col9, col10, col11,
    col12, col13

Parameter Styles

Different databases use different placeholder styles for bound parameters. Each dialect class uses the correct style automatically.

Every parameter in a query must either supply a value or omit it — you cannot mix the two. Omitting all values produces a reusable template; supplying all values produces executable SQL with bound arguments. See Template Mode below for details.

All examples below use the same query:

qb = (SQL.update('accounts')
      .set('balance', 100)
      .where(SQL.and_expr()
             .add('org_id', '=', 7)
             .add('status', '=', 'active')))

format (%s) — PG, MYSQL, MSSQL

Used by PostgreSQL (psycopg2), MySQL (pymysql), MSSQL (pymssql). Placeholders are %s. Arguments are collected as a list.

fmt = SQL.format(qb)
fmt.get_sql()
# UPDATE accounts
# SET balance = %s
# WHERE
#     org_id = %s
#     AND status = %s
fmt.get_args()   # [100, 7, 'active']

qmark (?) — SQLITE

Used by SQLite (sqlite3). Placeholders are ?. Arguments are collected as a list.

from sqlexpr import SQLITE as SQL

fmt = SQL.format(qb)
fmt.get_sql()
# UPDATE accounts
# SET balance = ?
# WHERE
#     org_id = ?
#     AND status = ?
fmt.get_args()   # [100, 7, 'active']

named (:name) — ORACLE

Used by Oracle (cx_Oracle, oracledb). Placeholders are :name. Arguments are collected as a dict.

Named styles require SQL.named_value(name, value) instead of raw values:

from sqlexpr import ORACLE as SQL

qb = (SQL.update('accounts')
      .set('balance', SQL.named_value('balance', 100))
      .where(SQL.and_expr()
             .add('org_id', '=', SQL.named_value('org_id', 7))
             .add('status', '=', SQL.named_value('status', 'active'))))
fmt = SQL.format(qb)
fmt.get_sql()
# UPDATE accounts
# SET balance = :balance
# WHERE
#     org_id = :org_id
#     AND status = :status
fmt.get_args()   # {'balance': 100, 'org_id': 7, 'status': 'active'}

numeric (:1, :2) — ORACLE

Also supported by Oracle. Placeholders are :1, :2, :3, etc. Arguments are collected as a list.

from sqlexpr import ORACLE as SQL

qb = (SQL.update('accounts')
      .set('balance', 100)
      .where(SQL.and_expr()
             .add('org_id', '=', 7)
             .add('status', '=', 'active')))
fmt = SQL.format_numeric(qb)
fmt.get_sql()
# UPDATE accounts
# SET balance = :1
# WHERE
#     org_id = :2
#     AND status = :3
fmt.get_args()   # [100, 7, 'active']

pyformat (%(name)s) — PG, MYSQL

Used by PostgreSQL (psycopg2 named mode) and MySQL (MySQLdb). Placeholders are %(name)s. Arguments are collected as a dict.

qb = (SQL.update('accounts')
      .set('balance', SQL.named_value('balance', 100))
      .where(SQL.and_expr()
             .add('org_id', '=', SQL.named_value('org_id', 7))
             .add('status', '=', SQL.named_value('status', 'active'))))
fmt = SQL.format_pyformat(qb)
fmt.get_sql()
# UPDATE accounts
# SET balance = %(balance)s
# WHERE
#     org_id = %(org_id)s
#     AND status = %(status)s
fmt.get_args()   # {'balance': 100, 'org_id': 7, 'status': 'active'}

Named Parameter Deduplication

When the same named parameter appears multiple times, the value only needs to be supplied on one occurrence — it doesn’t matter which one:

qb = (SQL.select()
      .from_table('documents')
      .column(SQL.cat('metadata -> ', SQL.named_value('elem', 'author'), " ->> 'name'"))
      .column(SQL.cat('metadata -> ', SQL.named_value('elem'), " ->> 'title'"))
      .where('id', '=', SQL.named_value('id', 42)))
fmt = SQL.format_pyformat(qb)
fmt.get_sql()
# SELECT metadata -> %(elem)s ->> 'name', metadata -> %(elem)s ->> 'title'
# FROM documents
# WHERE id = %(id)s
fmt.get_args()   # {'elem': 'author', 'id': 42}

The value can be supplied on any occurrence of the name. If the same name appears with conflicting values, a ValueError is raised.

Template Mode

Template mode produces a reusable SQL string with placeholders but no collected arguments. This is useful for preparing statements that will be executed multiple times with different parameter values.

Template mode is implicit: omit the value when constructing SQL.value() or SQL.named_value(name).

Positional template

qb = (SQL.update('accounts')
      .set('balance', SQL.value())
      .where(SQL.and_expr()
             .add('org_id', '=', SQL.value())
             .add('status', '=', SQL.value())))
fmt = SQL.format(qb)
fmt.get_sql()
# UPDATE accounts
# SET balance = %s
# WHERE
#     org_id = %s
#     AND status = %s
fmt.get_args()   # []

Named template

qb = (SQL.update('accounts')
      .set('balance', SQL.named_value('balance'))
      .where(SQL.and_expr()
             .add('org_id', '=', SQL.named_value('org_id'))
             .add('status', '=', SQL.named_value('status'))))
fmt = SQL.format_pyformat(qb)
fmt.get_sql()
# UPDATE accounts
# SET balance = %(balance)s
# WHERE
#     org_id = %(org_id)s
#     AND status = %(status)s
fmt.get_args()   # {}

The resulting SQL can then be executed repeatedly:

sql = fmt.get_sql()
cur.execute(sql, {'balance': 100, 'org_id': 7, 'status': 'active'})
cur.execute(sql, {'balance': 200, 'org_id': 8, 'status': 'pending'})

Mixing valued and unvalued parameters in the same query raises a ValueError.