Skip to content

Collation 3/4: follow the collation in Consensus Commit layer - #3844

Open
Torch3333 wants to merge 1 commit into
collation-pr2-utf8-text-comparisonfrom
collation-pr3-consensus-commit
Open

Torch3333 wants to merge 1 commit into
collation-pr2-utf8-text-comparisonfrom
collation-pr3-consensus-commit

Conversation

@Torch3333

@Torch3333 Torch3333 commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Description

Consensus Commit repeats part of the storage's work in memory: keying its read, write, and delete sets by record key, checking whether a buffered write overlaps a later scan, re-applying conditions to merged results, and validating conditional mutations. The previous PR in this stack made the storage-side in-memory comparisons follow scalar.db.collation; this PR does the same here.

Under ICU, collate-equal spellings name the same row, so the snapshot treats them as one key, mutation grouping puts them in one batch, and equality and range checks use the collation. Under the default BINARY collation, key identity stays byte-exact; the one default-path change is that TEXT range ordering in the scan-after-write check and in conditional-mutation validation moves from Java String order to unsigned UTF-8 byte order. Pattern matching here stays byte-exact at any collation and would disagree with an ICU backend, so transactions reject LIKE and NOT LIKE under ICU.

Related issues and/or PRs

This PR is the third of a stack of four PRs based on feature/collation that together replace #3791. It is based on the second PR's branch, collation-pr2-utf8-text-comparison.

Stack, bottom to top: #3842, #3843, #3844, #3845.

Changes made

  • Snapshot.Key identity follows the collation: under ICU, TEXT key columns are identified by their collation key (CollationComparator.canonicalTextFormOf), so collate-equal keys hit one entry in the read, write, and delete sets and compare as equal; under BINARY, equality, hashing, and ordering are unchanged. The fields and toString keep the original bytes.
  • The scan-after-write overlap check in Snapshot compares partition keys and tests clustering-key range membership with the collation's key comparator; inclusive boundaries are an ordering test, so a written key that collates equal to a boundary is in range. Conjunction re-evaluation on merged results and the scan-with-index column match use the collation's equality.
  • MutationConditionsValidator decides EQ and NE with the collation's equality and GT, GTE, LT, LTE with the collation's column comparator; IS_NULL and IS_NOT_NULL are unchanged.
  • MutationsGrouper groups mutations by the same collation-aware key identity as the snapshot, so under ICU collate-equal partition or clustering keys land in one batch.
  • CrudHandler keeps a row returned by a Get with index whose stored index value collates equal to the queried value instead of filtering it out byte-exactly.
  • ConsensusCommitOperationChecker rejects a Get or Scan carrying a LIKE or NOT LIKE condition with CoreError.COLLATION_ICU_LIKE_CONDITION_NOT_SUPPORTED when scalar.db.collation is ICU, at every isolation level.
  • ConsensusCommitManager, TwoPhaseConsensusCommitManager, and ConsensusCommitParticipant build one CollationComparator from DatabaseConfig and pass it to Snapshot, CrudHandler, MutationsGrouper, and the operation checker; the constructors of those classes, of Snapshot.Key, and of MutationConditionsValidator take it as a parameter.
  • Deleted the two-argument ScalarDbUtils.columnsMatchAnyOfConjunctions overload; its remaining callers, Snapshot and CrudHandler, pass their own comparator.
  • Tests: SnapshotKeyTest and MutationsGrouperTest cover key identity and grouping under BINARY and case-insensitive ICU (case variants, composite keys, null text, keys built with different comparators); SnapshotTest covers read-your-own-write, write-set merging, scan-after-write validation, and serializable validation with collate-equal keys, range boundaries, index values, and conjunctions under both collations; MutationConditionsValidatorTest covers EQ, NE, and GT on text and non-text under both collations; CrudHandlerTest covers the Get-with-index match; ConsensusCommitOperationCheckerTest and ConsensusCommitManagerTest cover the LIKE and NOT LIKE rejection for Get, Scan, and ScanAll.
  • Updated the remaining consensuscommit unit tests and the three ConsensusCommit*IntegrationTestBase classes in the integration-test module to the new constructor arities.

Checklist

  • I have commented my code, particularly in hard-to-understand areas.
  • I have updated the documentation to reflect the changes.
  • I have considered whether similar issues could occur in other products, components, or modules if this PR is for bug fixes.
  • Any remaining open issues linked to this PR are documented and up-to-date (Jira, GitHub, etc.).
  • Tests (unit, integration, etc.) have been added for the changes.
  • My changes generate no new warnings.
  • Any dependent changes in other PRs have been merged and published.

Additional notes (optional)

This PR adds no integration tests; the integration-test module changes only follow the new constructor arities. The next PR in this stack carries the integration tests and CI.

Release notes

N/A

