- v1.10.0 (May 20, 2026)
- Fix
with_loader_criteriasilently dropping filters on non-Snowflake dialects (#676). Importingsnowflake-sqlalchemypreviously altered SQLAlchemy's ORM compilation for every dialect in the process, causing loader-criteria filters to be omitted inside sealed subqueries when using PostgreSQL, MySQL, SQLite, etc. Snowflake dialect behavior is unchanged; the BCR-1057 lateral-join workaround is now scoped to Snowflake connections only. - Map Snowflake
UUIDcolumn type tosqlalchemy.sql.sqltypes.UUIDfor reflection on SQLAlchemy 2.x (#681). Previously reflected asNullType. Values are returned as plain strings (as_uuid=False) rather thanuuid.UUIDinstances. No change on SQLAlchemy 1.4 where the genericUUIDtype does not exist. - Add GCS bucket support for
CopyIntoStorage(SNOW-721174, #368). - Scope
referred_schema=Nonenormalization in foreign key reflection to the default schema only (#610, SNOW-2313675):- When reflecting the default schema, same-schema FKs (default → default) keep the established SQLAlchemy convention of
referred_schema=None, preserving compatibility with the upstream reflection test suite and with applications that do not qualify default-schema FK targets. - When reflecting a non-default schema every FK keeps its actual
referred_schema, which prevents SQLAlchemy's_reflect_fkfrom autoloading a non-default-schema target from the wrong place (the bug behind #610) and avoids the Alembic autogenerate mismatch that previously occurred when user metadata explicitly qualified a cross-schema FK that happened to target the default schema.
- When reflecting the default schema, same-schema FKs (default → default) keep the established SQLAlchemy convention of
- Add
SnowflakeBase,snowflake_declarative_base(), andSnowflakeSessionto enable efficient bulk inserts for ORM models with nullable optional columns (SNOW-893080, #441). Whensession.bulk_save_objects()is used with models that have randomly populated nullable columns, SQLAlchemy normally groups objects by their set of non-None column keys, producing O(N) separate INSERT statements.SnowflakeBase/snowflake_declarative_base()pre-populate all plain-nullable columns at construction time, andSnowflakeSessionpassesrender_nulls=Trueso all objects share the same parameter-key set and are batched into a singleexecutemanyINSERT. - Fix case-sensitive identifier handling (SNOW-1232488). Always-active bug fixes with no behavioural change for default users:
_split_schema_by_dotnow correctly parses SQL-escaped double-quotes ("") inside quoted schema/database identifiers (e.g."my""schema"→my"schema), preventing silent truncation of identifiers containing literal quote characters.denormalize_column_namenow correctly double-quotesquoted_name("mycol", True)columns inCLUSTER BYclauses instead of silently dropping the case-sensitivity signal. The caller has already opted into case-sensitive semantics by constructing aquoted_name(..., True), so this is honoured independently of the dialect flag._has_object(used byhas_table/has_sequence) now appliesdenormalize_nameto both the schema and object name before building theDESCSQL, making it consistent with all other reflection methods.- Atomic
_NameUtilsswap increate_connect_args— when the URL'scase_sensitive_identifiersvalue differs from the current dialect state, thename_utilsinstance is replaced rather than mutated in place, so concurrent readers on other threads observe either the old or the new instance but never a torn update.
- Add
case_sensitive_identifiersopt-in engine flag (kwarg or?case_sensitive_identifiers=TrueURL param) governing three related behaviours. The default isFalse; existing applications are unaffected unless they explicitly opt in:- ALL-UPPERCASE reserved-word identifiers (e.g.
TABLE) are normalised toquoted_name("table", True)instead of returning unchanged, preventing key-lookup mismatches between creation and reflection. - Mixed-case reflected identifiers (e.g.
MyColfrom a quoted Snowflake column) are returned asquoted_name("MyCol", True)instead of a plainstr. Emitted SQL is identical in both modes (_requires_quotesforce-quotes any name containing uppercase chars); the difference is only observable viaisinstance(..., quoted_name)and.quote. - Schema strings with inner double-quotes — e.g.
'"myschema"'or'"mydb"."myschema"'— have their extracted parts markedquote=Trueby_split_schema_by_dot, preserving case-sensitivity in emitted SQL. Without the flag, the extracted parts keepquote=Noneand the preparer's_requires_quotesheuristic decides per-part (stripping inner quotes for all-lowercase parts, which matches pre-PR behaviour). Usequoted_name("myschema", True)orMetaData(schema=quoted_name(..., True))to opt into case-sensitivity on a per-value basis without enabling the flag.
- ALL-UPPERCASE reserved-word identifiers (e.g.
- Add
create_snowflake_engine(url, schema=..., case_sensitive_schema=True)helper that URL-encodes case-sensitive schema names using%22so the Snowflake connector receives the literal double-quoted form. Fix security vulnerability: schema names are now always URL-encoded regardless ofcase_sensitive_schema, preventing special characters (?,#,/) from being misinterpreted as URL delimiters by SQLAlchemy's URL parser. - Add
snowflake.sqlalchemy.alembic_util.render_item— a drop-in Alembicrender_itemhook forenv.pythat serialisesquoted_namecolumns withquote=Truecorrectly in generated migration files, preventing Alembic autogenerate from silently converting case-sensitive column names to uppercase. - Emit
SnowflakeWarningat DDL compile time whenIdentity()is used on a primary key column, alerting users that ORM flush operations will raise aFlushError. The warning is emitted once per unique(table, column)pair per Python process. UseSequence()instead. - Add support for cross-database schema reflection using
schema='database.schema'notation. This allows reflecting and joining tables from different databases in a single session without raw SQL. (#456) - Restored backward-compatible SQL generation for true division (
/) whendiv_is_floordiv=True: the Snowflake compiler now correctly delegates to the SQLAlchemy base implementation, emittingCAST(col AS NUMERIC)for integer operands as it did before #545 introduced the override (#618). - Introduce composite key ordering, fixes #450
- Optimise reflection performance (SNOW-689531, #656):
- Add
get_multi_columns,get_multi_pk_constraint,get_multi_unique_constraints,get_multi_foreign_keysfor SQLAlchemy 2.x bulk reflection — each issues one schema-wide query per reflection pass instead of one query per table. - SQLAlchemy 2.x
get_columnsnow usesDESC TABLEdirectly (per-table, live) sinceget_multi_columnshandles all bulk reflection; temporary tables and dynamic tables are reflected correctly without schema-wide queries. - Fix
SHOW INDEXES IN TABLEreplacing the previousSHOW TABLES LIKEapproach for single-table index reflection, eliminating SQLLIKEwildcard false-positives and case-sensitivity bugs. - Add
_always_quote_joinhelper that always quotes denormalised identifiers — ensures correct SQL for case-sensitive table and schema names in per-table reflection paths. - Fix foreign key
referred_schemaresolution so reflected FKs always keep their actual schema unless the target lives in the connection's default schema. Previously FKs whose target shared the reflected non-default schema were reported withreferred_schema=None, which caused SQLAlchemy's_reflect_fkto autoload from the wrong schema and raiseNoReferencedColumnErrorduring Alembic autogenerate. - Add shared row-parsing helpers (
_parse_pk_rows,_parse_uk_rows,_parse_fk_rows) so correctness fixes propagate to both per-table and schema-wide reflection paths. cache_column_metadata=Trueopt-in enables per-tableSHOW … IN TABLEqueries forget_pk_constraint,get_unique_constraints,get_foreign_keys, andget_indexeson SQLAlchemy 1.4.- On SQLAlchemy 2.x,
get_pk_constraint,get_unique_constraints,get_foreign_keys, andget_indexesnow automatically use per-tableSHOW … IN TABLEqueries without any opt-in flag. Previously these methods always issuedSHOW … IN SCHEMAeven for single-table Inspector calls (e.g.pandas.read_sql_table()), causing ~20-second delays on schemas with thousands of tables (SNOW-689531).
- Add
- Fix