Skip to content
79 changes: 64 additions & 15 deletions .claude/backend_development.md
Original file line number Diff line number Diff line change
Expand Up @@ -273,31 +273,80 @@ Every dialect inherits from:
|----------|-------|-------------|
| `WindowFunctionSupport` | `WindowFunctionMixin` | Window functions (OVER, PARTITION BY) |
| `CTESupport` | `CTEMixin` | Common Table Expressions (WITH clause) |
| `AdvancedGroupingSupport` | `AdvancedGroupingMixin` | ROLLUP, CUBE, GROUPING SETS |
| `ReturningSupport` | `ReturningMixin` | RETURNING clause |
| `AdvancedGroupingSupport` | `DQLMixin` | ROLLUP, CUBE, GROUPING SETS |
| `ReturningSupport` | `DMLMixin` | RETURNING clause |
| `UpsertSupport` | `UpsertMixin` | UPSERT operations (ON CONFLICT) |
| `LateralJoinSupport` | `LateralJoinMixin` | LATERAL joins |
| `ArraySupport` | `ArrayMixin` | Array types and operations |
| `JSONSupport` | `JSONMixin` | JSON types and operations |
| `ExplainSupport` | `ExplainMixin` | EXPLAIN statement |
| `FilterClauseSupport` | `FilterClauseMixin` | FILTER clause for aggregates |
| `OrderedSetAggregationSupport` | `OrderedSetAggregationMixin` | WITHIN GROUP (ORDER BY) |
| `FilterClauseSupport` | `ExpressionMixin` | FILTER clause for aggregates |
| `OrderedSetAggregationSupport` | `ExpressionMixin` | WITHIN GROUP (ORDER BY) |
| `MergeSupport` | `MergeMixin` | MERGE statement |
| `TemporalTableSupport` | `TemporalTableMixin` | FOR SYSTEM_TIME queries |
| `QualifyClauseSupport` | `QualifyClauseMixin` | QUALIFY clause |
| `LockingSupport` | `LockingMixin` | FOR UPDATE, SKIP LOCKED |
| `QualifyClauseSupport` | `DQLMixin` | QUALIFY clause |
| `LockingSupport` | `DQLMixin` | FOR UPDATE, SKIP LOCKED |
| `GraphSupport` | `GraphMixin` | Graph queries (MATCH) |
| `JoinSupport` | `JoinMixin` | JOIN operations |
| `SetOperationSupport` | `SetOperationMixin` | UNION, INTERSECT, EXCEPT |
| `ILIKESupport` | `ILIKEMixin` | Case-insensitive LIKE |
| `TableSupport` | `TableMixin` | CREATE/DROP/ALTER TABLE |
| `ViewSupport` | `ViewMixin` | CREATE/DROP VIEW |
| `CreateTableSupport` | `TableMixin` | CREATE TABLE |
| `DropTableSupport` | `TableMixin` | DROP TABLE |
| `AlterTableSupport` | `TableMixin` | ALTER TABLE |
| `CreateViewSupport` | `ViewMixin` | CREATE VIEW |
| `DropViewSupport` | `ViewMixin` | DROP VIEW |
| `MaterializedViewSupport` | `ViewMixin` | CREATE / DROP / REFRESH MATERIALIZED VIEW |
| `TruncateSupport` | `TruncateMixin` | TRUNCATE TABLE |
| `SchemaSupport` | `SchemaMixin` | CREATE/DROP SCHEMA |
| `IndexSupport` | `IndexMixin` | CREATE/DROP INDEX |
| `SequenceSupport` | `SequenceMixin` | CREATE/DROP/ALTER SEQUENCE |
| `TriggerSupport` | `TriggerMixin` | CREATE/DROP TRIGGER (SQL:1999) |
| `FunctionSupport` | `FunctionMixin` | CREATE/DROP FUNCTION (SQL/PSM) |
| `CreateIndexSupport` | `IndexMixin` | CREATE INDEX |
| `DropIndexSupport` | `IndexMixin` | DROP INDEX |
| `FulltextIndexSupport` | `IndexMixin` | CREATE / DROP FULLTEXT INDEX |
| `CreateSequenceSupport` | `SequenceMixin` | CREATE SEQUENCE |
| `DropSequenceSupport` | `SequenceMixin` | DROP SEQUENCE |
| `AlterSequenceSupport` | `SequenceMixin` | ALTER SEQUENCE |
| `CreateTriggerSupport` | `TriggerMixin` | CREATE TRIGGER (SQL:1999) |
| `DropTriggerSupport` | `TriggerMixin` | DROP TRIGGER |
| `CreateRoutineSupport` | `FunctionMixin` | CREATE FUNCTION (SQL/PSM) |
| `DropRoutineSupport` | `FunctionMixin` | DROP FUNCTION |
| `CreateSchemaSupport` | `SchemaMixin` | CREATE SCHEMA |
| `DropSchemaSupport` | `SchemaMixin` | DROP SCHEMA |
| `CreateDatabaseSupport` | `DatabaseMixin` | CREATE DATABASE |
| `DropDatabaseSupport` | `DatabaseMixin` | DROP DATABASE |
| `AlterDatabaseSupport` | `DatabaseMixin` | ALTER DATABASE |

