Skip to content

feat(oracle): adapt to derived DDL with typed options and partition lifecycle - #5

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

vistart merged 36 commits into
release/v1.0.0.dev3from
feature/activerecord-derived-ddl

Conversation

@vistart

@vistart vistart commented Sep 27, 2026 •

Copy link
Copy Markdown
Contributor

Description

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

Several changes here stop the backend from discarding what the application asked for, or
from over-claiming what it can render:

  • supports_temporal_tables is declared as False rather than left unstated, and
    ALTER SCHEMA capabilities that cannot actually be rendered are no longer advertised.
  • CREATE VIEW ... WITH CHECK OPTION is gated on supports_view_check_option.
  • supports_comment is renamed to supports_comment_on, and declared table and column
    comments are no longer silently dropped.
  • DROP TYPE no longer applies a default VALIDATE, and DROP TABLE ... PURGE renders
    independently of CASCADE.

Typed options replace the reserved dialect_options bag: OracleColumnDefinition with
typed column options, a partition lifecycle provider with the provider protocol declared,
and partition definitions derived from the generic base declarations with the legacy
partition bag paths replaced by typed dispatch. The PURGE flag is read from typed options
rather than passed through.

Also included: type DDL support, DROP TABLE ... PURGE rendering, CommentSupport
classification for conformance, index drop for a bare index name without ON <table>, a
partition lifecycle provider, the OracleDatabaseMixin wiring, 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. Declared table and column comments now raise instead of
being silently dropped, and features the backend cannot render are no longer advertised as
supported — so code that relied on either will now fail rather than produce a schema that
differs from the request.

Impact on Backend Developers (Custom Implementations):

  • supports_comment is renamed to supports_comment_on. A conformance suite asserting the
    old name will fail to resolve it.
  • The reserved dialect_options bag is removed from data-type value objects, DDL
    expressions, the insert/merge formatters and OracleForUpdateClause. Partition
    definitions dispatch through typed options instead of the legacy bag paths.
  • Column classes drop the legacy identity fields in favour of the typed comment clause nodes.
  • The generated DDL lifecycle is removed.
  • TYPE_CHECKING-only imports that pointed at modules moved by the expression namespace split
    were repaired so type checkers resolve them.

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, which is a
prerequisite for merging: the dependencies were pinned to
feature/activerecord-derived-ddl, a branch that only exists to carry this work and will be
deleted once the backends are merged.

Merged or in flight: mysql #56, postgres #58, mariadb #5, sqlserver #4. Still to merge:
clickhouse, firebird, snowflake, 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 0870faf, which also covers Oracle 18c, 21c and 23c — the first
execution of this repository 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

feat(view): support Oracle materialized view refresh via DBMS_MVIEW.REFRESH is included
because it is the same derived-DDL work: the refresh path is expressed through the core
expression hierarchy rather than a dialect-specific form.

…arations

OraclePartitionDefinition / OracleSubpartitionDefinition now derive from the
generic PartitionDefinition / SubpartitionDefinition base declarations,
keeping Oracle's boundary fields (less_than / in_values) and its rule that
boundaries may be omitted (HASH / composite subpartitioning).
Introduce the Oracle-specific column definition (and options) derived from
the generic ColumnDefinition / ColumnOptions, carrying the typed INVISIBLE
attribute with no dialect_options bag. The Oracle format_column_definition
accepts both the generic and Oracle column definitions and renders the
Oracle-only attribute. Export the new classes and cover them with tests.
The typed DropTableExpression.purge field replaces the dialect_options bag.
Remove the unused/reserved dialect_options parameter, attribute and doc
from Oracle DDL/statement expression classes (ALTER TABLE actions,
ANALYZE, COMMENT, sequence, trigger, flashback, materialized view,
synonym, database link, routine, partition reference, table clauses).
The data-type family (expression/types.py) and ForUpdateClause keep their
own dialect_options.
…ateClause

Core ForUpdateClause no longer carries a dialect_options bag.
Core DataType no longer carries a dialect_options bag; backend type
subclasses stop forwarding it. Update the type-protocol tests.
Oracle has no SQL:2011 system-versioned temporal tables and no temporal
DDL expression is implemented; the previous True was an unbacked claim.
…k_option

The view formatter rendered WITH CHECK OPTION unconditionally, leaving the
capability bit inert.
Remove identity/identity_start/identity_increment/identity_clause from
XxxColumnDefinition and XxxColumnOptions to match core's column-attribute
migration; XxxColumnDefinition now forwards attributes.
Render the dialect-free column attributes channel in each backend's
format_column_definition (it was silently dropped), and give backends whose
identity syntax differs from the SQL-standard form a format_identity_clause
override (MySQL/MariaDB AUTO_INCREMENT, SQL Server IDENTITY(seed, inc)).
Oracle gates column COLLATE to 12.2+; MariaDB drops the duplicate dialect
formatter so the attribute-aware mixin is authoritative.
Core added the ColumnAttributeSupport protocol and renamed the deriver plan
entry to creation_plan(); update protocol conformance and derived-DDL tests.
Emit PURGE whether or not CASCADE CONSTRAINTS is present (DROP TABLE t PURGE
is valid Oracle), and pin it with a dialect test.
…ently dropping comments

Unify the standalone-comment capability with the other COMMENT ON backends
(supports_comment_on + format_comment_statement; protocol updated in step).
format_create_table_statement / format_column_definition raise
UnsupportedFeatureError for declared comments instead of silently dropping
them (Oracle has no inline comment clause).
Model base already defaults to "id"; fixtures should exercise the unspecified-PK default path.
- raise on declared INHERITS / generic storage options (unsupported)
- raise on FOREIGN KEY MATCH (unsupported)
- render DEFERRABLE / INITIALLY DEFERRED|IMMEDIATE table constraints
…EFRESH

