Skip to content

IGNITE-28277 CacheInterceptor need to take into account cache keepBinary mode#12911

Merged
zstan merged 8 commits intoapache:masterfrom
zstan:ignite-28277
Apr 6, 2026
Merged

IGNITE-28277 CacheInterceptor need to take into account cache keepBinary mode#12911
zstan merged 8 commits intoapache:masterfrom
zstan:ignite-28277

Conversation

@zstan
Copy link
Copy Markdown
Contributor

@zstan zstan commented Mar 18, 2026

I make a fix only for calcite related part, honestly - i afraid to make an equal changes on h2 related part and prefer to store it - as is. If approach is ok - i fill follow up issue for related documentation change.

@zstan zstan force-pushed the ignite-28277 branch 2 times, most recently from 8be5c14 to 84d439a Compare March 30, 2026 10:44
@zstan zstan requested a review from Copilot March 30, 2026 10:45
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR extends Calcite integration coverage around CacheInterceptor#onBeforePut behavior (particularly keep-binary vs unwrapped values) and adjusts Calcite DML execution to respect query keep-binary settings, while also applying several small cleanup/typo fixes across tests and core code.

Changes:

  • Add a new Calcite integration test covering interceptor value wrapping/unwrapping with/without keep-binary mode and include it in the Calcite integration suite.
  • Update Calcite ModifyNode to choose keepBinary() based on QueryProperties.keepBinary() and propagate session/application attributes to DML cache operations.
  • Introduce IgniteInternalCache#withApplicationAttributes(...) and wire it through cache proxies/contexts; plus assorted small cleanups (double semicolons, typo fixes, unused constant removal).

Reviewed changes

Copilot reviewed 21 out of 21 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
modules/indexing/src/test/java/org/apache/ignite/internal/processors/query/stat/PSUStatisticsTypesTest.java Removes an extra semicolon in test SQL string construction.
modules/indexing/src/test/java/org/apache/ignite/internal/processors/query/SqlSystemViewsSelfTest.java Removes an extra semicolon in test setup.
modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/SqlFieldsQuerySelfTest.java Removes an unused INSERT constant.
modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/standbycluster/IgniteStandByClusterTest.java Fixes typo in nested plugin provider name (StanByStandBy).
modules/core/src/test/java/org/apache/ignite/internal/processors/cache/binary/CacheKeepBinaryWithInterceptorTest.java Renames test interceptor classes for clarity; makes a test field final and suppresses unused warning.
modules/core/src/test/java/META-INF/services/org.apache.ignite.plugin.PluginProvider Updates service registration entry to match renamed provider class.
modules/core/src/main/java/org/apache/ignite/internal/util/BasicRateLimiter.java Removes an extra semicolon in rate limiter resync logic.
modules/core/src/main/java/org/apache/ignite/internal/processors/job/GridJobWorker.java Removes an extra semicolon in logging statement.
modules/core/src/main/java/org/apache/ignite/internal/processors/cache/query/continuous/CacheContinuousQueryPartitionRecovery.java Removes an extra semicolon in boolean expression.
modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/atomic/GridDhtAtomicCache.java Removes an extra semicolon after lock acquisition.
modules/core/src/main/java/org/apache/ignite/internal/processors/cache/IgniteInternalCache.java Adds withApplicationAttributes(Map<String,String>) projection API.
modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheProxyImpl.java Implements withApplicationAttributes on the internal cache proxy (but see review comments).
modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheAdapter.java Implements withApplicationAttributes for adapter-based projections (but see review comments).
modules/core/src/main/java/org/apache/ignite/internal/processors/cache/CacheOperationContext.java Adds withApplicationAttributes(...) to propagate app/session attributes through operation context.
modules/core/src/main/java/org/apache/ignite/internal/managers/discovery/GridDiscoveryManager.java Removes an extra semicolon in an assertion.
modules/calcite/src/test/java/org/apache/ignite/testsuites/IntegrationTestSuite.java Registers the new Calcite interceptor integration test in the suite.
modules/calcite/src/test/java/org/apache/ignite/internal/processors/tx/TxWithExceptionalInterceptorTest.java Ensures grids are stopped in afterTest() to avoid leakage across parameter runs.
modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/CacheWithInterceptorIntegrationTest.java New integration test validating interceptor onBeforePut value wrapping/unwrapping with keep-binary mode.
modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/CancelTest.java Removes an extra semicolon in query entity builder.
modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/rel/ModifyNode.java Uses QueryProperties.keepBinary() to decide whether to apply keepBinary() for DML; propagates session attributes via withApplicationAttributes.
modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/rel/HashJoinNode.java Removes an extra semicolon in downstream push.
Comments suppressed due to low confidence (1)

modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheProxyImpl.java:321

  • In withApplicationAttributes, the opCtx == null branch constructs a CacheOperationContext with skipReadThrough set to true (see the second boolean argument). That changes cache behavior (read-through) just by setting application attributes, which is very likely unintended and inconsistent with GridCacheAdapter.withApplicationAttributes (which keeps it false). Please preserve the existing flags (i.e., do not enable skipReadThrough here).
    @Override public GridCacheProxyImpl<K, V> withApplicationAttributes(Map<String, String> attrs) {
        CacheOperationContext prev = gate.enter(opCtx);

        try {
            return new GridCacheProxyImpl<>(ctx, delegate,
                opCtx != null ? opCtx.setApplicationAttributes(attrs) :
                    new CacheOperationContext(
                        false,
                        true,
                        false,
                        null,
                        false,
                        null,
                        false,
                        null,
                        attrs));

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 21 out of 21 changed files in this pull request and generated 2 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 21 out of 21 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (1)

modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheProxyImpl.java:321

  • When opCtx is null, withApplicationAttributes stores the caller-provided attrs map directly in the new CacheOperationContext. This differs from the opCtx != null path (which copies via setApplicationAttributes) and can leak later mutations or preserve a non-serializable map implementation. Consider always storing a defensive copy (and optionally rejecting null like the public IgniteCache#withApplicationAttributes implementations do).
    @Override public GridCacheProxyImpl<K, V> withApplicationAttributes(Map<String, String> attrs) {
        CacheOperationContext prev = gate.enter(opCtx);

        try {
            return new GridCacheProxyImpl<>(ctx, delegate,
                opCtx != null ? opCtx.setApplicationAttributes(attrs) :
                    new CacheOperationContext(
                        false,
                        false,
                        false,
                        null,
                        false,
                        null,
                        false,
                        null,
                        attrs));

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@zstan zstan requested a review from alex-plekhanov March 30, 2026 13:49
@zstan zstan changed the title IGNITE-28277 Append additional tests for CacheInterceptor#onBeforePut IGNITE-28277 CacheInterceptor need to take into account cache keepBinary mode Apr 1, 2026
@Override protected void afterTest() throws Exception {
super.afterTest();

stopAllGrids(true);
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.

Do we still need stopAllGrids at beforeTest?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

remove

Comment on lines +204 to +207
if (keepBinary)
assertTrue(newVal instanceof BinaryObject);
else
assertFalse(newVal instanceof BinaryObject);
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.

assertEquals(keepBinary, newVal instanceof BinaryObject);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

done

var pureCacheCfg = new CacheConfiguration<Integer, Object>("Pure")
.setAtomicityMode(TRANSACTIONAL)
.setSqlSchema("PUBLIC")
.setInterceptor(new TestAlwaysUnwrappedValCacheInterceptor())
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.

new TestCacheInterceptor(false) and remove redundant class?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

done, thanks !

Comment on lines +77 to +92
var entity1 = new QueryEntity()
.setTableName("CITY")
.setKeyType(Integer.class.getName())
.setValueType(City.class.getName())
.addQueryField("id", Integer.class.getName(), null)
.addQueryField("name", String.class.getName(), null)
.setKeyFieldName("id");

var entity2 = new QueryEntity()
.setTableName("PERSON")
.setKeyType(Integer.class.getName())
.setValueType(Person.class.getName())
.addQueryField("id", Integer.class.getName(), null)
.addQueryField("name", String.class.getName(), null)
.addQueryField("city_id", Integer.class.getName(), null)
.setKeyFieldName("id");
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.

Why don't use @SqlQueryField annotation and simplify configuration?

Copy link
Copy Markdown
Contributor Author

@zstan zstan Apr 1, 2026

Choose a reason for hiding this comment

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

done, but new approach looks like not more simply ) as i see

@zstan
Copy link
Copy Markdown
Contributor Author

zstan commented Apr 2, 2026

append jmh test, results:

Benchmark                             (engine)  (keepBinary)   Mode  Cnt     Score     Error  Units
JmhCacheWithInterceptorBenchmark.put   CALCITE          true  thrpt   10  3504.528 ± 142.769  ops/s
JmhCacheWithInterceptorBenchmark.put   CALCITE         false  thrpt   10  3461.187 ± 165.752  ops/s

@zstan
Copy link
Copy Markdown
Contributor Author

zstan commented Apr 2, 2026

update op:

Benchmark                                (engine)  (keepBinary)   Mode  Cnt     Score     Error  Units
JmhCacheWithInterceptorBenchmark.update   CALCITE          true  thrpt   10  2672.824 ± 119.782  ops/s
JmhCacheWithInterceptorBenchmark.update   CALCITE         false  thrpt   10  2702.268 ± 171.562  ops/s

MultiDcQueryMappingTest.class,
TxWithExceptionalInterceptorTest.class
TxWithExceptionalInterceptorTest.class,
CacheWithInterceptorIntegrationTest.class
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.

Please add comma at the end of line

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

done


/** */
@QuerySqlField
int city_id;
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.

Let's use java style field naming (cityId) and add alias

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

done

int incParam = 0;

try (Transaction tx = client.transactions().txStart(PESSIMISTIC, READ_COMMITTED)) {
client.context().query().querySqlFields(new SqlFieldsQuery("INSERT INTO PUBLIC.PURE(id, name) VALUES (?, 'val')")
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.

Let's test not only INSERT, but also UPDATE and DELETE (with onBeforeRemove in intercepter)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

done

.addQueryField("name", String.class.getName(), null)
.setKeyFieldName("id")
.setValueFieldName("name");

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.

Let's also add entity:

var entity1 = new QueryEntity()
    .setTableName("Complex")
    .setKeyType(Integer.class.getName())
    .setValueType("ComplexKey")
    .addQueryField("id", Integer.class.getName(), null)
    .addQueryField("name", String.class.getName(), null)
    .addQueryField("val", Integer.class.getName(), null)
    .setKeyFieldName("id");

And cache

        var complexCacheCfg = new CacheConfiguration<Integer, Object>("complex")
            .setAtomicityMode(TRANSACTIONAL)
            .setSqlSchema("PUBLIC")
            .setQueryEntities(List.of(entity1));

Without iterceptor.
DML on this cache will fail on interceptor, since there is no such class for deserialization, but without interceptor it should pass until someone add getValue for entry processor in ModifyNode.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

done, but i still miss an idea - why we need it ?)

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.

To ensure that nobody will break this case in the future by adding entry.getValue() to any entry processor in ModifyNode

.setArgs(incParam++), keepBinary).getAll();
client.context().query().querySqlFields(new SqlFieldsQuery("INSERT INTO PUBLIC.CITY(id, name) VALUES (?, 'val')")
.setArgs(incParam++), keepBinary).getAll();
client.context().query().querySqlFields(new SqlFieldsQuery("INSERT INTO PUBLIC.PERSON(id, name, city_id) VALUES (?, 'val', 1)")
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.

client.context().query().querySqlFields - it's a private API, maybe it's better to check with public API (via cache.withKeepBinary)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

yep, done

@zstan
Copy link
Copy Markdown
Contributor Author

zstan commented Apr 3, 2026

Benchmark                                (engine)  (keepBinary)   Mode  Cnt     Score     Error  Units
JmhCacheWithInterceptorBenchmark.update   CALCITE          true  thrpt   10  4372.599 ± 157.473  ops/s
JmhCacheWithInterceptorBenchmark.update   CALCITE         false  thrpt   10  4429.750 ± 140.710  ops/s

}

/** {@inheritDoc} */
@Override public @Nullable Object onBeforePut(Cache.Entry<Integer, Object> entry, Object newVal) {
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.

What about onBeforeRemove?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

done

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 5, 2026

Possible compatibility issues. Please, check rolling upgrade cases

This PR modifies protected classes (with Order annotation).
Changes to these classes can break rolling upgrade compatibility.

Affected files:

  • modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/message/QueryStartRequest.java

@sonarqubecloud
Copy link
Copy Markdown

sonarqubecloud Bot commented Apr 5, 2026

Quality Gate Failed Quality Gate failed

Failed conditions
3 New Code Smells (required ≤ 1)

See analysis details on SonarQube Cloud

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

@zstan zstan merged commit e8f16e0 into apache:master Apr 6, 2026
8 of 10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants