github snowflakedb/snowflake-sqlalchemy v1.10.0
Release

3 hours ago
  • v1.10.0 (May 20, 2026)
    • Fix with_loader_criteria silently dropping filters on non-Snowflake dialects (#676). Importing snowflake-sqlalchemy previously 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 UUID column type to sqlalchemy.sql.sqltypes.UUID for reflection on SQLAlchemy 2.x (#681). Previously reflected as NullType. Values are returned as plain strings (as_uuid=False) rather than uuid.UUID instances. No change on SQLAlchemy 1.4 where the generic UUID type does not exist.
    • Add GCS bucket support for CopyIntoStorage (SNOW-721174, #368).
    • Scope referred_schema=None normalization 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_fk from 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.
    • Add SnowflakeBase, snowflake_declarative_base(), and SnowflakeSession to enable efficient bulk inserts for ORM models with nullable optional columns (SNOW-893080, #441). When session.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, and SnowflakeSession passes render_nulls=True so all objects share the same parameter-key set and are batched into a single executemany INSERT.
    • Fix case-sensitive identifier handling (SNOW-1232488). Always-active bug fixes with no behavioural change for default users:
      • _split_schema_by_dot now 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_name now correctly double-quotes quoted_name("mycol", True) columns in CLUSTER BY clauses instead of silently dropping the case-sensitivity signal. The caller has already opted into case-sensitive semantics by constructing a quoted_name(..., True), so this is honoured independently of the dialect flag.
      • _has_object (used by has_table / has_sequence) now applies denormalize_name to both the schema and object name before building the DESC SQL, making it consistent with all other reflection methods.
      • Atomic _NameUtils swap in create_connect_args — when the URL's case_sensitive_identifiers value differs from the current dialect state, the name_utils instance 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_identifiers opt-in engine flag (kwarg or ?case_sensitive_identifiers=True URL param) governing three related behaviours. The default is False; existing applications are unaffected unless they explicitly opt in:
      • ALL-UPPERCASE reserved-word identifiers (e.g. TABLE) are normalised to quoted_name("table", True) instead of returning unchanged, preventing key-lookup mismatches between creation and reflection.
      • Mixed-case reflected identifiers (e.g. MyCol from a quoted Snowflake column) are returned as quoted_name("MyCol", True) instead of a plain str. Emitted SQL is identical in both modes (_requires_quotes force-quotes any name containing uppercase chars); the difference is only observable via isinstance(..., quoted_name) and .quote.
      • Schema strings with inner double-quotes — e.g. '"myschema"' or '"mydb"."myschema"' — have their extracted parts marked quote=True by _split_schema_by_dot, preserving case-sensitivity in emitted SQL. Without the flag, the extracted parts keep quote=None and the preparer's _requires_quotes heuristic decides per-part (stripping inner quotes for all-lowercase parts, which matches pre-PR behaviour). Use quoted_name("myschema", True) or MetaData(schema=quoted_name(..., True)) to opt into case-sensitivity on a per-value basis without enabling the flag.
    • Add create_snowflake_engine(url, schema=..., case_sensitive_schema=True) helper that URL-encodes case-sensitive schema names using %22 so the Snowflake connector receives the literal double-quoted form. Fix security vulnerability: schema names are now always URL-encoded regardless of case_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 Alembic render_item hook for env.py that serialises quoted_name columns with quote=True correctly in generated migration files, preventing Alembic autogenerate from silently converting case-sensitive column names to uppercase.
    • Emit SnowflakeWarning at DDL compile time when Identity() is used on a primary key column, alerting users that ORM flush operations will raise a FlushError. The warning is emitted once per unique (table, column) pair per Python process. Use Sequence() 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 (/) when div_is_floordiv=True: the Snowflake compiler now correctly delegates to the SQLAlchemy base implementation, emitting CAST(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_keys for SQLAlchemy 2.x bulk reflection — each issues one schema-wide query per reflection pass instead of one query per table.
      • SQLAlchemy 2.x get_columns now uses DESC TABLE directly (per-table, live) since get_multi_columns handles all bulk reflection; temporary tables and dynamic tables are reflected correctly without schema-wide queries.
      • Fix SHOW INDEXES IN TABLE replacing the previous SHOW TABLES LIKE approach for single-table index reflection, eliminating SQL LIKE wildcard false-positives and case-sensitivity bugs.
      • Add _always_quote_join helper that always quotes denormalised identifiers — ensures correct SQL for case-sensitive table and schema names in per-table reflection paths.
      • Fix foreign key referred_schema resolution 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 with referred_schema=None, which caused SQLAlchemy's _reflect_fk to autoload from the wrong schema and raise NoReferencedColumnError during 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=True opt-in enables per-table SHOW … IN TABLE queries for get_pk_constraint, get_unique_constraints, get_foreign_keys, and get_indexes on SQLAlchemy 1.4.
      • On SQLAlchemy 2.x, get_pk_constraint, get_unique_constraints, get_foreign_keys, and get_indexes now automatically use per-table SHOW … IN TABLE queries without any opt-in flag. Previously these methods always issued SHOW … IN SCHEMA even for single-table Inspector calls (e.g. pandas.read_sql_table()), causing ~20-second delays on schemas with thousands of tables (SNOW-689531).

Don't miss a new snowflake-sqlalchemy release

NewReleases is sending notifications on new releases.