operations#
Advanced database operations for SQLAlchemy.
This module provides high-performance database operations that extend beyond basic CRUD functionality. It implements specialized database operations optimized for bulk data handling and schema management.
The operations module is designed to work seamlessly with SQLAlchemy Core and ORM, providing efficient implementations for common database operations patterns.
Features#
Cross-database ON CONFLICT/ON DUPLICATE KEY UPDATE operations
MERGE statement support for Oracle and PostgreSQL 15+
Security#
This module constructs SQL statements using database identifiers (table and column names) that MUST come from trusted sources only. All identifiers should originate from:
SQLAlchemy model metadata (e.g., Model.__table__)
Hardcoded strings in application code
Validated configuration files
Never pass user input directly as table names, column names, or other SQL identifiers. Data values are properly parameterized using bindparam() to prevent SQL injection.
Notes:#
This module is designed to be database-agnostic where possible, with specialized optimizations for specific database backends where appropriate.
See Also:#
sqlalchemy.sql.expression: SQLAlchemy Core expression languagesqlalchemy.orm: SQLAlchemy ORM functionalityadvanced_alchemy.extensions: Additional database extensions
- class advanced_alchemy.operations.MergeStatement[source]#
Bases:
Executable,ClauseElementA MERGE statement for Oracle and PostgreSQL 15+.
This provides a high-level interface for MERGE operations that can handle both matched and unmatched conditions.
- inherit_cache = True#
Indicate if this
HasCacheKeyinstance should make use of the cache key generation scheme used by its immediate superclass.The attribute defaults to
None, which indicates that a construct has not yet taken into account whether or not its appropriate for it to participate in caching; this is functionally equivalent to setting the value toFalse, except that a warning is also emitted.This flag can be set to
Trueon a particular class, if the SQL that corresponds to the object does not change based on attributes which are local to this class, and not its superclass.See also
Enabling Caching Support for Custom Constructs - General guideslines for setting the
HasCacheKey.inherit_cacheattribute for third-party or user defined SQL constructs.
- __init__(table, source, on_condition, when_matched_update=None, when_not_matched_insert=None)[source]#
Initialize a MERGE statement.
- Parameters:
source¶ (
Union[ClauseElement,str]) – Source data (can be a subquery or table)on_condition¶ (
ClauseElement) – Condition for matching rowswhen_matched_update¶ (
Optional[dict[str,Any]]) – Values to update when rows matchwhen_not_matched_insert¶ (
Optional[dict[str,Any]]) – Values to insert when rows don’t matchtable (Table)
source (ClauseElement | str)
on_condition (ClauseElement)
- Return type:
None
- class advanced_alchemy.operations.OnConflictUpsert[source]#
Bases:
objectCross-database upsert operation using dialect-specific constructs.
This class provides a unified interface for upsert operations across different database backends using their native ON CONFLICT, ON DUPLICATE KEY UPDATE, MERGE, or INSERT OR UPDATE mechanisms.
- static supports_native_upsert(dialect_name)[source]#
Check if the dialect supports the single-row
create_upsertAPI.This flag is scoped to the per-row
INSERT ... ON CONFLICT/ON DUPLICATE KEY UPDATEdialects thatOnConflictUpsert.create_upsert()can compile directly. The bulkMERGE(mssql, oracle) andINSERT OR UPDATE(spanner) primitives are dispatched separately byRepository.upsert_many()viaresolve_upsert_strategy()and are intentionally not reported here.
- static create_upsert(table, values, conflict_columns, update_columns=None, dialect_name=None, validate_identifiers=False)[source]#
Create a dialect-specific upsert statement.
- Parameters:
conflict_columns¶ (
list[str]) – Columns that define the conflict conditionupdate_columns¶ (
Optional[list[str]]) – Columns to update on conflict (defaults to all non-conflict columns)dialect_name¶ (
Optional[str]) – Database dialect name (auto-detected if not provided)validate_identifiers¶ (
bool) – If True, validate column names for safety (default: False)table (Table)
dialect_name (str | None)
validate_identifiers (bool)
- Return type:
- Returns:
A SQLAlchemy
Insertfor the ON-CONFLICT dialects (postgresql / cockroachdb / sqlite / duckdb / mysql / mariadb). MSSQL, Oracle, and Spanner are handled by the bulkMERGE/INSERT OR UPDATEpath inOnConflictUpsert.create_merge_many()andOnConflictUpsert.create_insert_or_update_many(), accessed throughresolve_upsert_strategy()from the repository layer.- Raises:
NotImplementedError – If the dialect doesn’t support native upsert
ValueError – If validate_identifiers is True and invalid identifiers are found
- static create_insert_or_update_many(table, rows)[source]#
Create a Spanner
INSERT OR UPDATEusing SQLAlchemy’s Insert.A regular
Insertretains result-column metadata, allowing callers to addreturning(model_type). The Spanner dialect compiles that combination toTHEN RETURN.- Parameters:
- Return type:
- Returns:
A multi-values insert prefixed with Spanner’s
OR UPDATEtoken.- Raises:
ValueError –
rowsis empty or rows have heterogeneous keys.
- static create_merge_upsert(table, values, conflict_columns, update_columns=None, dialect_name=None, validate_identifiers=False)[source]#
Create a MERGE-based upsert for Oracle/PostgreSQL 15+.
For Oracle databases, this method automatically generates values for primary key columns that have callable defaults (such as UUID generation functions). This is necessary because Oracle MERGE statements cannot use Python callable defaults directly in the INSERT clause. Since Oracle requires
FROM DUALforSELECTstatements without tables, the Oracle source subquery selects fromDUAL.- Parameters:
conflict_columns¶ (
list[str]) – Columns that define the matching conditionupdate_columns¶ (
Optional[list[str]]) – Columns to update on match (defaults to all non-conflict columns)dialect_name¶ (
Optional[str]) – Database dialect name (used to determine Oracle-specific syntax)validate_identifiers¶ (
bool) – If True, validate column names for safety (default: False)table (Table)
dialect_name (str | None)
validate_identifiers (bool)
- Return type:
tuple[MergeStatement,dict[str,Any]]- Returns:
A tuple of (MergeStatement, additional_params) where additional_params contains any generated values (like Oracle UUID primary keys)
- Raises:
ValueError – If validate_identifiers is True and invalid identifiers are found
- static create_upsert_many(table, rows, conflict_columns, update_columns=None, dialect_name=None, validate_identifiers=False, model_type=None)[source]#
Build a dialect-specific bulk Insert with ON CONFLICT / ON DUPLICATE KEY UPDATE.
Compiles to a single
INSERT ... VALUES (...), (...), ...per chunk so the round-trip cost is fixed regardless of batch size.- Parameters:
rows¶ (
Sequence[dict[str, typing.Any]]) – Rows to insert/update. All rows MUST share the same keys.conflict_columns¶ (
list[str]) – Columns that define the conflict / match condition.update_columns¶ (
Optional[list[str]]) – Columns to update on conflict (defaults to all non-conflict keys from the first row).dialect_name¶ (
Optional[str]) – Database dialect name; determines compile path.validate_identifiers¶ (
bool) – If True, validate column identifiers for safety.model_type¶ (
Optional[type[Any]]) – Optional ORM model target used for ORM-aware RETURNING.table (Table)
rows (Sequence[dict[str, TypeAliasForwardRef('typing.Any')]])
dialect_name (str | None)
validate_identifiers (bool)
- Return type:
- Returns:
A SQLAlchemy
Insertstatement configured for bulk upsert.- Raises:
ValueError –
rowsis empty, rows have heterogeneous keys, or identifier validation fails.NotImplementedError – The dialect does not support an ON CONFLICT style native bulk upsert.
- static create_merge_many(table, rows, conflict_columns, update_columns=None, dialect_name=None, validate_identifiers=False)[source]#
Build a multi-row
MergeStatementfor dialects with native MERGE support.- Parameters:
rows¶ (
Sequence[dict[str, typing.Any]]) – Rows to insert/update. All rows MUST share the same keys.conflict_columns¶ (
list[str]) – Columns that define the matching condition.update_columns¶ (
Optional[list[str]]) – Columns to update on match (defaults to all non-conflict keys from the first row).dialect_name¶ (
Optional[str]) – Database dialect name; selects the source construction.validate_identifiers¶ (
bool) – If True, validate column identifiers for safety.table (Table)
rows (Sequence[dict[str, TypeAliasForwardRef('typing.Any')]])
dialect_name (str | None)
validate_identifiers (bool)
- Return type:
- Returns:
A single
MergeStatementbinding all input rows.- Raises:
ValueError –
rowsis empty, rows have heterogeneous keys, or identifier validation fails.NotImplementedError – The dialect does not support bulk MERGE compilation.
- class advanced_alchemy.operations.UpsertStrategy[source]#
Bases:
NamedTupleDispatch decision returned by
resolve_upsert_strategy().Tells the repository which native primitive to compile (
on_conflict/merge/insert_or_update) or whether to take the existing SELECT-then-partition fallback. Theconflict_columnsfield is the validated unique key (PK / UniqueConstraint / unique Index) and has exactly the same columns as the caller’smatch_fields.
- advanced_alchemy.operations.resolve_column_default(arg)[source]#
Invoke a SQLAlchemy
ColumnDefault.argcallable, tolerating both signatures.SQLAlchemy accepts both context-taking defaults (
lambda ctx: …) and zero-arg defaults (lambda: …,uuid7,datetime.utcnow). The context-taking shape is attempted first withNone; a signature mismatch retries the zero-arg shape. Errors raised inside the default itself — including a context-sensitive default that dereferences theNonecontext — propagate so callers can route the operation through the ORM flush, where a real execution context is available.
- advanced_alchemy.operations.resolve_upsert_strategy(table, match_fields, dialect)[source]#
Resolve the optimal upsert strategy for
(table, match_fields, dialect).The result is cached for the process lifetime;
Tableobjects are singletons per declarative class, so this is one decision per(model, match_fields, dialect)tuple.Resolution priority:
match_fieldsequals the table’s primary key → native primitive for the dialect,conflict_columnsis the PK.A
UniqueConstraintwhose columns match exactly → native primitive where the backend can target it,conflict_columnsis that constraint’s columns.A unique
Indexwhose columns match exactly → native primitive where the backend can target it,conflict_columnsis that index’s columns. SpannerINSERT OR UPDATEonly matches the primary key, and ambiguous MySQL/MariaDB unique targets fall back.Otherwise →
kind="fallback",supports_returning=False. This includesmatch_fieldsentries that do not resolve to table columns (mapped attributes whose name differs from the column key, synonyms, hybrids) — the ORM fallback resolves those viagetattr.
- Parameters:
table¶ (
Table) – Target table. Used by identity for caching.match_fields¶ (
Sequence[str]) – Columns the caller wants to match on. Order-insensitive.dialect¶ (
Union[str,Dialect]) – Database dialect or dialect name. Passing the runtime dialect allows the resolver to honor its actual RETURNING capability.table (Table)
- Return type:
- Returns:
An
UpsertStrategydescribing the decision.- Raises:
ValueError –
match_fieldsis empty.
- advanced_alchemy.operations.validate_identifier(name, identifier_type='identifier')[source]#
Validate a SQL identifier to ensure it’s safe for use in SQL statements.
This function provides validation for SQL identifiers (table names, column names, etc.) to ensure they contain only safe characters. While the operations in this module should only receive identifiers from trusted sources, this validation adds an extra layer of security.
Note: SQL keywords (like ‘select’, ‘insert’, etc.) are allowed as they can be properly quoted/escaped by SQLAlchemy when used as identifiers.
- Parameters:
- Return type:
- Returns:
The validated identifier
- Raises:
ValueError – If the identifier is empty or contains invalid characters
Examples
>>> validate_identifier("user_id") 'user_id' >>> validate_identifier("users_table", "table") 'users_table' >>> validate_identifier("select") # SQL keywords are allowed 'select' >>> validate_identifier( ... "drop table users; --" ... ) # Raises ValueError - contains invalid characters