Skip to content

feat(postgres): expression-based formatters, DDL improvements, protocol compliance - #57

Merged
vistart merged 50 commits into
release/v1.0.0.dev17from
feature/parameter-inline
Sep 18, 2026
Merged

vistart merged 50 commits into
release/v1.0.0.dev17from
feature/parameter-inline

Conversation

@vistart

@vistart vistart commented Sep 16, 2026

Copy link
Copy Markdown
Contributor

Description

Comprehensive PostgreSQL backend overhaul: expression-based formatter system, DDL improvements, protocol compliance, and identifier quoting.

This PR modernizes the PostgreSQL backend to align with the rhosocial-activerecord core's node-based expression system. Key changes include:

  • Expression-based formatters: All format_* methods now accept expression objects instead of raw parameters, following the standardized formatter signature contract
  • DDL improvements: CREATE TABLE enhancements (LIKE, UNLOGGED tables, identity fields, generated columns), identifier quoting with reserved words
  • Protocol compliance: Formalized PostgreSQL type support declarations, AutoIncrement + GeneratedColumn protocol support, RETURNING OLD/NEW for PG 17+
  • ILIKE/Enum/Multirange: New expression classes for PostgreSQL-specific features
  • Breaking changes: CREATE TABLE LIKE migrated to CreateTableLikeExpression, UNLOGGED via typed CreateTableOptions, dialect method renames, removal of duplicate/mixed-in dialect methods

Adds rhosocial-activerecord >=1.0.0.dev29 dependency for expression system.

Type of change

  • feat: A new feature
  • docs: Documentation only changes
  • perf: A code change that improves performance
  • test: Adding missing tests or correcting existing tests
  • chore: Changes to the build process or auxiliary tools
  • ci: Changes to our CI configuration files and scripts
  • build: Changes that affect the build system or external dependencies
  • revert: Reverts a previous commit

Breaking Change

  • Yes

Impact: CreateTableLikeExpression replaces raw CREATE TABLE ... LIKE string formatting. UNLOGGED tables now use typed CreateTableOptions. Dialect methods renamed to Postgres-prefixed convention. Some mixin methods removed (merged into core DQLMixin/ExpressionMixin).

Migration path: Update any direct calls to renamed/removed dialect methods. Use CreateTableLikeExpression for CREATE TABLE ... LIKE. Use CreateTableOptions(unlogged=True) for UNLOGGED tables.

Applicable rhosocial-activerecord Version Range:
>=1.0.0.dev29

Related Repositories/Packages

  • rhosocial-activerecord (core): Requires >=1.0.0.dev29 for expression system changes
  • rhosocial-activerecord-testsuite: No coordinated changes required

Testing

  • I have added new tests to cover my changes.
  • I have updated existing tests to reflect my changes.
  • All existing tests pass. (Local tests skipped per user request)
  • This change has been tested on PostgreSQL 9.6–17.

Test Plan:
Tests were skipped locally. CI/CD will validate against PostgreSQL across Python 3.8–3.14.

Checklist

  • My code follows the project's code style guidelines.
  • I have performed a self-review of my own code.
  • I have commented my code, particularly in hard-to-understand areas.
  • My changes generate no new warnings.
  • I have added a Changelog fragment.
  • I have verified that my changes do not introduce SQL injection vulnerabilities.
  • I have checked for potential performance regressions.

Additional Notes

  • 49 commits spanning expression system migration, DDL improvements, protocol formalization, and CI updates
  • Preflight script and git hook installer added for developer experience
  • Python 3.15.0-rc.2 testing enabled in CI

… format_method

- Backend types carry postgres_-prefixed generic names; dispatch by
  naming convention (format_data_type_<name>); @DDLTypeMixin.handles
  registration removed.
- Formatting functions take (self, expr); the ::type cast syntax is the
  CastExpression formatter.
- Ownership of the expression source tree moves to the core library.
- CI installs rhosocial-activerecord and the testsuite from their active
  branches.
…format_grouping_clause

- Updated imports and usage in recursive_cte_graph_traversal.py (src + tests)
- Updated import in test_property_graph_query_scenarios.py
- Updated protocol conformance exclusion string in test_postgres_protocol_conformance.py
…ion contracts

- 5 type classes declare dialect_options explicitly; unified _type_params
  based equality/hash (hand-written eq/hash retired)
- PostgresEnumType remains BaseExpression (DDL expression, not DataType);
  format_data_type_postgres_enum/supports pair added for future DataType
  subclasses
- 70+ supports_data_type_* added (1:1 with format family);
  supports_data_types() updated to check supports() before including types
- suggested_data_types() returns empty dict (PG handles all core types
  natively; no honest cross-backend suggestion warranted)
- Precision validation: decimal 1-1000/0-precision, float 1-53,
  fractional-seconds 0-6 for time/timestamp types
- parse_type() constructor calls converted to keyword arguments for
  dialect-first signatures; PostgresArrayType/CustomType calls fixed
- New test_postgres_type_protocol.py (24 tests)
- ArrayType(IntegerType(...), dialect=...) → ArrayType(element_type=..., dialect=...)
- DecimalType(10, 2, dialect=...) → DecimalType(precision=10, scale=2, dialect=...)
- TimestampType(6, dialect=...) → TimestampType(fsp=6, dialect=...)
- Hash tests updated: hash(X) == hash(X._type_params())
…ataType convention

Update ColumnDefinition and DataType subclass constructors to use
dialect-first signatures throughout examples:
- ColumnDefinition(dialect, name, type, ...)
- PostgresSerialType(dialect), ArrayType(dialect, ...), IntegerType(dialect), etc.

Aligns with parameter-inline refactor in core library.
- Complete POSTGRES_PROTOCOLS base list with 5 previously-omitted generic
  protocols (AlterTableModifierSupport, DDLTypeSupport, GraphTableSupport,
  ILIKESupport, TriggerSupport).
- Add POSTGRES_NOT_IMPLEMENTED (intentional: generic FunctionSupport
  [Postgres uses PostgresRoutineSupport]; known gaps: AutoIncrementSupport,
  GeneratedColumnSupport — see TODO comments).
- Add negative isinstance test + partition completeness test.
- Whitelist AlterTableModifierSupport/PostgresConstraintSupport and
  ILIKESupport/PostgresILIKESupport overlaps.
Postgres supports SERIAL/IDENTITY and (12+) STORED generated columns but
never declared the generic protocols. Add the missing methods and promote
both protocols from POSTGRES_NOT_IMPLEMENTED to the positive list.
…ion-based signature

Core protocol changed from scatter args to (self, expr). Update backend
overrides and tests to construct expression objects.
- Add backend reserved words and wire format_identifier(need_quote=...)
  through SQLDialectBase, warning when a reserved word is left unquoted
- Rename supports_table_like_syntax to supports_create_table_like
- Add identifier quoting tests
Delete format_identifier, format_commit/rollback_transaction,
format_savepoint/release_savepoint, format_drop_view_statement,
format_drop_materialized_view_statement, supports_generated_columns,
format_truncate_statement, format_refresh_materialized_view_statement,
format_explain_statement — all equivalent to implementations provided
by SQLDialectBase, TransactionControlMixin, ViewMixin, TruncateMixin,
PostgresFeaturesMixin, and ExplainMixin respectively.

dialect.py: 1877 → 1866 (-11)
The dialect methods were removed without moving the behavior into
PostgresExplainMixin/PostgresTruncateMixin, so the generic core
formatters won: EXPLAIN options rendered invalid SQL (COSTS/FORMAT) and
TRUNCATE ignored the 8.4 restart-identity gate. Restore both.
The reverse-coverage conformance test requires the mixin formatters to be
declared on PostgresExplainSupport/PostgresTruncateSupport.
Move 30 methods from dialect.py into 17 mixin files:
- datetime.py, collation.py, dql.py, json.py, generated_column.py,
  expression.py, function.py (new)
- Extended: ilike.py, upsert.py, view.py, materialized_view.py,
  ddl/column.py, ddl/index.py, table.py, ddl/constraint.py,
  dml/locking.py, transaction.py

dialect.py: 1657 → 645 lines (retains __init__, _validate_data_type,
get_parameter_placeholder, get_server_version, create_schema_differ).
…ange expressions

- Add ILikeExpression for format_ilike_expression
- Add EnumTypeNameExpression, EnumValuesExpression
- Add CreateEnumTypeRawExpression, DropEnumTypeRawExpression, AlterEnumAddValueRawExpression
- Add CreateMultirangeTypeStatementExpression, MultirangeAggFunctionExpression
- Update format methods to accept expressions
- Add 16 new tests
Add the reverse-coverage declarations (constraint/index/locking/
materialized-view/transaction/upsert/view) and align the ILIKESupport
format_ilike_expression signature.
The dialect dispatches expression formatters as formatter(expr); a
formatter referenced by format_method must therefore accept exactly
(self, expr). Drop the raw/str hybrid signature on
PostgresILIKEMixin.format_ilike_expression and read column/pattern/
negate from the ILIKEExpression node instead.

Align the PostgresILIKESupport declaration and update the ILIKE tests
to build an ILIKEExpression; exempt the generic ILIKESupport protocol
from the signature-conformance test since its named-parameter
declaration is a pre-existing core signature.
expr.column is an expression node; render it via to_sql().
Drop the raw-string test helper; construct ILIKEExpression with a
Column and dispatch via to_sql().
Route DDL/extension formatter tests through their expression nodes and
to_sql() instead of calling dialect.format_*() with raw values or
pre-built expressions. Convert bloom/btree_gin/btree_gist unit tests to
CreateIndexExpression, enum name/values to EnumTypeNameExpression /
EnumValuesExpression, and inline PartitionValue construction (dropping
the test-local _format_partition_value helper).
The ILIKEExpression constructor requires an expression column; pass
Column(...) instead of a raw string so to_sql() exercises the real
expression/formatter path.
Replace hard-coded %s bind markers in SQL builders with the dialect's
single source of truth: self.p() for dialect mixins and
self.dialect.p()/self._backend.dialect.p() for non-dialect helpers.
…pr type hints

- Mixin methods: return type 'tuple' -> 'Tuple[str, tuple]' for
  format_on_conflict_clause, format_create_view_statement,
  format_create_materialized_view_statement, format_truncate_statement,
  format_explain_statement, format_datetime_diff_expression

- ENUM raw methods: return type 'str' -> 'Tuple[str, tuple]' for
  format_enum_type_name, format_enum_values, format_create_enum_type_raw,
  format_drop_enum_type_raw, format_alter_enum_add_value_raw; convenience
  methods (create/drop/alter_enum_*) extract SQL from tuple

- Protocol layer: add 'Any' type hint to all expr parameters

- Mixin layer: add concrete type hints for expr parameters
  (OnConflictClause, CreateViewExpression, etc.) and rename
  explain_expr -> expr in format_explain_statement

- Eliminate isinstance(Column) in format_on_conflict_clause by using
  getattr(expr_val, 'table', None) == 'EXCLUDED' and unified to_sql()
  for conflict_target
…re change

format_storage_options now accepts StorageOptionsExpression and returns
Tuple[str, tuple] instead of dict and str.
…ssion

- remove the dialect_options['like_table'] block from
  format_create_table_statement and delete the format_create_table_like
  delegate
- add PostgreSQL's format_create_table_like_statement rendering the
  column-list clause form CREATE TABLE t (LIKE src [INCLUDING ...])
  with support for dict/list like_options; space-separated options
  (the old comma-separated form was invalid PostgreSQL)
- protocols + docs + tests updated to CreateTableLikeExpression

BREAKING CHANGE: CREATE TABLE ... LIKE is no longer expressed through
CreateTableExpression(dialect_options={'like_table': ...}); use
CreateTableLikeExpression(dialect, table, like_table, like_options=...).
- remove the dialect_options['unlogged_table'] post-processing; the
  generic renderer now emits CREATE UNLOGGED TABLE gated on
  supports_unlogged_table()
- explicitly reject UNLOGGED combined with TEMPORARY (mutually exclusive)
- tests use CreateTableOptions(unlogged=True)

BREAKING CHANGE: request UNLOGGED via
CreateTableOptions(unlogged=True); the dialect_options key is ignored.
PostgreSQL format_column_definition now reads identity from the typed
ColumnDefinition.identity field, falling back to dialect_options["identity"]
for backward compatibility.
- Add PostgresDatabaseMixin for CREATE/DROP/ALTER DATABASE support
- Add PostgresViewMixin with capability gating (OR REPLACE, IF EXISTS, CASCADE, MATERIALIZED)
- Add test_postgres_ddl_improvements.py with 11 tests
- Rename view.py -> ddl_view.py
… on PG < 8.4

- PostgreSQL truncate mixin: raise UnsupportedFeatureError when RESTART IDENTITY
  is requested on versions < 8.4 (was silently skipping before)
- Updated test to expect UnsupportedFeatureError instead of silent skip
- PostgresReturningMixin.supports_returning_old_new() returns True on 17+
- Add tests for OLD/NEW validation and capability defaults
- PostgresReturningSupport: add supports_returning_old_new
- PostgresViewSupport: add supports_create_or_replace_view / supports_if_not_exists_view
- Classify DatabaseSupport in protocol conformance
…core; nulls ordering

- Remove PG LockStrength; use core LockStrength via clause.strength (no dialect_options)
- Remove supports_fetch_with_ties (now core default = PG 13+)
- Add supports_nulls_first_last
- Update examples/tests
@codecov

codecov Bot commented Sep 16, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 86.17100% with 186 lines in your changes missing coverage. Please review.
✅ Project coverage is 77.31%. Comparing base (6779f08) to head (cad7681).
⚠️ Report is 1 commits behind head on release/v1.0.0.dev17.

Files with missing lines Patch % Lines
...ord/backend/impl/postgres/mixins/ddl/constraint.py 31.57% 31 Missing and 8 partials ⚠️
.../backend/impl/postgres/mixins/materialized_view.py 5.00% 19 Missing ⚠️
...verecord/backend/impl/postgres/mixins/ddl_table.py 80.23% 10 Missing and 7 partials ⚠️
...ctiverecord/backend/impl/postgres/mixins/upsert.py 55.26% 11 Missing and 6 partials ⚠️
...iverecord/backend/impl/postgres/mixins/function.py 80.59% 6 Missing and 7 partials ⚠️
...impl/postgres/mixins/types/data_type_formatting.py 95.93% 9 Missing and 4 partials ⚠️
...iverecord/backend/impl/postgres/mixins/ddl_view.py 76.74% 6 Missing and 4 partials ⚠️
...cord/backend/impl/postgres/protocols/types/enum.py 64.70% 6 Missing ⚠️
...record/backend/impl/postgres/mixins/transaction.py 89.36% 1 Missing and 4 partials ⚠️
...record/backend/impl/postgres/mixins/dml/locking.py 73.33% 2 Missing and 2 partials ⚠️
... and 23 more
Additional details and impacted files
@@                   Coverage Diff                    @@
##           release/v1.0.0.dev17      #57      +/-   ##
========================================================
+ Coverage                 77.15%   77.31%   +0.15%     
========================================================
  Files                       306      314       +8     
  Lines                     12530    12884     +354     
  Branches                   2033     2042       +9     
