SQLAlchemy release notes for 2026¶
This article contains the release notes for the SQLAlchemy, including the following when applicable:
- Behavior changes
- New features
- Customer-facing bug fixes
Snowflake uses semantic versioning for SQLAlchemy updates.
See Snowflake SQLAlchemy for documentation.
Version 2.1.0 (October 26, 2026)¶
Behavior changes¶
- The database and schema segments of the connection URL path now follow Snowflake quoted-identifier rules on SQLAlchemy 2.0 and 2.1: a
/inside"..."is part of the name, not a delimiter. In hand-written URLs, a+in the path is no longer read as a space, and an encoded%2Foutside quotes now separates the database from the schema (previously it stayed in the database name). For details, see URL database and schema path decoding. - On SQLAlchemy 2.1,
URL.create(...)values are no longer percent-decoded. On SQLAlchemy 2.0, a database or schema name that contains%and no raw"is still decoded. See Special characters in database and schema names. - Async support is now opt-in: install
snowflake-sqlalchemy[async](orsqlalchemy[asyncio]) to usecreate_async_engine(). SQLAlchemy 2.1 no longer installsgreenlet; without the extra, the install succeeds but async engine creation raises anImportErrorwith the install command. - Now requires
snowflake-connector-python>=5.1.0,<6.0.
New features and updates¶
- Added support for SQLAlchemy 2.0.44 through 2.1.x (
SQLAlchemy>=2.0.44,<2.2). The dialect detects SQLAlchemy 2.1 API changes at import time; no action is needed beyond upgrading. - Reflected
NUMBER(..., 0)andINTEGERcolumns now report SQLAlchemy’s genericIntegertype affinity. timedeltaliterals now render as SnowflakeINTERVAL '...'constants, so SQLAlchemy 2.1FrameClauseRANGEbounds such astimedelta(days=7)compile toRANGE BETWEEN INTERVAL '7 DAYS' PRECEDING. Negative intervals raise aCompileError, because Snowflake interval constants are unsigned. Bound (non-literal)timedeltaparameters are unchanged.- Snowflake custom types now declare SQLAlchemy 2.1
operator_classes, so expressions on them no longer emit anSADeprecationWarning.
Bug fixes¶
- Fixed database and schema names built with
snowflake.sqlalchemy.URLso that they round-trip unchanged on SQLAlchemy 2.0 and 2.1, including a database-only quoted name with special characters, which was previously left percent-encoded. - Fixed window
ROWSandRANGEbounds so that they compile as integer literals, avoiding bind-parameter conflicts. - Fixed
literal_bindsdate arithmetic withtimedeltavalues raisingCompileError. - SQLAlchemy 2.1
Delete.using()joins that contain the DELETE target table now raise aCompileErrorwith guidance, instead of failing in Snowflake with a duplicate alias error. Join an alias of the target table or use a subquery instead.
Version 2.0.0 (October 19, 2026)¶
This is a major release. To upgrade from 1.x, see Migrating Snowflake SQLAlchemy from 1.x to 2.x.
Behavior changes¶
- Dropped support for SQLAlchemy 1.4. The dialect now requires
SQLAlchemy>=2.0.44,<2.1; SQLAlchemy 2.1.0 removed APIs that the dialect imports at module load time. Removed the internalsnowflake.sqlalchemy.compatmodule. If you’re still on SQLAlchemy 1.4, pinsnowflake-sqlalchemy<2.0.0. - Now requires
snowflake-connector-python>=5.0.0, which provides the async driver. Connector 4.x is no longer supported. - Supported Python versions are now 3.11 through 3.14 (dropped 3.8, 3.9, and 3.10), because connector 5.x requires Python 3.11 or later.
enable_structured_type_jsonis on by default. Setting it toFalserestores raw JSON strings and emits aDeprecationWarning.- Removed the
force_div_is_floordivdialect option. The/operator always performs true division and//always performs floor division (FLOOR(left / right)). Passing the option tocreate_engine()raisesTypeError. If you’re coming from a release older than 1.11.1, integer//previously compiled to plain/(true division in Snowflake) under the default option value; it now floors. - Removed the
legacy_url_paramsopt-out and theSNOWFLAKE_SQLALCHEMY_LEGACY_URL_PARAMSenvironment variable. Passinglegacy_url_paramstocreate_engine()raisesTypeError, and the environment variable has no effect. Pass sensitive connection parameters throughconnect_args=.unsafe_skip_config_file_permissions_checkandcrl_unsafe_skip_file_permissions_checkare now also blocked from the URL query string.
New features and updates¶
- Added async support: use
create_async_engine()orcreate_snowflake_async_engine()with the samesnowflake://URL as the sync dialect to get anAsyncEngineandAsyncSession. The async dialect checks forgreenletwhen the engine is created and raises anImportErrorwith install instructions if it’s missing. - Added the
enable_structured_type_jsondialect option (on by default). For semi-structuredVARIANT,OBJECT, andARRAYcolumns and for structuredMAP,ARRAY(<type>), andOBJECT(...)columns, reads returndictorlistvalues, and writes acceptdictorlistvalues, which the dialect wraps inPARSE_JSON()(cast to the declared type for structured columns). The engine’sjson_serializerandjson_deserializerare used when provided. - Added support for UUID and DECFLOAT values inside
VARIANTand structured types.decimal.Decimalvalues in semi-structured payloads are serialized as JSON strings to preserve all 38DECFLOATdigits. - Added the opt-in
enable_native_uuiddialect option, which renders SQLAlchemy’s genericUuidtype andMapped[uuid.UUID]as native SnowflakeUUIDinstead ofCHAR(32), bindsuuid.UUIDvalues, and honorsas_uuidon reads. - Added opt-in strict host-name mode (
strict_host_namesorSNOWFLAKE_SQLALCHEMY_STRICT_HOST_NAMES), which stops the dialect from inferring or appending domains to host names and forwards connection parameters, includingconnection_nameprofiles, to the connector unvalidated. - Added
COLLATEsupport:Column.collate()andString(collation=...)render the collation as a single-quoted string literal. - Added subscript access (
col["key"],col[0]) forVARIANT,OBJECT,ARRAY, andMAPcolumns. - Added the
InsertMulticonstruct for multi-tableINSERT ALLandINSERT FIRSTstatements. - Added row access policy support on
SnowflakeTablethrough therow_access_policyparameter. - Added opt-in
snowflake_rely=Trueto renderRELYonPRIMARY KEY,FOREIGN KEY, andUNIQUEconstraints. - Added support for the
json_serializerandjson_deserializerparameters increate_engine(). - Added the
asdecimalparameter on_CUSTOM_DECIMALto opt into float results instead ofDecimal. - Added a
python_typeproperty to Snowflake custom types. - Added the full Snowflake
TYPE=JSONoption set toJSONFormatter, andif_not_existsandcommentoptions toCreateFileFormat. - The dialect now sets
enable_server_session_keep_alive_auto_detection=Trueexplicitly on new connections, which pins the connector’s current default and silences itsFutureWarning. A value supplied in the URL orconnect_argstakes precedence.
Bug fixes¶
- Improved stage reference handling in compiled
COPY INTOand staged-file SQL so thatExternalStagepaths, including sub-paths created withfrom_parent_stage, are escaped when quoting is required. - Fixed a
NULL_IFbackslash breakout inCOPYFILE_FORMAToptions. - Fixed
repr()ofCopyIntoStorageunloading into anExternalStageor a cloud location, which raisedUnsupportedCompilationError. - Fixed hybrid table index reflection: reflected index names are normalized to SQLAlchemy’s lowercase convention for unquoted identifiers,
get_multi_indexes()keys preserve the caller’s schema, and emptyINCLUDEcolumns reflect as[]. - Fixed
get_multi_indexes()raisingTypeErrorwhenfilter_namesisNoneand the schema contains hybrid tables. - Fixed reflection failing on schemas or databases with more than 10,000 objects by paginating the underlying
SHOWcommands. - Fixed PrivateLink and regional account parsing for org-style account names that contain dashes.
- Fixed
URLfailing to encode[,],?, and#in passwords. - Fixed invalid
LIMIT -1SQL for aSELECTwithOFFSETbut noLIMIT; the dialect now emitsLIMIT NULL OFFSET .... - Expired-session, expired-token (including error code
390195), and closed-connection errors are now detected as disconnects, so pooled connections are invalidated and recycled.
Version 1.11.0 (July 8, 2026)¶
New features and updates¶
- Restricted a set of sensitive connection parameters from being supplied via the URL query string. The following parameters must now be passed via
connect_args=increate_engine()instead:host,protocol,token_file_path,private_key_file,ocsp_response_cache_filename,connection_diag_log_path,crl_cache_dir,unsafe_file_write, andunsafe_skip_file_permissions_check. Applications already usingconnect_args=are unaffected. To temporarily restore the previous behavior, set theSNOWFLAKE_SQLALCHEMY_LEGACY_URL_PARAMS=1environment variable. - Improved
_url()helper andcreate_connect_argsconnection-parameter handling:accountandregionvalues are now validated against an allowlist of DNS-safe characters (alphanumeric,-,.,_) before being interpolated into the connection URL, preventing URL-authority corruption from unexpected characters.uservalues are now percent-encoded before being placed in the URL userinfo component, preserving the original value delivered to the connector while preventing@,?, and#from being misinterpreted as URL delimiters.
- Improved identifier quoting and string-literal escaping for caller-supplied values across the DDL compiler. Column keys in
MERGE INTO(both theWHEN NOT MATCHED … INSERTlist andWHEN MATCHED … SETtargets), stage namespaces/names,format_name, andfile_formatare now routed through the identifier preparer. Cloud-storage URIs,CREDENTIALS, andENCRYPTIONclauses (shared byCOPY INTOandCREATE STAGE) andFILES=(…)entries now apply the dialect’s standard literal escaping ('→'',\→\\).CREATE STAGEbuilds its SQL from the container’s fields rather thanrepr(container). - Improved escaping in
CopyIntoStorage,CreateStage, andCreateFileFormat, and added secret redaction:FILE_FORMAToption string values (for example,CSVFormatter().date_format(...),file_extension,timestamp_format) are now escaped before being embedded in the compiled SQL.repr()ofAWSBucket,AzureContainer, andGCSBucketnow masks cloud secrets (AWS_SECRET_KEY,AWS_KEY_ID,AWS_TOKEN,AZURE_SAS_TOKEN,MASTER_KEY), rendering them as'***'. Compiled SQL is unchanged.- Added an opt-in logging redactor for engine logs that contain inline credentials:
SnowflakeSecretRedactionFilter,add_secret_redaction_filter(), andredact_secrets(). PreferSTORAGE_INTEGRATIONto avoid putting secrets in SQL.
- Optimized single-table reflection so that reflecting one table no longer scans the entire schema, reducing latency and Snowflake credit usage for targeted
Inspectorcalls.
Bug fixes¶
- Fixed
ClusterByOptionto raiseTypeErrorat DDL compile time when an expression element is neither astrnor asqlalchemy.sql.expression.TextClause. Previously, such values were silently coerced viastr(), producing malformed DDL (for example, bind-parameter placeholders like:id_1) for any expression beyond a bare column name. Code using onlystrortext(...)is unaffected. - Fixed
get_view_definitionsilently truncating or failing for view names containing a single quote or backslash (for example,o'brien). The name is now SQL-escaped before being embedded in theSHOW VIEWS LIKE '...'literal, so such views are found correctly.
Version 1.10.2 (June 18, 2026)¶
Bug fixes¶
- Fixed double-escaped identifier quoting for database-qualified schemas in structured type column reflection. Since v1.10.1, the schema was quoted as a single identifier, so a qualified schema such as
"MYDB"."MYSCHEMA"became"""MYDB"".""MYSCHEMA""", causing theDESC TABLEfallback to fail (emitting aFailed to reflect table ... sqlalchemy:_get_schema_columnswarning) for every structured-typed table when reflecting a non-defaultdatabase.schema. The schema is now split on the dot and each component is double-quoted individually, preserving the identifier injection guard while correctly handling qualified schemas.
Version 1.10.1 (June 15, 2026)¶
Bug fixes¶
- Fixed
regexp_matchandregexp_replaceflags being rendered as bound parameters instead of literal strings. Flags passed toColumnElement.regexp_match(..., flags=...)andColumnElement.regexp_replace(..., flags=...)were processed through the standard parameter pipeline, producing incorrect SQL. Flags are now rendered as inline string literals, matching Snowflake’s expectedREGEXP_LIKE(col, pattern, 'i')andREGEXP_REPLACE(col, pattern, replacement, 'i')syntax. - Fixed inconsistent identifier quoting in structured type column reflection. The
DESC TABLEfallback path used raw unquoted identifiers for schema and table names while all other reflection paths applied proper quoting. Schema and table components are now consistently double-quoted before the statement is constructed.
Version 1.10.0 (May 20, 2026)¶
New features and updates¶
- Added Google Cloud Storage (GCS) bucket support for the
CopyIntoStorageexpression. - Added
SnowflakeBase,snowflake_declarative_base(), andSnowflakeSessionto enable efficient bulk inserts for ORM models with nullable optional columns. UsingSnowflakeBase(orsnowflake_declarative_base()) along withSnowflakeSessionbatches all objects into a singleexecutemanyINSERT, instead of producing one INSERT statement per distinct set of non-Nonecolumn keys. - Added the
case_sensitive_identifiersopt-in engine flag (constructor keyword argument or?case_sensitive_identifiers=TrueURL parameter) that governs case-sensitive identifier handling. The default value isFalse, so existing applications aren’t affected unless they explicitly opt in. When you opt in:- All-uppercase reserved-word identifiers (for example,
TABLE) are normalized toquoted_name("table", True)to prevent key-lookup mismatches between creation and reflection. - Mixed-case reflected identifiers (for example,
MyColfrom a quoted Snowflake column) are returned asquoted_name("MyCol", True)instead of a plainstr. - Schema strings with inner double quotes (for example,
'"myschema"'or'"mydb"."myschema"') have their extracted parts markedquote=True, preserving case sensitivity in emitted SQL.
- All-uppercase reserved-word identifiers (for example,
- Added the
create_snowflake_engine(url, schema=..., case_sensitive_schema=True)helper. The helper URL-encodes case-sensitive schema names using%22so the Snowflake connector receives the literal double-quoted form. Schema names are now always URL-encoded regardless ofcase_sensitive_schema, preventing special characters (?,#,/) from being misinterpreted as URL delimiters. - Added
snowflake.sqlalchemy.alembic_util.render_item, a drop-in Alembicrender_itemhook forenv.pythat serializesquoted_namecolumns withquote=Truecorrectly in generated migration files, preventing Alembic autogenerate from silently converting case-sensitive column names to uppercase. - Added support for cross-database schema reflection using
schema='database.schema'notation, so you can reflect and join tables from different databases in a single session without using raw SQL. - Added composite key ordering.
- Added a
SnowflakeWarningthat’s emitted at DDL compile time whenIdentity()is used on a primary key column. The warning alerts you that ORM flush operations will raise aFlushError. UseSequence()instead. The warning is emitted once per unique(table, column)pair per Python process. - Mapped the Snowflake
UUIDcolumn type tosqlalchemy.sql.sqltypes.UUIDfor reflection on SQLAlchemy 2.x. The column was previously reflected asNullType. Values are returned as plain strings (as_uuid=False) rather thanuuid.UUIDinstances. There’s no change on SQLAlchemy 1.4, where the genericUUIDtype doesn’t exist. - Optimized reflection performance:
- Added
get_multi_columns,get_multi_pk_constraint,get_multi_unique_constraints, andget_multi_foreign_keysfor SQLAlchemy 2.x bulk reflection. Each method issues one schema-wide query per reflection pass instead of one query per table. - 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. These methods previously always issuedSHOW … IN SCHEMA, even for single-table Inspector calls, which caused approximately 20-second delays on schemas with thousands of tables. - The
cache_column_metadata=Trueopt-in now enables per-tableSHOW … IN TABLEqueries forget_pk_constraint,get_unique_constraints,get_foreign_keys, andget_indexeson SQLAlchemy 1.4. - SQLAlchemy 2.x
get_columnsnow usesDESC TABLEdirectly so that temporary tables and dynamic tables are reflected correctly.
- Added
Bug fixes¶
- Fixed
with_loader_criteriasilently dropping filters on non-Snowflake dialects. 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, and others. Snowflake dialect behavior is unchanged; the BCR-1057 lateral-join workaround is now scoped to Snowflake connections only. - Scoped
referred_schema=Nonenormalization in foreign key reflection to the default schema only. When reflecting the default schema, same-schema foreign keys (default to default) keep the established SQLAlchemy convention ofreferred_schema=None. When reflecting a non-default schema, every foreign key keeps its actualreferred_schema. This change prevents Alembic autogenerate mismatches that previously occurred for cross-schema foreign keys that targeted the default schema. - Fixed case-sensitive identifier handling:
_split_schema_by_dotnow correctly parses SQL-escaped double quotes ("") inside quoted schema and database identifiers (for example,"my""schema"becomesmy"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._has_object(used byhas_tableandhas_sequence) now appliesdenormalize_nameto both the schema and object name before building theDESCSQL, making it consistent with all other reflection methods.create_connect_argsnow atomically replaces thename_utilsinstance when the URL’scase_sensitive_identifiersvalue differs from the current dialect state, so concurrent readers on other threads never observe a torn update.
- 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. - Fixed foreign key
referred_schemaresolution so reflected foreign keys keep their actual schema unless the target is in the connection’s default schema. Foreign keys whose target shared the reflected non-default schema were previously reported withreferred_schema=None, which caused SQLAlchemy’s_reflect_fkto autoload from the wrong schema and raiseNoReferencedColumnErrorduring Alembic autogenerate. - Replaced
SHOW TABLES LIKEwithSHOW INDEXES IN TABLEfor single-table index reflection, eliminating SQLLIKEwildcard false positives and case-sensitivity bugs.
Version 1.9.0 (March 04, 2026)¶
New features and updates¶
- Added support for
DECFLOATandVECTORdata types. - Added support for
server_version_infosupport. - Added support for
ILIKEin queries. - Introduced a shared helper for fully-qualified schema name resolution, replacing inconsistent ad-hoc patterns across reflection methods.
- Refactored column reflection internals into dedicated helpers to reduce complexity without changing behavior.
- Added
pytest-xdistparallel test support via per-worker schema provisioning hooks. - Bumped pandas lower bound in the sa14 test environment from <2.1 to >=2.1.1,<2.2 to ensure pre-built wheels are available for Python 3.12.
- Added support for timezone in timestamp and datetime types.
Bug fixes¶
- Fixed
SYSDATE()rendering. - Fixed and improved schema reflection.
- Fixed a crash issue when reflecting without specifying a schema, caused by
Nonearguments in internal schema resolution. - Fixed a crash issue when SHOW TABLES returns empty string table names, causing
IndexErrorduring reflection. - Fixed incomplete identity column reflection metadata. This column now includes all fields required by SQLAlchemy 2.0+ (
always,cycle,order, and so on). - Fixed SQLAlchemy version parsing.