IGNITE-28277 CacheInterceptor need to take into account cache keepBinary mode#12911
IGNITE-28277 CacheInterceptor need to take into account cache keepBinary mode#12911zstan merged 8 commits intoapache:masterfrom
Conversation
8be5c14 to
84d439a
Compare
There was a problem hiding this comment.
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
ModifyNodeto choosekeepBinary()based onQueryProperties.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 (StanBy → StandBy). |
| 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, theopCtx == nullbranch constructs aCacheOperationContextwithskipReadThroughset totrue(see the second boolean argument). That changes cache behavior (read-through) just by setting application attributes, which is very likely unintended and inconsistent withGridCacheAdapter.withApplicationAttributes(which keeps itfalse). Please preserve the existing flags (i.e., do not enableskipReadThroughhere).
@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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
opCtxis null,withApplicationAttributesstores the caller-providedattrsmap directly in the newCacheOperationContext. This differs from theopCtx != nullpath (which copies viasetApplicationAttributes) and can leak later mutations or preserve a non-serializable map implementation. Consider always storing a defensive copy (and optionally rejecting null like the publicIgniteCache#withApplicationAttributesimplementations 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.
| @Override protected void afterTest() throws Exception { | ||
| super.afterTest(); | ||
|
|
||
| stopAllGrids(true); |
There was a problem hiding this comment.
Do we still need stopAllGrids at beforeTest?
| if (keepBinary) | ||
| assertTrue(newVal instanceof BinaryObject); | ||
| else | ||
| assertFalse(newVal instanceof BinaryObject); |
There was a problem hiding this comment.
assertEquals(keepBinary, newVal instanceof BinaryObject);
| var pureCacheCfg = new CacheConfiguration<Integer, Object>("Pure") | ||
| .setAtomicityMode(TRANSACTIONAL) | ||
| .setSqlSchema("PUBLIC") | ||
| .setInterceptor(new TestAlwaysUnwrappedValCacheInterceptor()) |
There was a problem hiding this comment.
new TestCacheInterceptor(false) and remove redundant class?
| 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"); |
There was a problem hiding this comment.
Why don't use @SqlQueryField annotation and simplify configuration?
There was a problem hiding this comment.
done, but new approach looks like not more simply ) as i see
|
append jmh test, results: |
|
update op: |
| MultiDcQueryMappingTest.class, | ||
| TxWithExceptionalInterceptorTest.class | ||
| TxWithExceptionalInterceptorTest.class, | ||
| CacheWithInterceptorIntegrationTest.class |
There was a problem hiding this comment.
Please add comma at the end of line
|
|
||
| /** */ | ||
| @QuerySqlField | ||
| int city_id; |
There was a problem hiding this comment.
Let's use java style field naming (cityId) and add alias
| 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')") |
There was a problem hiding this comment.
Let's test not only INSERT, but also UPDATE and DELETE (with onBeforeRemove in intercepter)
| .addQueryField("name", String.class.getName(), null) | ||
| .setKeyFieldName("id") | ||
| .setValueFieldName("name"); | ||
|
|
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
done, but i still miss an idea - why we need it ?)
There was a problem hiding this comment.
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)") |
There was a problem hiding this comment.
client.context().query().querySqlFields - it's a private API, maybe it's better to check with public API (via cache.withKeepBinary)
|
| } | ||
|
|
||
| /** {@inheritDoc} */ | ||
| @Override public @Nullable Object onBeforePut(Cache.Entry<Integer, Object> entry, Object newVal) { |
There was a problem hiding this comment.
What about onBeforeRemove?
Possible compatibility issues. Please, check rolling upgrade casesThis PR modifies protected classes (with Order annotation). Affected files:
|
|




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.