Class Hierarchy
sqlexpr has two class hierarchies: SqlObj for SQL expressions and query
objects, and SqlParam for parameter placeholders.
SqlParam
- class SqlParam
Base class for parameter placeholders. These are not SQL expressions — they represent bound parameter values that emit a placeholder and optionally collect an argument. They are handled specially by the formatter via
fmt.param().- sql(fmt)
Emit the placeholder via
fmt.param(self).
SqlParam
├── SqlValue
└── SqlNamedValue
SqlValue — positional placeholder (
%s,?,:1)SqlNamedValue — named placeholder (
:name,%(name)s)
SqlObj
- class SqlObj
Base class for all SQL expression and query objects. Every class that produces SQL structure inherits from
SqlObj. This is what allows objects to be composed — any method that accepts aSqlObjwill accept any sqlexpr object (a term, a subquery, a cat expression, etc.).You do not instantiate
SqlObjdirectly. It defines the interface that SqlFormat uses to render queries.- sql(fmt)
Render this object into the formatter. Called by
SqlFormatduring query rendering. Each subclass implements this to write its SQL representation.- Parameters:
fmt – The
SqlFormatinstance accumulating the output.
- is_empty()
Returns
Trueif this object produces no SQL output. Used by boolean expressions and WHERE clauses to skip empty terms.- Return type:
bool
- is_complex()
Returns
Trueif this object should be rendered on multiple lines (e.g. a multi-term AND/OR expression). Used by the formatter to decide whether to break afterWHERE.- Return type:
bool
- get_len()
Returns the character length of the rendered output, or
Noneif the length cannot be determined without rendering (e.g. subqueries). Used by the formatter to decide whether a SELECT list fits on one line.- Return type:
int or None
SqlObj
├── SqlCat
├── SqlColumn
├── SqlColumnList
├── SqlColumnValue
├── SqlTerm
├── SqlBoolOp
│ ├── SqlAnd
│ └── SqlOr
├── SqlCte
│ └── PgCte
├── SqlJoin
├── SqlFromItem
├── SqlUnion
├── PgWindow
├── PgForLocking
└── SqlStatement
├── SqlCreateTable
├── SqlCreateIndex
├── SqlDropTable
└── SqlQuery
├── SqlSelect
│ └── PgSelect
├── SqlInsert
│ ├── PgInsert
│ ├── MysqlInsert
│ └── SqliteInsert
├── SqlUpdate
│ └── PgUpdate
└── SqlDelete
├── PgDelete
└── MysqlDelete
SqlStatement is a subclass of SqlObj for any top-level SQL statement
that can be sent to the database as a complete unit. It provides the
comment() method for attaching SQL comments to statements.
SqlQuery is a subclass of SqlStatement that marks objects which
represent DML queries (SELECT, INSERT, UPDATE, DELETE). It adds WITH/CTE
support. This distinction is used throughout the formatter to decide when to
wrap a value in parentheses as a subquery.
Special handling of SqlQuery instances occurs in:
SqlTerm — when value is a
SqlQuery, it is rendered indented inside parentheses:op (\n subquery\n). When expr is aSqlQuerywithop='EXISTS', it renders asEXISTS (\n subquery\n).SqlInsert (VALUES clause) — when a column value is a
SqlQuery, it is wrapped in parentheses as a scalar subquery within the VALUES list.SqlColumn — when name is a
SqlQuery, it is rendered in parentheses as a derived column. When alias is aSqlQuery, it renders as a CTE-styleAS (subquery).SqlFromItem (used by
from_table()) — when the table is aSqlQuery, it is rendered as a derived table:(\n subquery\n) alias.SqlJoin — when the table is a
SqlQuery, same parenthesized rendering.
In contrast, a plain SqlObj (like SqlCat) is rendered inline without
parentheses or indentation.
SqlStatement
- class SqlStatement
Base class for any top-level SQL statement. Subclassed by
SqlQuery(DML with CTEs) and DDL classes (SqlCreateTable,SqlCreateIndex,SqlDropTable).Provides comment support — a SQL comment block emitted before the statement.
- comment(comment)
Set a SQL comment that will be emitted as ``– `` prefixed lines before the statement.
- Parameters:
comment – A string (may contain newlines for multi-line comments).
- Returns:
selffor method chaining.
qb = (SQL.select() .from_table('users') .column('*') .comment('Fetch all active users')) # -- Fetch all active users # SELECT * # FROM users
Multi-line comments:
qb.comment('First line\nSecond line') # -- First line # -- Second line # SELECT ...
Interaction with SqlFormat
When you call SQL.format(query), the formatter:
Calls
query.sql(fmt)on the top-level query object.Each object writes its SQL by calling methods on the
fmtinstance:fmt.write(text)— write literal SQL text.fmt.write_text(value)— write a value: if it’s aSqlObjorSqlParam, callsvalue.sql(fmt); otherwise converts to string and writes literally.fmt.write_query(query)— write aSqlQueryas a parenthesized, indented subquery:(\n query\n).fmt.param(value_obj)— emit a parameter placeholder and collect the argument. AcceptsSqlValueorSqlNamedValue.fmt.comment(text)— write a ``– `` prefixed comment block.fmt.indent()/fmt.dedent()— manage indentation.fmt.mark()— set a potential line-break point.
After rendering completes,
fmt.get_sql()returns the formatted string andfmt.get_args()returns the collected parameters.
This means any SqlObj can be nested inside any other — a SqlSelect
inside a SqlTerm, a SqlCat inside a SqlColumn, etc. The formatter
handles indentation and line wrapping automatically.
Parameter Handling
Parameters are handled via SqlParam objects (SqlValue and
SqlNamedValue). When a parameter is encountered during rendering, it calls
fmt.param(self) which emits the appropriate placeholder for the formatter’s
style and collects the argument value.
The rules for when a parameter is created vs. when SQL is written literally:
SqlValue (
SQL.value()) — emits a positional placeholder and adds its value to the argument list.SqlNamedValue (
SQL.named_value()) — emits a named placeholder and adds its value to the argument dict.SqlTerm (
SQL.term()) — if value is notNone, not aSqlObj, and not aSqlParam, wraps it inSqlValueand emits a placeholder. If value is aSqlParam, calls its.sql()directly. If value is aSqlObj(e.g. a subquery), it is rendered via its.sql()method in parentheses.SqlInsert (
SQL.insert()) — for each column/value pair, if the value is aSqlParamit is emitted directly; if it is aSqlObjit is rendered as SQL; otherwise it is wrapped inSqlValue.SqlCat (
SQL.cat()) — does not create parameters. Non-SqlObjparts are converted to strings and written literally. UseSQL.value()orSQL.named_value()inside aSQL.cat()to create a parameterized value.
In summary: if you pass a plain Python value (string, int, bool, etc.) where a
SQL value is expected, it becomes a parameter. If you pass a SqlObj, it
is rendered as SQL. If you want a literal string in the SQL output (not
parameterized), wrap it in SQL.cat().