@Torch3333 Torch3333 self-assigned this Sep 2, 2026
@Torch3333 Torch3333 changed the title feat(collation): follow the collation in Consensus Commit snapshot ordering, equality, and key identity feat(collation): follow the collation in Consensus Commit snapshots Sep 2, 2026
@Torch3333 Torch3333 changed the title feat(collation): follow the collation in Consensus Commit snapshots feat(collation): follow the collation in Consensus Commit layer Sep 2, 2026
@Torch3333 Torch3333 changed the title feat(collation): follow the collation in Consensus Commit layer Collation 3/4: follow the collation in Consensus Commit layer Sep 2, 2026
@Torch3333
Torch3333 force-pushed the collation-pr3-consensus-commit branch from dcdcd5e to 7c3b34a Compare September 4, 2026 07:24
@Torch3333
Torch3333 force-pushed the collation-pr3-consensus-commit branch from 82206d3 to cf6b7be Compare September 7, 2026 06:29
@Torch3333
Torch3333 marked this pull request as ready for review September 8, 2026 09:00
@Torch3333
Torch3333 requested review from a team, KodaiD, brfrn169 and feeblefakie and removed request for a team September 8, 2026 09:01
@brfrn169
brfrn169 requested a balanced review from Copilot September 14, 2026 06:21

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚪ Unable to assess

Pull request overview

Aligns Consensus Commit’s in-memory key identity, filtering, validation, and scan overlap behavior with the configured collation.

Changes:

  • Adds collation-aware snapshot keys, comparisons, and conditional validation.
  • Propagates comparators through transaction managers and participants.
  • Rejects unsupported pattern matching under ICU and expands unit coverage.