##### Naming is its own protocol

Naming was split out of the DDL protocols above, because "does the engine have
schemas" and "may a name be qualified with one" are different questions with
different owners and an engine can answer them differently.

| Protocol | Mixin | Description |
|----------|-------|-------------|
| `NamespaceSupport` | `NamespaceMixin` | Whether a name may be qualified, and how it is spelled |
| `TableObjectSupport` | `TableNameMixin` | Render a `Table` as its name |
| `ViewObjectSupport` | `ViewNameMixin` | Render a `View` |
| `MaterializedViewObjectSupport` | `MaterializedViewNameMixin` | Render a `MaterializedView` |
| `ForeignTableObjectSupport` | `ForeignTableNameMixin` | Render a `ForeignTable` |
| `IndexObjectSupport` | `IndexNameMixin` | Render an `Index` |
| `SequenceObjectSupport` | `SequenceNameMixin` | Render a `Sequence` |
| `TriggerObjectSupport` | `TriggerNameMixin` | Render a `Trigger` |
| `TypeObjectSupport` | `TypeNameMixin` | Render a `Type` |
| `RoutineObjectSupport` | `FunctionNameMixin` / `ProcedureNameMixin` | Render a `Function` or `Procedure` |

`NamespaceSupport` declares two halves that used to be one method:

* `validate_namespace(expr)` -- the check. Returns `None` or raises
`UnsupportedFeatureError`. It is called while *rendering*, not while
constructing, because at construction the dialect may not be settled and the
object's slots may not be complete.
* `format_qualified_name(expr)` -- the spelling. Builds the qualified name and
joins the levels with `self.separator` (a class attribute, `"."` by default).

