Add composite primary key support#2693
Merged
Merged
Conversation
4 tasks
Active Record gained native composite-primary-key support in Rails 7.1 (rails/rails#48541), but oracle-enhanced still treated composite PKs as unsupported: pk_and_sequence_for emitted a runtime warning and returned nil, primary_key returned nil for composite-PK tables, the insert / RETURNING path was single-column-only, and empty_insert_statement_value raised NotImplementedError for an Array primary key. Adapter changes: - Drop the runtime "Composite primary key is ignored" warning from pk_and_sequence_for. The method still returns nil for composite PKs (a single sequence does not model a composite key); callers fall back to primary_keys. - Remove the primary_key override; Rails' abstract default (`pk = pk.first unless pk.size > 1`) already returns an Array for composite PKs and a String / nil for single-column / no-PK tables. - has_primary_key? now uses primary_keys directly so it returns true for composite-PK tables. - prefetch_primary_key_from_dictionary short-circuits composite PKs to false (cardinality check, not Array? — the adapter's primary_keys always returns an Array, including ["id"] for single-column PKs). The schema-cache helper already handled composite via composite_primary_key?(pks). Composite PKs cannot be prefetched from a single sequence and instead go through the RETURNING-driven insert path. - empty_insert_statement_value accepts an Array primary key and produces a multi-column "(c1, c2, ...) VALUES (DEFAULT, DEFAULT, ...)" form for the all-defaults case. - columns_for_returning_clause accepts an Array pk so multi-column RETURNING is generated. The "all-defaults" rejection-skip regex is widened from /VALUES \(DEFAULT\)/ to /VALUES \(DEFAULT(?:, DEFAULT)*\)/ so composite PKs whose columns all default still get RETURNING for the database-generated values rather than being dropped by the column-list rejection logic. write_lobs gets a TODO documenting that its scalar-PK assumption breaks for CPK + LOB models. Out of scope for this change; tracked separately. Validations stay as-is: identity: true and primary_key_trigger: true combined with a composite primary key still raise ArgumentError. Tests: - New spec/.../composite_primary_key_spec.rb ports the adapter-relevant cases from Rails' own activerecord/test/cases/primary_keys_test.rb so they run against Oracle. Coverage: - introspection (primary_keys, primary_key, has_primary_key?, pk_and_sequence_for returning nil and emitting no warning); - schema cache caching and invalidation of the composite PK column array; - model contract (composite_primary_key?, id= array assignment, id= with non-array raising TypeError, id_was, id?, to_key, primary_key_values_present?); - schema dump round-trip (in-order and out-of-order composite PKs); - prefetch_primary_key? — false for composite, true for single PK, including a cold-cache regression that exercises the dictionary fallback (would have caught the composite_primary_key? cardinality bug); - RETURNING — both the "user supplies all PK columns" and the "all PK columns defaulted" path on a CPK table with column defaults (would have caught the multi-DEFAULT regex bug); - find / find with multiple keys (preserves request order) / wrapped-array find / class-level destroy / predicate-builder where-by-PK-columns; - mixed-type CPK (String + Integer) round-trip and find/destroy. - identity_primary_key_spec.rb's empty_insert_statement_value composite assertion is updated to expect the new SQL fragment instead of NotImplementedError. Closes rsim#2648, supersedes rsim#2530. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
yahonda
added a commit
to yahonda/oracle-enhanced
that referenced
this pull request
May 5, 2026
`has_primary_key?` was an oracle-enhanced override of an abstract
adapter method that Rails itself removed in commit d1521719c5
("Removed support for accessing attributes on a
has_and_belongs_to_many join table", 2011-01-16) when the HABTM
attribute-access path that needed it was retired. oracle-enhanced
kept the override, but it has been dead surface for 14+ years:
- No in-tree caller. The only previous user, the original
`prefetch_primary_key?` cache-fill path, was refactored into
`prefetch_primary_key_from_dictionary` and now goes through
`primary_keys(table_name)` directly.
- Not part of the Rails abstract adapter API anymore. Rails callers
use `primary_key(table_name)` / `primary_keys(table_name)` instead.
- Marked `# :nodoc:` so no API contract.
- The single spec assertion added in rsim#2693
(`expect(@conn.has_primary_key?("uber_barcodes")).to be true`)
duplicated the immediately preceding `primary_keys` assertion;
removed alongside the method.
The body had also already lost its `(owner, desc_table_name)`
arguments (carried over from `pk_and_sequence_for`'s historical
signature, never functional because `pk_and_sequence_for` itself
unconditionally re-resolves them). Rather than keep a one-line
wrapper around `primary_keys(table_name).any?`, drop the method
entirely.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This was referenced May 5, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds full composite primary key support to the Oracle Enhanced adapter, tracking the native Active Record CPK feature added in Rails 7.1 (rails/rails#48541). Closes #2648 and supersedes the partial fix in #2530.
Until now, oracle-enhanced treated composite PKs as unsupported:
pk_and_sequence_foremitted a runtime warning (visible in every test run that touched a composite-PK table such astest_authors_test_books) and returnednil.primary_keyreturnednilfor composite-PK tables.empty_insert_statement_valueraisedNotImplementedErrorfor an Array primary key.Adapter changes
pk_and_sequence_for— drop the "Composite primary key is ignored" warning. The method still returnsnilfor composite PKs (a single sequence cannot model a composite key); callers fall back toprimary_keys.primary_key— removed; Rails' abstract default (pk = pk.first unless pk.size > 1) already returns an Array for composite PKs and a String /nilfor single-column / no-PK tables.has_primary_key?— usesprimary_keysdirectly so it returnstruefor composite-PK tables.prefetch_primary_key_from_dictionary— short-circuits composite PKs tofalseusing a cardinality check (pks.size > 1), notcomposite_primary_key?(pks)— the predicate isArray?-based andprimary_keysalways returns an Array (including["id"]for single-column PKs), so anArray?check would have wrongly disabled prefetch for every PK table when the schema cache is cold. The schema-cache fast path keeps using the existingcomposite_primary_key?(pks)predicate, whereschema_cache.primary_keysreturns a String for single-column PKs.empty_insert_statement_value— accepts an Array primary key and produces a multi-column(c1, c2, ...) VALUES (DEFAULT, DEFAULT, ...)form.columns_for_returning_clause— accepts an Arraypkso multi-column RETURNING is generated. The "all-defaults" rejection-skip regex is widened from/VALUES \(DEFAULT\)/to/VALUES \(DEFAULT(?:, DEFAULT)*\)/so composite PKs whose columns all default still get RETURNING for the database-generated values rather than being dropped by the column-list rejection logic.write_lobs— gets aTODOdocumenting that its scalar-PK assumption breaks for CPK + LOB models. Out of scope for this PR; tracked separately.Validations stay as-is:
identity: trueandprimary_key_trigger: truecombined with a composite primary key still raiseArgumentError.Tests
spec/.../composite_primary_key_spec.rbports the adapter-relevant cases from Rails' ownactiverecord/test/cases/primary_keys_test.rbso they run against Oracle. Coverage:primary_keys,primary_key,has_primary_key?,pk_and_sequence_forreturningniland emitting no warning)composite_primary_key?,id=array assignment,id=with non-array raisingTypeError,id_was,id?,to_key,primary_key_values_present?)prefetch_primary_key?— false for composite, true for single PK, including a cold-cache regression that exercises the dictionary fallbackfind/findwith multiple keys (preserves request order) / wrapped-arrayfind/ class-leveldestroy/ predicate-builderwhere-by-PK-columnsidentity_primary_key_spec.rb'sempty_insert_statement_valuecomposite assertion is updated to expect the new SQL fragment instead ofNotImplementedError.Test plan
bundle exec rspec spec/active_record/connection_adapters/oracle_enhanced/composite_primary_key_spec.rb— all examples passWARNING: Active Record does not support composite primary key.bundle exec rubocop— clean