File summaries
File Description
integration-test/.../ConsensusCommitSpecificIntegrationTestBase.java Passes configured comparator to CRUD handling.
integration-test/.../ConsensusCommitNullMetadataIntegrationTestBase.java Updates comparator wiring.
integration-test/.../ConsensusCommitImportTableIntegrationTestBase.java Updates comparator wiring.
core/src/test/.../util/ScalarDbUtilsTest.java Tests collation and null-range filtering.
core/src/test/.../WriteSetEncoderTest.java Updates snapshot-key construction.
core/src/test/.../WriteSetDecoderTest.java Updates snapshot-key construction.
core/src/test/.../TwoPhaseConsensusCommitManagerTest.java Configures default collation.
core/src/test/.../TransactionContextTest.java Tests comparator-aware validation keys.
core/src/test/.../SnapshotTest.java Extensively tests collation-aware snapshots.
core/src/test/.../SnapshotKeyTest.java Tests canonical key identity.
core/src/test/.../ParticipantCommitHandlerTest.java Updates comparator-aware fixtures.
core/src/test/.../MutationsGrouperTest.java Tests byte-exact grouping behavior.
core/src/test/.../MutationConditionsValidatorTest.java Tests collation-aware conditions.
core/src/test/.../MergedResultTest.java Tests stored key spelling.
core/src/test/.../CrudHandlerTest.java Tests collation-aware CRUD behavior.
core/src/test/.../CoordinatorCommitHandlerWithGroupCommitTest.java Updates snapshot construction.
core/src/test/.../ConsensusCommitParticipantTest.java Updates participant comparator wiring.
core/src/test/.../ConsensusCommitOperationCheckerTest.java Tests ICU restrictions.
core/src/test/.../ConsensusCommitManagerTest.java Tests manager-level ICU rejection.
core/src/test/.../SelectStatementHandlerTest.java Reuses binary test comparator.
core/src/test/.../ObjectStoragePartitionTest.java Reuses binary test comparator.
core/src/test/.../MutateStatementHandlerTest.java Reuses binary test comparator.
core/src/test/.../io/CollationComparators.java Adds shared test comparators.
core/src/test/.../FilterableScannerTest.java Updates scanner comparator fixtures.
core/src/test/.../checker/OperationCheckerTest.java Tests invalid conditional LIKE.
core/src/main/.../util/ScalarDbUtils.java Removes fallback overload and handles null ranges.
core/src/main/.../TwoPhaseConsensusCommitManager.java Propagates configured comparator.
core/src/main/.../TransactionContext.java Builds validation keys with snapshot collation.
core/src/main/.../Snapshot.java Implements collation-aware identity and validation.
core/src/main/.../MutationConditionsValidator.java Applies collation to mutation conditions.
core/src/main/.../MergedResult.java Preserves stored key spelling in columns.
core/src/main/.../CrudHandler.java Applies collation throughout CRUD processing.
core/src/main/.../ConsensusCommitParticipant.java Propagates comparator to participant snapshots.
core/src/main/.../ConsensusCommitOperationChecker.java Enforces ICU operation restrictions.
core/src/main/.../ConsensusCommitManager.java Propagates comparator to transactions.
core/src/main/.../ConditionChecker.java Rejects pattern-matching mutation conditions.
Review details
  • Files reviewed: 36/36 changed files
  • Comments generated: 3
  • Review effort level: Balanced

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +1140 to +1146
// Byte-equal keys are collate-equal under every collation, so the common case of a re-read
// row skips collation-key generation.
if (namespace.equals(another.namespace)
&& table.equals(another.table)
&& partitionKey.equals(another.partitionKey)
&& clusteringKey.equals(another.clusteringKey)) {
return true;
Comment on lines +311 to +313
// No storage with a record or partition atomicity supports ICU collation
// If we support such storage in the future, we need to make the MutationsGrouper collation
// aware
Comment on lines +35 to +39
if (!result.isPresent()) {
// A Put cannot rewrite a stored key, so a stored record keeps its own key spelling even when
// the Put's key only collates equal to it.
put.getPartitionKey().getColumns().forEach(c -> putColumns.put(c.getName(), c));
put.getClusteringKey()
Comment on lines +72 to +77
if (expression.getOperator() == Operator.LIKE
|| expression.getOperator() == Operator.NOT_LIKE) {
// No storage evaluates pattern matching in a conditional write.
isValid = false;
break;
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The LIKE/NOT_LIKE rejection added here doesn't take effect in Consensus Commit. ConsensusCommitOperationChecker calls ConditionChecker.check() for Put and Delete but discards the result:

Since ConditionChecker has no side effects, these calls currently do nothing. A LIKE condition that bypasses the builder check (e.g., via ConditionBuilder.putIf(List) or deleteIf(List)) passes the checker and, when the target record exists, hits default: throw new AssertionError() in MutationConditionsValidator.shouldMutate(). Other conditions that the storage-side OperationChecker rejects, such as a nonexistent column or a type mismatch, aren't rejected up front in Consensus Commit either.

This bug isn't introduced by this PR. The result has been discarded since the call was added in #899, and the same code is on 3 and 3.16 through 3.19. Could you fix it in a separate PR against master? The fix should be backported to all the supported release branches, which is easier as a standalone change than as part of this collation stack.

Some notes for that PR:

  • Throwing IllegalArgumentException when check() returns false, as OperationChecker.checkCondition() does with CoreError.OPERATION_CHECK_ERROR_CONDITION, would align Consensus Commit with the storage API and JDBC transactions.
  • This is a behavior change. Conditions on primary-key columns and comparisons against a null value (e.g., isEqualToText(null)) are currently accepted and evaluated in memory; they would start to be rejected.
  • ConsensusCommitOperationCheckerTest uses an unstubbed ConditionChecker mock, which returns false, so the existing tests that assert no exception would need check() stubbed to return true.

// Arrange
Key partitionKey = Key.of(PKEY1, 1, PKEY2, "val1");
Key clusteringKey = Key.of(CKEY1, 2, CKEY2, "val1");
MutationCondition condition = new PutIf(ConditionBuilder.column(PKEY2).isLikeText("val%"));

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test passes without the new LIKE branch in ConditionChecker. PKEY2 is a partition key column, and ColumnChecker is constructed with requireNotPrimaryKey = true, so it already returns false for this column and OperationChecker throws IllegalArgumentException regardless of the operator. The test table has no non-key TEXT column, so one would be needed for this test to exercise the LIKE branch.

this.collation = collation;
}

private void throwIfLikeConditionUnderIcuCollation(Selection selection) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: The other private helpers in this class, including throwIfKeyedMutationAtomicityUnitUnderIcuCollation added in this PR, are placed after the public check methods. Could you move this one there as well?

Comment on lines +63 to +66
if (operator == Operator.LIKE || operator == Operator.NOT_LIKE) {
throw new IllegalArgumentException(
CoreError.COLLATION_ICU_LIKE_CONDITION_NOT_SUPPORTED.buildMessage(
operator, selection.forFullTableName().get(), condition.getColumn().getName()));

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question: Could you explain why LIKE/NOT_LIKE can't be supported under ICU?

Comment on lines +311 to +314
// No storage with a record or partition atomicity supports ICU collation
// If we support such storage in the future, we need to make the MutationsGrouper collation
// aware
throw new UnsupportedOperationException(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This branch is effectively dead code: every storage whose mutation atomicity unit is RECORD or PARTITION rejects the ICU collation when it is created, and multi-storage doesn't allow overriding the collation per storage. How about throwing AssertionError instead and writing down why it's unreachable?

Suggested change
// No storage with a record or partition atomicity supports ICU collation
// If we support such storage in the future, we need to make the MutationsGrouper collation
// aware
throw new UnsupportedOperationException(
// Unreachable: every storage whose mutation atomicity unit is RECORD or PARTITION rejects
// the ICU collation when it is created (CoreError.COLLATION_ICU_NOT_SUPPORTED_BY_STORAGE),
// and multi-storage doesn't allow overriding the collation per storage. If such a storage
// supports the ICU collation in the future, revisit MutationsGrouper, which compares keys
// byte-exactly.
throw new AssertionError(

checkForMutation_WithKeyedMutationAtomicityUnitUnderIcuCollation_ShouldThrowUnsupportedOperationException would need to be updated as well.

Comment on lines +132 to +133
.partitionKey(Key.ofText(ANY_NAME_1, ANY_TEXT_1.toLowerCase()))
.clusteringKey(Key.ofText(ANY_NAME_2, ANY_TEXT_2.toLowerCase()))

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: ANY_TEXT_1 and ANY_TEXT_2 are already lowercase ("text1" and "text2"), so toLowerCase() doesn't change the spelling and this test passes without the change in MergedResult. Using a spelling that differs from the stored one (e.g., toUpperCase()) would make the test cover the change.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants