Skip to content

feat(snowflake): adapt to derived DDL with typed options and comment support - #3

Merged
vistart merged 28 commits into
release/v1.0.0.dev2from
feature/activerecord-derived-ddl
Sep 27, 2026
Merged

vistart merged 28 commits into
release/v1.0.0.dev2from
feature/activerecord-derived-ddl

Conversation

@vistart

@vistart vistart commented Sep 27, 2026 •

Copy link
Copy Markdown
Contributor

Description

Adapts the Snowflake backend to the derived-DDL model in
rhosocial/python-activerecord#121.

Comment handling is the main addition: standalone COMMENT ON is supported, comment
capabilities are advertised deliberately, and a table comment renders inline the way Snowflake
expects rather than as a separate statement. Column options are typed —
SnowflakeCreateTableOptions renders TRANSIENT, and IDENTITY(start, step) and quoted
column collation are supported and pinned by tests.

Two capability corrections prevent the backend from claiming or emitting DDL it cannot
honour: capabilities for ALTER SCHEMA operations that cannot be rendered are no longer
advertised, and partition semantics are corrected to the clustering and external forms
Snowflake actually has. SnowflakeDatabaseMixin is wired into the dialect, materialized-view
expressions are wired into the core expression hierarchy, and an index is dropped by bare
name without ON <table>.

Also included: type DDL support, CommentSupport classification, typed database and
insert-overwrite expressions, and coverage for DDLSource collection, table inheritance and
tablespace.

Type of change

  • feat: A new feature
  • docs: Documentation only changes (if related to the new feature)
  • test: Adding missing tests or correcting existing tests (for the new feature)
  • chore: Changes to the build process or auxiliary tools and libraries (if related to the new feature)

Breaking Change

  • Yes, for end-users (backward-incompatible API changes)
  • Yes, for backend developers (internal architectural changes that may affect custom implementations)
  • No breaking changes

No commit carries a ! marker. The public model and query APIs are unchanged.

Impact on End-Users:

No backward-incompatible API changes. Unrenderable ALTER SCHEMA capabilities are no longer
advertised, so code that relied on them will now fail rather than emit SQL the server rejects.

Impact on Backend Developers (Custom Implementations):

  • dialect_options is removed from data-type value objects and from partition clauses.
    Partition semantics are corrected to clustering/external forms, so custom partition
    implementations keyed to the previous behaviour need updating.
  • SnowflakeCreateTableOptions is the new entry point for typed create-table options and
    replaces ad-hoc TRANSIENT handling; the transient tests were migrated onto it.
  • SnowflakeDatabaseMixin is now wired into the dialect, so tests construct
    SnowflakeDialect directly.
  • Materialized-view expressions are now part of the core expression hierarchy rather than
    dialect-local.
  • A .gitattributes file is added to enforce LF line endings, which also resolves the
    cross-platform line-ending noise this repository had.

Related Repositories/Packages

The core half is rhosocial/python-activerecord#121 (merged into release/v1.0.0.dev30) and
the shared contract half is rhosocial/python-activerecord-testsuite#21 (merged into
release/v1.0.0.dev17).

This PR also repoints this repository's workflow at those release branches, a prerequisite for
merging: the dependencies were pinned to feature/activerecord-derived-ddl, a branch that
only exists to carry this work.

Merged or in flight: mysql #56, postgres #58, mariadb #5, sqlserver #4, oracle. Still to
merge: clickhouse, firebird, bigquery.

Testing

  • I have added new tests to cover my changes.
  • I have updated existing tests to reflect my changes.
  • I have run the full test suite locally and confirmed all tests pass.

Local full-suite execution was skipped at the user's instruction. Verification relied on
remote CI on this exact tip 9e7e282, which is also the first execution against the release
branches rather than the feature branch, so the new dependency refs are verified to resolve.

Checklist

  • My code follows the project's coding conventions
  • I have added tests that prove my fix is effective or that my feature works
  • I have updated the documentation accordingly
  • I have added a changelog fragment to changelog.d/ (to be added after this PR is
    opened, named by the actual PR number)