========================================================
+ Hits                       9668     9961     +293     
- Misses                     2171     2212      +41     
- Partials                    691      711      +20     
Flag Coverage Δ
postgres-18 77.31% <86.17%> (+0.15%) ⬆️
python-3.14 77.31% <86.17%> (+0.15%) ⬆️
unittests 77.31% <86.17%> (+0.15%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@sonarqubecloud

Copy link
Copy Markdown

@vistart vistart self-assigned this Sep 17, 2026
@vistart vistart added enhancement New feature or request and removed feature labels Sep 18, 2026
@vistart
vistart merged commit ef1a19d into release/v1.0.0.dev17 Sep 18, 2026
17 checks passed
vistart added a commit that referenced this pull request Sep 24, 2026
…ol compliance (#57)

* feat(postgres): adapt to node-based expression system and declarative format_method

- Backend types carry postgres_-prefixed generic names; dispatch by
  naming convention (format_data_type_<name>); @DDLTypeMixin.handles
  registration removed.
- Formatting functions take (self, expr); the ::type cast syntax is the
  CastExpression formatter.
- Ownership of the expression source tree moves to the core library.
- CI installs rhosocial-activerecord and the testsuite from their active
  branches.

* ci: test against Python 3.15.0-rc.2

* fix: Rename JoinExpression→JoinClause and format_grouping_expression→format_grouping_clause

- Updated imports and usage in recursive_cte_graph_traversal.py (src + tests)
- Updated import in test_property_graph_query_scenarios.py
- Updated protocol conformance exclusion string in test_postgres_protocol_conformance.py

* feat(dialect-protocol): formalize PostgreSQL type support and suggestion contracts

- 5 type classes declare dialect_options explicitly; unified _type_params
  based equality/hash (hand-written eq/hash retired)
- PostgresEnumType remains BaseExpression (DDL expression, not DataType);
  format_data_type_postgres_enum/supports pair added for future DataType
  subclasses
- 70+ supports_data_type_* added (1:1 with format family);
  supports_data_types() updated to check supports() before including types
- suggested_data_types() returns empty dict (PG handles all core types
  natively; no honest cross-backend suggestion warranted)
- Precision validation: decimal 1-1000/0-precision, float 1-53,
  fractional-seconds 0-6 for time/timestamp types
- parse_type() constructor calls converted to keyword arguments for
  dialect-first signatures; PostgresArrayType/CustomType calls fixed
- New test_postgres_type_protocol.py (24 tests)

* fix(test): VarCharType dialect-first call sites in fixtures and examples

* fix(test): ArrayType/DecimalType/TimestampType dialect-first call sites

- ArrayType(IntegerType(...), dialect=...) → ArrayType(element_type=..., dialect=...)
- DecimalType(10, 2, dialect=...) → DecimalType(precision=10, scale=2, dialect=...)
- TimestampType(6, dialect=...) → TimestampType(fsp=6, dialect=...)
- Hash tests updated: hash(X) == hash(X._type_params())

* fix(test): TimestampType precision keyword, ArrayType element_type keyword

* fix(test): hash tests use (type, _type_params) tuple

* fix(test): ArrayType dimensions keyword in test_core_type_mappings

* fix(examples): adapt example code to dialect-first ColumnDefinition/DataType convention

Update ColumnDefinition and DataType subclass constructors to use
dialect-first signatures throughout examples:
- ColumnDefinition(dialect, name, type, ...)
- PostgresSerialType(dialect), ArrayType(dialect, ...), IntegerType(dialect), etc.

Aligns with parameter-inline refactor in core library.

* test(protocol): add negative protocol list + partition completeness

- Complete POSTGRES_PROTOCOLS base list with 5 previously-omitted generic
  protocols (AlterTableModifierSupport, DDLTypeSupport, GraphTableSupport,
  ILIKESupport, TriggerSupport).
- Add POSTGRES_NOT_IMPLEMENTED (intentional: generic FunctionSupport
  [Postgres uses PostgresRoutineSupport]; known gaps: AutoIncrementSupport,
  GeneratedColumnSupport — see TODO comments).
- Add negative isinstance test + partition completeness test.
- Whitelist AlterTableModifierSupport/PostgresConstraintSupport and
  ILIKESupport/PostgresILIKESupport overlaps.

* feat(dialect): declare AutoIncrement + GeneratedColumn protocol support

Postgres supports SERIAL/IDENTITY and (12+) STORED generated columns but
never declared the generic protocols. Add the missing methods and promote
both protocols from POSTGRES_NOT_IMPLEMENTED to the positive list.

* fix(dialect): adapt format_fulltext_match/array_expression to expression-based signature

Core protocol changed from scatter args to (self, expr). Update backend
overrides and tests to construct expression objects.

* feat(identifier): add reserved words and opt-out identifier quoting

- Add backend reserved words and wire format_identifier(need_quote=...)
  through SQLDialectBase, warning when a reserved word is left unquoted
- Rename supports_table_like_syntax to supports_create_table_like
- Add identifier quoting tests

* refactor(identifier): remove IdentifierMixin and fix format_column with _need_quote

* refactor(postgres): remove 11 duplicate dialect methods

Delete format_identifier, format_commit/rollback_transaction,
format_savepoint/release_savepoint, format_drop_view_statement,
format_drop_materialized_view_statement, supports_generated_columns,
format_truncate_statement, format_refresh_materialized_view_statement,
format_explain_statement — all equivalent to implementations provided
by SQLDialectBase, TransactionControlMixin, ViewMixin, TruncateMixin,
PostgresFeaturesMixin, and ExplainMixin respectively.

dialect.py: 1877 → 1866 (-11)

* fix(mixins): restore Postgres EXPLAIN and TRUNCATE formatters

The dialect methods were removed without moving the behavior into
PostgresExplainMixin/PostgresTruncateMixin, so the generic core
formatters won: EXPLAIN options rendered invalid SQL (COSTS/FORMAT) and
TRUNCATE ignored the 8.4 restart-identity gate. Restore both.

* fix(protocols): declare Postgres EXPLAIN/TRUNCATE formatters

The reverse-coverage conformance test requires the mixin formatters to be
declared on PostgresExplainSupport/PostgresTruncateSupport.

* refactor(postgres): split dialect methods into feature mixins

Move 30 methods from dialect.py into 17 mixin files:
- datetime.py, collation.py, dql.py, json.py, generated_column.py,
  expression.py, function.py (new)
- Extended: ilike.py, upsert.py, view.py, materialized_view.py,
  ddl/column.py, ddl/index.py, table.py, ddl/constraint.py,
  dml/locking.py, transaction.py

dialect.py: 1657 → 645 lines (retains __init__, _validate_data_type,
get_parameter_placeholder, get_server_version, create_schema_differ).

* feat(postgres): format_* signature compliance - ILIKE + Enum + Multirange expressions

- Add ILikeExpression for format_ilike_expression
- Add EnumTypeNameExpression, EnumValuesExpression
- Add CreateEnumTypeRawExpression, DropEnumTypeRawExpression, AlterEnumAddValueRawExpression
- Add CreateMultirangeTypeStatementExpression, MultirangeAggFunctionExpression
- Update format methods to accept expressions
- Add 16 new tests

* chore: resolve merge conflict in expression types

* fix(postgres): restore types.py accidentally emptied during conflict resolution

* fix(postgres): fix expression format_method to match mixin method names

* fix(postgres): fix format_method for EnumTypeNameExpression and EnumValuesExpression

* fix(postgres): add missing methods to protocols for expression compliance

* fix(postgres): declare mixin formatters on protocols

Add the reverse-coverage declarations (constraint/index/locking/
materialized-view/transaction/upsert/view) and align the ILIKESupport
format_ilike_expression signature.

* fix(postgres): convert format_ilike_expression to (self, expr)

The dialect dispatches expression formatters as formatter(expr); a
formatter referenced by format_method must therefore accept exactly
(self, expr). Drop the raw/str hybrid signature on
PostgresILIKEMixin.format_ilike_expression and read column/pattern/
negate from the ILIKEExpression node instead.

Align the PostgresILIKESupport declaration and update the ILIKE tests
to build an ILIKEExpression; exempt the generic ILIKESupport protocol
from the signature-conformance test since its named-parameter
declaration is a pre-existing core signature.

* refactor(postgres): drop str fallback in ILIKE column

expr.column is an expression node; render it via to_sql().

* test(postgres): build ILIKE expressions with Column nodes

Drop the raw-string test helper; construct ILIKEExpression with a
Column and dispatch via to_sql().

* test(postgres): exercise formatter tests via expression to_sql()

Route DDL/extension formatter tests through their expression nodes and
to_sql() instead of calling dialect.format_*() with raw values or
pre-built expressions. Convert bloom/btree_gin/btree_gist unit tests to
CreateIndexExpression, enum name/values to EnumTypeNameExpression /
EnumValuesExpression, and inline PartitionValue construction (dropping
the test-local _format_partition_value helper).

* test(postgres): build ILIKE expression tests with Column nodes

The ILIKEExpression constructor requires an expression column; pass
Column(...) instead of a raw string so to_sql() exercises the real
expression/formatter path.

* refactor(postgres): emit bind markers via dialect placeholder

Replace hard-coded %s bind markers in SQL builders with the dialect's
single source of truth: self.p() for dialect mixins and
self.dialect.p()/self._backend.dialect.p() for non-dialect helpers.

* refactor: unify format_* return types to Tuple[str, tuple] and add expr type hints

- Mixin methods: return type 'tuple' -> 'Tuple[str, tuple]' for
  format_on_conflict_clause, format_create_view_statement,
  format_create_materialized_view_statement, format_truncate_statement,
  format_explain_statement, format_datetime_diff_expression

- ENUM raw methods: return type 'str' -> 'Tuple[str, tuple]' for
  format_enum_type_name, format_enum_values, format_create_enum_type_raw,
  format_drop_enum_type_raw, format_alter_enum_add_value_raw; convenience
  methods (create/drop/alter_enum_*) extract SQL from tuple

- Protocol layer: add 'Any' type hint to all expr parameters

- Mixin layer: add concrete type hints for expr parameters
  (OnConflictClause, CreateViewExpression, etc.) and rename
  explain_expr -> expr in format_explain_statement

- Eliminate isinstance(Column) in format_on_conflict_clause by using
  getattr(expr_val, 'table', None) == 'EXCLUDED' and unified to_sql()
  for conflict_target

* chore: fix .gitignore for .claude/plan*/

* fix: update storage_options test for StorageOptionsExpression signature change

format_storage_options now accepts StorageOptionsExpression and returns
Tuple[str, tuple] instead of dict and str.

* refactor(ddl)!: migrate CREATE TABLE ... LIKE to CreateTableLikeExpression

- remove the dialect_options['like_table'] block from
  format_create_table_statement and delete the format_create_table_like
  delegate
- add PostgreSQL's format_create_table_like_statement rendering the
  column-list clause form CREATE TABLE t (LIKE src [INCLUDING ...])
  with support for dict/list like_options; space-separated options
  (the old comma-separated form was invalid PostgreSQL)
- protocols + docs + tests updated to CreateTableLikeExpression

BREAKING CHANGE: CREATE TABLE ... LIKE is no longer expressed through
CreateTableExpression(dialect_options={'like_table': ...}); use
CreateTableLikeExpression(dialect, table, like_table, like_options=...).

* refactor(ddl)!: UNLOGGED via typed CreateTableOptions

- remove the dialect_options['unlogged_table'] post-processing; the
  generic renderer now emits CREATE UNLOGGED TABLE gated on
  supports_unlogged_table()
- explicitly reject UNLOGGED combined with TEMPORARY (mutually exclusive)
- tests use CreateTableOptions(unlogged=True)

BREAKING CHANGE: request UNLOGGED via
CreateTableOptions(unlogged=True); the dialect_options key is ignored.

* feat(ddl): prefer typed identity field over dialect_options

PostgreSQL format_column_definition now reads identity from the typed
ColumnDefinition.identity field, falling back to dialect_options["identity"]
for backward compatibility.

* refactor(ddl): rename table.py → ddl_table.py in mixins

* feat: add PostgreSQL DDL improvements with capability gating

- Add PostgresDatabaseMixin for CREATE/DROP/ALTER DATABASE support
- Add PostgresViewMixin with capability gating (OR REPLACE, IF EXISTS, CASCADE, MATERIALIZED)
- Add test_postgres_ddl_improvements.py with 11 tests
- Rename view.py -> ddl_view.py

* fix: B01 TRUNCATE RESTART IDENTITY now raises UnsupportedFeatureError on PG < 8.4

- PostgreSQL truncate mixin: raise UnsupportedFeatureError when RESTART IDENTITY
  is requested on versions < 8.4 (was silently skipping before)
- Updated test to expect UnsupportedFeatureError instead of silent skip

* refactor: remove ReturningMixin import (merged into DMLMixin in core)

* feat: add supports_returning_old_new for PostgreSQL 17+ OLD/NEW refs

- PostgresReturningMixin.supports_returning_old_new() returns True on 17+
- Add tests for OLD/NEW validation and capability defaults

* fix: declare new RETURNING/view capability methods in Postgres protocols

- PostgresReturningSupport: add supports_returning_old_new
- PostgresViewSupport: add supports_create_or_replace_view / supports_if_not_exists_view
- Classify DatabaseSupport in protocol conformance

* refactor(dql): typed lock strength; lift supports_fetch_with_ties to core; nulls ordering

- Remove PG LockStrength; use core LockStrength via clause.strength (no dialect_options)
- Remove supports_fetch_with_ties (now core default = PG 13+)
- Add supports_nulls_first_last
- Update examples/tests

* refactor: remove core DQL/Expression mixin references (merged into DQLMixin/ExpressionMixin)

* feat(ddl): render generated columns via core GeneratedColumnExpression

* chore: add scripts/preflight.sh and optional git hook installer

* chore(ci): update rhosocial-activerecord install source to release/v1.0.0.dev30

* chore: add changelog fragment for PR #57
@vistart
vistart deleted the feature/parameter-inline branch October 1, 2026 03:51
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