Oracle has no REFRESH MATERIALIZED VIEW statement — refresh is a
DBMS_MVIEW procedure call — so supports_refresh_materialized_view() was always
False and the generic RefreshMaterializedViewExpression raised on Oracle, leaving
a created materialized view impossible to update.

Adds OracleRefreshMaterializedViewExpression, which renders an anonymous PL/SQL
block:

    BEGIN DBMS_MVIEW.REFRESH('SALES_SUMMARY', method => 'C'); END;

Key points:

- The procedure's method codes differ from the DDL keywords: the procedure uses
  F (fast) / C (complete) / ? (force) / P (PCT) / A (always) while the REFRESH
  clause uses FAST / COMPLETE / FORCE. OracleRefreshMethod keeps the two apart
  so they cannot be mixed up.
- Parameters use named notation (param => value): atomic_refresh, out_of_place,
  nested, refresh_after_errors, push_deferred_rpc, parallelism, purge_option.
  Defaults are never emitted, leaving the server defaults (atomic_refresh=TRUE,
  purge_option=1) in charge rather than freezing them into the SQL.
- The MV name is a string argument, not an identifier: upper-cased (Oracle folds
  unquoted identifiers to upper case) with embedded single quotes escaped;
  schema qualification supported.
- The procedure has existed since 8i, so supports_refresh_materialized_view() is
  not version gated and an unadapted dialect can render — unlike CREATE/DROP,
  which gate on 9i.
- The core RefreshMaterializedViewExpression routes here as well: concurrent is
  rejected explicitly (Oracle has no concurrent refresh mode; use
  atomic_refresh or out_of_place instead) and with_data is ignored (a refresh
  always repopulates).

Tests: 17 new cases, including all five method codes, quote escaping, parameter
validation, omitted defaults, rendering on an unadapted dialect, generic
expression interop and concurrent rejection. oracle dialect group: 311 passed;
cross-backend MV contract: 12 passed.
Expression classes moved under backend.expression/ and several were renamed
during the derived-DDL refactor, leaving relative-import depths and forward
references in TYPE_CHECKING blocks pointing at modules and classes that no
longer exist. Type checkers and any runtime evaluation of those blocks fail
to resolve them.

Verified statically: every TYPE_CHECKING import in the touched files now
resolves to an existing module and an existing top-level symbol, and no
string annotation references a name absent from the workspace.
The suite applies markers that were never declared in
[tool.pytest.ini_options] markers. pytest only warns for an unknown marker, so
the warning scrolls past unnoticed and a dropped or misspelt registration is
silent.

Registers the ones in use, with descriptions that say what they do: collection
grouping, not ordering. `feature` labels feature-area tests and
`benchmark_relation` marks relation benchmarks; neither affects scheduling, so
neither substitutes for the xdist_group marker that actually serialises a suite.

usefixtures needs no entry and is not added: pytest provides it, so it was a
false positive in the isolation audit rather than a missing registration.
… 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 26db84b, 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.
@sonarqubecloud

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
C Security Rating on New Code (required ≥ A)

See analysis details on SonarQube Cloud

Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE

@codecov

codecov Bot commented Sep 27, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 69.05983% with 543 lines in your changes missing coverage. Please review.
✅ Project coverage is 71.88%. Comparing base (26db84b) to head (393a813).

Files with missing lines Patch % Lines
...ctiverecord/backend/impl/oracle/mixins/ddl_type.py 57.68% 150 Missing and 73 partials ⚠️
...erecord/backend/impl/oracle/expression/ddl/type.py 71.02% 135 Missing and 80 partials ⚠️
...verecord/backend/impl/oracle/protocols/ddl_type.py 55.40% 33 Missing ⚠️
...ial/activerecord/backend/impl/oracle/mixins/ddl.py 32.25% 15 Missing and 6 partials ⚠️
...social/activerecord/backend/impl/oracle/backend.py 76.59% 9 Missing and 2 partials ⚠️
.../activerecord/backend/impl/oracle/async_backend.py 78.26% 8 Missing and 2 partials ⚠️
...social/activerecord/backend/impl/oracle/dialect.py 74.35% 6 Missing and 4 partials ⚠️
...social/activerecord/backend/impl/oracle/version.py 79.16% 6 Missing and 4 partials ⚠️
...tiverecord/backend/impl/oracle/mixins/partition.py 94.73% 2 Missing and 2 partials ⚠️
...ackend/impl/oracle/expression/materialized_view.py 93.75% 1 Missing and 1 partial ⚠️
... and 3 more
Additional details and impacted files
@@                   Coverage Diff                   @@
##           release/v1.0.0.dev3       #5      +/-   ##
=======================================================
- Coverage                72.98%   71.88%   -1.11%     
=======================================================
  Files                      123      127       +4     
  Lines                     8177     9674    +1497     
  Branches                  1250     1579     +329     
=======================================================
+ Hits                      5968     6954     +986     
- Misses                    1818     2157     +339     
- Partials                   391      563     +172     
Flag Coverage Δ
oracle-18c 71.64% <68.03%> (-1.32%) ⬇️
oracle-21c 71.64% <68.03%> (-1.32%) ⬇️
oracle-23c 71.60% <67.52%> (-1.37%) ⬇️
python-3.14 71.85% <68.88%> (-1.14%) ⬇️
unittests 71.85% <68.88%> (-1.14%) ⬇️

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.

@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 43c8c00 into release/v1.0.0.dev3 Sep 27, 2026
4 of 7 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