Additional Notes

This repository's history was flattened before the merge sequence, and chore: add .gitattributes to enforce LF line endings is included here. That file matters more than it
looks: with the derived-DDL diff spanning dozens of files, mixed line endings would otherwise
show up as unrelated whitespace changes in review and again in the diff-coverage calculation.

(cherry picked from commit a9b4bfd)
(cherry picked from commit 3c87feb)
…al forms

Snowflake has no declarative RANGE/LIST/HASH table partitioning, so the
previous SnowflakePartitionClause (RANGE/LIST + VALUES LESS THAN/IN) produced
invalid Snowflake SQL. Correct the model:

- Capability bits are honest: supports_table_partitioning /
  supports_partitioned_table_creation / range / list / hash now return False.
- The generic PartitionClause is rejected (UnsupportedFeatureError); standard
  tables use CLUSTER BY.
- Add SnowflakeClusterByClause + format_cluster_by_clause (wired into
  SnowflakeTableModifierMixin/protocol) for standard-table clustering keys.
- Add SnowflakeExternalPartitionClause + format_external_partition_clause for
  the external-table PARTITION BY (cols) form (no method, no VALUES).
- Update tests to the corrected semantics.

(cherry picked from commit 9dbc506)
(cherry picked from commit f409122)
Introduce SnowflakeCreateTableOptions (deriving the generic CreateTableOptions)
carrying the Snowflake-only TRANSIENT header modifier. The generic
format_create_table_options no longer renders TRANSIENT; Snowflake overrides
it to add TRANSIENT and accepts both the generic and Snowflake options.

(cherry picked from commit bbd0fac)
(cherry picked from commit 8046777)
TRANSIENT now lives on the typed SnowflakeCreateTableOptions.

(cherry picked from commit 05333a8)
(cherry picked from commit 8b8794d)
Add SnowflakeCreateDatabaseExpression (transient/clone) and
SnowflakeInsertExpression (overwrite), replacing dialect_options reads.

(cherry picked from commit bd88851)
(cherry picked from commit ca1b76d)
…uses

(cherry picked from commit d24c51b)
(cherry picked from commit d53f530)
Core DataType no longer carries a dialect_options bag; backend type
subclasses stop forwarding it. Update the type-protocol tests.

(cherry picked from commit f441874)
(cherry picked from commit ce04ee3)
(cherry picked from commit 7cfcc81)
(cherry picked from commit bb90881)
(cherry picked from commit b8aa478)
(cherry picked from commit 777b306)
Core added the ColumnAttributeSupport protocol and renamed the deriver plan
entry to creation_plan(); update protocol conformance and derived-DDL tests.

(cherry picked from commit 9271a8b)
(cherry picked from commit 19fb76f)
Snowflake has no SQL-standard GENERATED ... AS IDENTITY column syntax; render
IDENTITY(start, step) (synonym for AUTOINCREMENT). Column collation must be a
quoted specification (COLLATE 'en-ci'), so override format_column_attribute.

(cherry picked from commit 55cc826)
(cherry picked from commit 7366f44)
(cherry picked from commit e5b92f7)
(cherry picked from commit 56d359f)
… inline

Snowflake natively supports both the inline COMMENT = 'text' table option
and the standalone COMMENT ON statement; declare supports_table_comment() /
supports_column_comment() as True so the capability bits match the actual
rendering behavior. format_table_comment renders the Snowflake table-level
form (COMMENT = 'text'), the column level keeps the bare form.

(cherry picked from commit 304fc44)
(cherry picked from commit 6b9f701)
Advertise supports_comment_on and compose CommentOnMixin so the core
CommentOnExpression renders native COMMENT ON for tables, columns, and other
objects, complementing the inline COMMENT = '...' table option.

(cherry picked from commit 03fa5c9)
(cherry picked from commit e0525fd)
(cherry picked from commit fc1411f)
(cherry picked from commit 6139b3a)
supports_alter_schema* returned True with no AlterSchemaExpression/formatter,
so the switches were dead. Return False until the statement is modelled.
Also type SnowflakeCreateTableOptions.comment as TableCommentClause.