A dialect's own answers go in **at most one** naming-side mixin, `<Backend>
NamespaceMixin`, holding only what differs from core's default. Backends that mix
in core's `NamespaceMixin` unchanged put it after the `*NameMixin` block in their
base list — those subclasses it, and a subclass precedes its base, so anything
listed first wins the MRO lookup. That ordering is load-bearing: getting it wrong
produces a dialect that answers a question from the wrong class, silently.

##### Principles for Adding New Protocols/Mixins

Expand All @@ -315,8 +364,8 @@ Every dialect inherits from:

| Feature | SQL Standard? | Location |
|---------|---------------|----------|
| `CREATE TRIGGER` | Yes (SQL:1999) | Main Package (`TriggerSupport`) |
| `CREATE FUNCTION` | Yes (SQL/PSM) | Main Package (`FunctionSupport`) |
| `CREATE TRIGGER` | Yes (SQL:1999) | Main Package (`CreateTriggerSupport`) |
| `CREATE FUNCTION` | Yes (SQL/PSM) | Main Package (`CreateRoutineSupport`) |
| `COMMENT ON` | No (PostgreSQL/Oracle) | PostgreSQL Extension |
| `CREATE TYPE ... AS ENUM` | No (PostgreSQL-specific) | PostgreSQL Extension |
| `AUTO_INCREMENT` | No (MySQL-specific) | MySQL Extension |
Expand Down
8 changes: 4 additions & 4 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ jobs:
- name: Install activerecord and testsuite
run: |
pip install "rhosocial-activerecord @ git+https://github.com/rhosocial/python-activerecord.git@release/v1.0.0.dev30"
pip install "rhosocial-activerecord-testsuite @ git+https://github.com/rhosocial/python-activerecord-testsuite.git@release/v1.0.0.dev17"
pip install "rhosocial-activerecord-testsuite @ git+https://github.com/rhosocial/python-activerecord-testsuite.git@feature/expression-schema-object-refactor"

- name: Create postgres scenario config
run: |
Expand Down Expand Up @@ -280,7 +280,7 @@ jobs:
- name: Install activerecord and testsuite
run: |
pip install "rhosocial-activerecord @ git+https://github.com/rhosocial/python-activerecord.git@release/v1.0.0.dev30"
pip install "rhosocial-activerecord-testsuite @ git+https://github.com/rhosocial/python-activerecord-testsuite.git@release/v1.0.0.dev17"
pip install "rhosocial-activerecord-testsuite @ git+https://github.com/rhosocial/python-activerecord-testsuite.git@feature/expression-schema-object-refactor"

- name: Create postgres scenario config
run: |
Expand Down Expand Up @@ -392,7 +392,7 @@ jobs:
- name: Install activerecord and testsuite
run: |
pip install "rhosocial-activerecord @ git+https://github.com/rhosocial/python-activerecord.git@release/v1.0.0.dev30"
pip install "rhosocial-activerecord-testsuite @ git+https://github.com/rhosocial/python-activerecord-testsuite.git@release/v1.0.0.dev17"
pip install "rhosocial-activerecord-testsuite @ git+https://github.com/rhosocial/python-activerecord-testsuite.git@feature/expression-schema-object-refactor"

- name: Create postgres scenario config
run: |
Expand Down Expand Up @@ -490,7 +490,7 @@ jobs:
- name: Install activerecord and testsuite
run: |
pip install "rhosocial-activerecord @ git+https://github.com/rhosocial/python-activerecord.git@release/v1.0.0.dev30"
pip install "rhosocial-activerecord-testsuite @ git+https://github.com/rhosocial/python-activerecord-testsuite.git@release/v1.0.0.dev17"
pip install "rhosocial-activerecord-testsuite @ git+https://github.com/rhosocial/python-activerecord-testsuite.git@feature/expression-schema-object-refactor"

- name: Create postgres scenario config
run: |
Expand Down
1 change: 1 addition & 0 deletions changelog.d/61.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add the PostgreSQL capability declarations the expression/dialect split asks the backend to state: `PostgresIdentityColumnMixin`/`PostgresIdentitySupport` declare SQL-standard identity columns from PostgreSQL 10 on with one probe per generation mode and sequence option (`ALWAYS`, `START`, `INCREMENT`, `MINVALUE`, `MAXVALUE`, `CYCLE`, `CACHE`) and route the legacy `is_auto_increment` column constraint through the gated formatter, `PostgresAutoIncrementMixin`/`PostgresAutoIncrementSupport` answer the bare `AUTO_INCREMENT` marker `False` explicitly, the sequence probes now cover every `CREATE`/`ALTER SEQUENCE` option including `ALTER SEQUENCE ... START WITH`, the newly measured restrict and cache probes (`supports_schema_restrict`, `supports_restrict_view`, `supports_materialized_view_restrict`, `supports_truncate_restrict`, `supports_drop_function_restrict`, `supports_identity_cache`) and the routine probes are declared, and the master probes (`supports_materialized_cte`, `supports_truncate`, `supports_with_data_clause`, `supports_transaction_wait`) let core's formatters render `WITH [NO] DATA` on CTAS, `CREATE MATERIALIZED VIEW` and `REFRESH MATERIALIZED VIEW`; the naming side is stated once in the new `PostgresNamespaceMixin`/`PostgresNamespaceSupport`, which spells `[database.][schema.]name` level by level.
1 change: 1 addition & 0 deletions changelog.d/61.changed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Change what PostgreSQL renders when no spelling was requested and how unexpressible spellings fail: `CREATE MATERIALIZED VIEW` with neither `with_data` nor `no_data` set no longer appends `WITH NO DATA`, `SET TRANSACTION` with neither deferrability parameter set no longer appends `NOT DEFERRABLE`, `BEGIN ... DEFERRABLE` renders for any isolation level instead of only alongside `SERIALIZABLE`, `WAIT`/`NO WAIT` on `BEGIN`/`SET TRANSACTION` and `NO CACHE` on sequences and identity columns now raise `UnsupportedFeatureError` naming the option instead of being silently dropped or rendered, and a PostgreSQL 9 server asked for an identity column is refused instead of being handed `GENERATED ... AS IDENTITY` (the `SERIAL` type family remains the explicit pre-10 choice).
1 change: 1 addition & 0 deletions changelog.d/61.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix the clauses the pre-split formatters dropped or could not reach: `TRUNCATE ... CONTINUE IDENTITY` and `TRUNCATE ... RESTRICT`, `DROP MATERIALIZED VIEW ... RESTRICT`, `COPY ... FORCE_ARRAY FALSE`, table-constraint `ENFORCED`/`NOT ENFORCED` (the tri-state gate refused every state) and the `EXCLUDE` constraint's independent `DEFERRABLE`/`INITIALLY ...` attributes now render, the transaction manager's `set_deferrable(False)` renders `NOT DEFERRABLE` instead of raising, `CreateFunctionExpression`/`DropFunctionExpression` render through the core formatters instead of resolving to a protocol stub, and the six type names that were never module attributes (`VECTOR`, `HALFVEC`, `BITVEC`, `SPARSEVEC`, `GEOMETRY`, `GEOGRAPHY`) were removed from `type_values.__all__`, where they broke star imports.
1 change: 1 addition & 0 deletions changelog.d/61.removed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
**BREAKING**: Remove `PostgresDialect.supports_auto_increment()` (ask `supports_identity_column()` or `supports_auto_increment_column()` instead), reshape the PostgreSQL DDL expressions to carry schema objects (`PostgresCreateMaterializedViewExpression`, `PostgresDropMaterializedViewExpression`, `PostgresRefreshMaterializedViewExpression` and `PostgresAlterMaterializedViewExpression` take a `MaterializedView` instead of `view_name`/`name` plus `schema`, `PostgresCreateIndexExpression` takes `index` and `table` instead of `index_name` and `table_name`, `PostgresDropTypeExpression` takes `type` instead of `type_name` plus `schema_name`, and `PostgresCommentExpression` takes `object` instead of `object_name` plus `schema`), replace their tri-state options with one parameter per spelling (`with_data`/`no_data` on `PostgresCreateMaterializedViewExpression` and `PostgresRefreshMaterializedViewExpression`, `force_array`/`force_array_false` on `PostgresCopyToExpression`, and `deferrable`/`not_deferrable`, `initially_deferred`/`initially_immediate`, `enforced`/`not_enforced` on `PostgresExcludeConstraint`), and return `(sql, params)` from `format_materialized_view_alter_action()` instead of a bare string; setting both sides of a pair raises `ValueError` at construction.
Loading
Loading