(cherry picked from commit e78ee1d)
(cherry picked from commit 8b57feb)
(cherry picked from commit f0995e1)
(cherry picked from commit 7fb9443)
(cherry picked from commit 6e12818)
(cherry picked from commit e1f7373)
(cherry picked from commit 3492613)
(cherry picked from commit 2e8bd06)
(cherry picked from commit 668e0e7)
(cherry picked from commit a7ebaff)
(cherry picked from commit 911d7cb)
(cherry picked from commit b5bb21a)
(cherry picked from commit e053d00)
(cherry picked from commit 0f1946b)
(cherry picked from commit 6b29659)
(cherry picked from commit ab0d5cb)
…n hierarchy

Snowflake declares supports_materialized_view() = True, but its formatter only
read Snowflake-specific fields (as_query / column_list / name), so passing a
core expression raised:

    AttributeError: 'CreateMaterializedViewExpression' object has no
    attribute 'as_query'

A backend that advertises MV support yet cannot use the generic MV API is
exactly the defect the new backend contract catches
(python-activerecord-testsuite feature/basic/ddl/test_materialized_view_contract.py).

Make SnowflakeCreateMaterializedViewExpression inherit the core
CreateMaterializedViewExpression and normalise the field names (view_name /
query / column_aliases), while keeping the existing Snowflake spellings
name= / as_query= / column_list= as aliases, and or_replace / if_not_exists /
cluster_by / comment as extensions. query now accepts either a QueryExpression
or a raw SQL string.

Also fixes two silent discards:
- TABLESPACE / storage_options do not exist in Snowflake and now raise
  UnsupportedFeatureError instead of being quietly ignored
- or_replace together with if_not_exists is rejected up front, since Snowflake
  forbids the combination, rather than emitting SQL the server will reject

Snowflake has no WITH [NO] DATA (a view is always created empty and filled in
the background), so the inherited with_data is not rendered, and a test pins
that behaviour.

Tests: the 15 existing MV cases stay green; 11 new cases cover generic
expression interop, field aliases, the mutual-exclusion check, the
TABLESPACE/storage_options rejection, and with_data not being rendered.

(cherry picked from commit 26f409f)
(cherry picked from commit cbf9fd9)
(cherry picked from commit 5fdd917)
(cherry picked from commit 7594b33)
(cherry picked from commit 310a77f)
(cherry picked from commit 141309e)
… revisions

The installed hook exec'd scripts/preflight.sh unconditionally. That hook is a
local, untracked artifact, so it survives a checkout of any commit that predates
the script, and on such a commit exec failed with a bare "No such file or
directory" — blocking the commit with nothing to act on.

In this repository scripts/preflight.sh first appears at 33ce3d1, so every earlier
commit is affected. The failure is also silent in a dangerous way: it swallowed
a `git commit --amend`, so a commit-message rewrite reported as applied when it
had not been, and had to be redone.

The hook now checks the script is executable and exits 0 otherwise, so a revision
without the pre-flight simply skips it. Normal operation is unchanged.

Also honours core.hooksPath. git ignores .git/hooks entirely once that is set, so
the hook was being written to a location that would never run while this script
still reported success. No repository sets it today, so that part is latent.
Both dependencies were pinned to feature/activerecord-derived-ddl, a branch
that exists only to carry the derived-DDL work. Now that the core change has
landed in release/v1.0.0.dev30 and the matching testsuite change in
release/v1.0.0.dev17, pinning to the feature branch means CI keeps resolving a
branch that will be deleted once the backend PRs merge.

This is a prerequisite for merging: leaving the old ref in place means the
first run after that branch disappears fails to resolve, not the code.
@vistart vistart self-assigned this Sep 27, 2026
@vistart vistart added the enhancement New feature or request label Sep 27, 2026
@vistart
vistart merged commit 2485875 into release/v1.0.0.dev2 Sep 27, 2026
9 checks passed
@vistart
vistart deleted the feature/activerecord-derived-ddl branch October 1, 2026 03:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant