Skip to content

Commit 84ef371

Browse files
Merge remote-tracking branch 'origin/master' into ignite-26212
2 parents aa2c868 + 3455af6 commit 84ef371

File tree

179 files changed

+3679
-2920
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

179 files changed

+3679
-2920
lines changed

docs/_docs/SQL/schemas.adoc

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,3 +92,52 @@ If you do not use this parameter, the cache name is defined in the following for
9292
....
9393
SQL_<SCHEMA_NAME>_<TABLE_NAME>
9494
....
95+
96+
97+
== Validating Data Against the Schema
98+
99+
Ignite can verify that values inserted into a table match the column types and constraints declared in its SQL schema. By default, Ignite performs these checks only for indexed columns. To validate all columns, enable the server-side check on every node before startup:
100+
101+
[tabs]
102+
--
103+
tab:Java[]
104+
[source,java]
105+
----
106+
SqlConfiguration sqlCfg = new SqlConfiguration()
107+
.setValidationEnabled(true);
108+
109+
IgniteConfiguration cfg = new IgniteConfiguration()
110+
.setSqlConfiguration(sqlCfg);
111+
----
112+
113+
tab:XML[]
114+
[source,xml]
115+
----
116+
<property name="sqlConfiguration">
117+
<bean class="org.apache.ignite.configuration.SqlConfiguration">
118+
<property name="validationEnabled" value="true"/>
119+
</bean>
120+
</property>
121+
----
122+
--
123+
124+
When validation is enabled, Ignite runs extra checks for each SQL `INSERT`, `MERGE`, and `UPDATE` statement as well as cache API calls that modify SQL-managed tables and rejects data that breaks the schema. Violations raise an `IgniteSQLException` for SQL clients (JDBC, ODBC, REST), while key-value API callers get a `CacheException` whose root cause is that same `IgniteSQLException`.
125+
In both cases the application can handle the failure and the incorrect values are not stored. SQL DML already attempts to coerce values to the declared column types, so type mismatches typically appear when you write data through the cache API or binary objects.
126+
127+
[source,java]
128+
----
129+
// CREATE TABLE Person (id INT PRIMARY KEY, age INT);
130+
131+
IgniteCache<Integer, BinaryObject> cache = ignite.cache("SQL_PUBLIC_PERSON").withKeepBinary();
132+
133+
BinaryObject invalidPerson = ignite.binary().builder("Person")
134+
.setField("id", 2)
135+
.setField("age", "forty-two") // String instead of INT column
136+
.build();
137+
138+
cache.put(2, invalidPerson); // CacheException wrapping IgniteSQLException when validation is enabled
139+
----
140+
141+
With the default configuration (validation disabled for non-indexed columns), the `put` above succeeds even though the stored `BinaryObject` does not match the SQL schema. Turning on validation causes Ignite to reject the update, protecting the table from malformed records.
142+
143+
Enable the option when you need stronger guarantees that dynamic or user-provided data cannot break the table definition, and keep it disabled when the overhead of additional checks outweighs the risk of incorrect data.

modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/LogicalRelImplementor.java

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import java.util.function.Predicate;
2828
import java.util.function.Supplier;
2929
import org.apache.calcite.rel.RelCollation;
30+
import org.apache.calcite.rel.RelFieldCollation;
3031
import org.apache.calcite.rel.RelNode;
3132
import org.apache.calcite.rel.core.Intersect;
3233
import org.apache.calcite.rel.core.JoinRelType;
@@ -37,6 +38,7 @@
3738
import org.apache.calcite.rex.RexLiteral;
3839
import org.apache.calcite.rex.RexNode;
3940
import org.apache.calcite.util.ImmutableBitSet;
41+
import org.apache.calcite.util.mapping.IntPair;
4042
import org.apache.ignite.internal.processors.failure.FailureProcessor;
4143
import org.apache.ignite.internal.processors.query.QueryUtils;
4244
import org.apache.ignite.internal.processors.query.calcite.exec.RowHandler.RowFactory;
@@ -116,8 +118,6 @@
116118
import org.apache.ignite.internal.util.typedef.F;
117119

118120
import static org.apache.calcite.rel.RelDistribution.Type.HASH_DISTRIBUTED;
119-
import static org.apache.calcite.sql.SqlKind.IS_DISTINCT_FROM;
120-
import static org.apache.calcite.sql.SqlKind.IS_NOT_DISTINCT_FROM;
121121
import static org.apache.ignite.internal.processors.query.calcite.util.TypeUtils.combinedRowType;
122122

123123
/**
@@ -298,12 +298,40 @@ public LogicalRelImplementor(
298298
RelDataType rightType = rel.getRight().getRowType();
299299
JoinRelType joinType = rel.getJoinType();
300300

301-
int pairsCnt = rel.analyzeCondition().pairs().size();
301+
List<IntPair> joinPairs = rel.analyzeCondition().pairs();
302+
int pairsCnt = joinPairs.size();
303+
304+
List<RelFieldCollation> leftCollations = rel.leftCollation().getFieldCollations();
305+
List<RelFieldCollation> rightCollations = rel.rightCollation().getFieldCollations();
306+
307+
ImmutableBitSet allowNulls = rel.allowNulls();
308+
ImmutableBitSet.Builder collsAllowNullsBuilder = ImmutableBitSet.builder();
309+
int lastCollField = -1;
310+
311+
for (int c = 0; c < Math.min(leftCollations.size(), rightCollations.size()); ++c) {
312+
RelFieldCollation leftColl = leftCollations.get(c);
313+
RelFieldCollation rightColl = rightCollations.get(c);
314+
collsAllowNullsBuilder.set(c);
315+
316+
for (int p = 0; p < pairsCnt; ++p) {
317+
IntPair pair = joinPairs.get(p);
318+
319+
if (pair.source == leftColl.getFieldIndex() && pair.target == rightColl.getFieldIndex()) {
320+
lastCollField = c;
321+
322+
if (!allowNulls.get(p)) {
323+
collsAllowNullsBuilder.clear(c);
324+
325+
break;
326+
}
327+
}
328+
}
329+
}
302330

303331
Comparator<Row> comp = expressionFactory.comparator(
304-
rel.leftCollation().getFieldCollations().subList(0, pairsCnt),
305-
rel.rightCollation().getFieldCollations().subList(0, pairsCnt),
306-
rel.getCondition().getKind() == IS_NOT_DISTINCT_FROM || rel.getCondition().getKind() == IS_DISTINCT_FROM
332+
leftCollations.subList(0, lastCollField + 1),
333+
rightCollations.subList(0, lastCollField + 1),
334+
collsAllowNullsBuilder.build()
307335
);
308336

309337
Node<Row> node = MergeJoinNode.create(ctx, outType, leftType, rightType, joinType, comp, hasExchange(rel));

modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/ExpressionFactory.java

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,13 @@
2323
import java.util.function.Function;
2424
import java.util.function.Predicate;
2525
import java.util.function.Supplier;
26-
2726
import org.apache.calcite.rel.RelCollation;
2827
import org.apache.calcite.rel.RelFieldCollation;
2928
import org.apache.calcite.rel.core.AggregateCall;
3029
import org.apache.calcite.rel.type.RelDataType;
3130
import org.apache.calcite.rex.RexLiteral;
3231
import org.apache.calcite.rex.RexNode;
32+
import org.apache.calcite.util.ImmutableBitSet;
3333
import org.apache.ignite.internal.processors.query.calcite.exec.exp.agg.AccumulatorWrapper;
3434
import org.apache.ignite.internal.processors.query.calcite.exec.exp.agg.AggregateType;
3535
import org.apache.ignite.internal.processors.query.calcite.prepare.bounds.SearchBounds;
@@ -60,11 +60,14 @@ Supplier<List<AccumulatorWrapper<Row>>> accumulatorsFactory(
6060
*
6161
* @param left Collations of left row.
6262
* @param right Collations of right row.
63-
* @param nullsEqual If {@code true}, nulls are considered equal. Usually, NULL <> NULL in SQL. So, the value should
64-
* be {@code false}. Except cases with IS DISTINCT / IS NOT DISTINCT.
63+
* @param allowNulls Matching null fields. Usually, NULL <> NULL in SQL. Except IS DISTINCT / IS NOT DISTINCT.
6564
* @return Rows comparator.
6665
*/
67-
Comparator<Row> comparator(List<RelFieldCollation> left, List<RelFieldCollation> right, boolean nullsEqual);
66+
Comparator<Row> comparator(
67+
List<RelFieldCollation> left,
68+
List<RelFieldCollation> right,
69+
ImmutableBitSet allowNulls
70+
);
6871

6972
/**
7073
* Creates a Filter predicate.

modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/ExpressionFactoryImpl.java

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@
6262
import org.apache.calcite.rex.RexUtil;
6363
import org.apache.calcite.sql.type.SqlTypeName;
6464
import org.apache.calcite.sql.validate.SqlConformance;
65+
import org.apache.calcite.util.ImmutableBitSet;
6566
import org.apache.ignite.internal.binary.BinaryUtils;
6667
import org.apache.ignite.internal.processors.query.calcite.exec.ExecutionContext;
6768
import org.apache.ignite.internal.processors.query.calcite.exec.RowHandler;
@@ -197,7 +198,11 @@ else if (c2 != HIGHEST_VALUE)
197198
}
198199

199200
/** {@inheritDoc} */
200-
@Override public Comparator<Row> comparator(List<RelFieldCollation> left, List<RelFieldCollation> right, boolean nullsEqual) {
201+
@Override public Comparator<Row> comparator(
202+
List<RelFieldCollation> left,
203+
List<RelFieldCollation> right,
204+
ImmutableBitSet allowNulls
205+
) {
201206
if (F.isEmpty(left) || F.isEmpty(right) || left.size() != right.size())
202207
throw new IllegalArgumentException("Both inputs should be non-empty and have the same size: left="
203208
+ (left != null ? left.size() : "null") + ", right=" + (right != null ? right.size() : "null"));
@@ -213,9 +218,10 @@ else if (c2 != HIGHEST_VALUE)
213218

214219
return new Comparator<Row>() {
215220
@Override public int compare(Row o1, Row o2) {
216-
boolean hasNulls = false;
217221
RowHandler<Row> hnd = ctx.rowHandler();
218222

223+
boolean hasNulls = false;
224+
219225
for (int i = 0; i < left.size(); i++) {
220226
RelFieldCollation leftField = left.get(i);
221227
RelFieldCollation rightField = right.get(i);
@@ -226,8 +232,9 @@ else if (c2 != HIGHEST_VALUE)
226232
Object c1 = hnd.get(lIdx, o1);
227233
Object c2 = hnd.get(rIdx, o2);
228234

229-
if (c1 == null && c2 == null) {
230-
hasNulls = true;
235+
if (c1 == null && c2 == null && !hasNulls) {
236+
hasNulls = !allowNulls.get(i);
237+
231238
continue;
232239
}
233240

@@ -243,7 +250,7 @@ else if (c2 != HIGHEST_VALUE)
243250

244251
// If compared rows contain NULLs, they shouldn't be treated as equals, since NULL <> NULL in SQL.
245252
// Except cases with IS DISTINCT / IS NOT DISTINCT.
246-
return hasNulls && !nullsEqual ? 1 : 0;
253+
return hasNulls ? 1 : 0;
247254
}
248255
};
249256
}

modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/rel/ModifyNode.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242

4343
import static org.apache.ignite.internal.processors.cache.query.IgniteQueryErrorCode.CONCURRENT_UPDATE;
4444
import static org.apache.ignite.internal.processors.cache.query.IgniteQueryErrorCode.DUPLICATE_KEY;
45+
import static org.apache.ignite.internal.processors.query.QueryUtils.cacheForDML;
4546

4647
/**
4748
*
@@ -218,7 +219,7 @@ private void invokeOutsideTransaction(
218219
GridCacheProxyImpl<Object, Object> cache
219220
) throws IgniteCheckedException {
220221
Map<Object, EntryProcessor<Object, Object, Long>> map = invokeMap(tuples);
221-
Map<Object, EntryProcessorResult<Long>> res = cache.invokeAll(map);
222+
Map<Object, EntryProcessorResult<Long>> res = cacheForDML(cache).invokeAll(map);
222223

223224
long updated = res.values().stream().mapToLong(EntryProcessorResult::get).sum();
224225

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

Lines changed: 27 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -17,23 +17,23 @@
1717

1818
package org.apache.ignite.internal.processors.query.calcite.message;
1919

20-
import java.nio.ByteBuffer;
2120
import java.util.UUID;
22-
23-
import org.apache.ignite.plugin.extensions.communication.MessageReader;
24-
import org.apache.ignite.plugin.extensions.communication.MessageWriter;
21+
import org.apache.ignite.internal.Order;
2522

2623
/**
2724
*
2825
*/
2926
public class InboxCloseMessage implements CalciteMessage {
3027
/** */
31-
private UUID queryId;
28+
@Order(value = 0, method = "queryId")
29+
private UUID qryId;
3230

3331
/** */
32+
@Order(1)
3433
private long fragmentId;
3534

3635
/** */
36+
@Order(2)
3737
private long exchangeId;
3838

3939
/** */
@@ -42,8 +42,8 @@ public InboxCloseMessage() {
4242
}
4343

4444
/** */
45-
public InboxCloseMessage(UUID queryId, long fragmentId, long exchangeId) {
46-
this.queryId = queryId;
45+
public InboxCloseMessage(UUID qryId, long fragmentId, long exchangeId) {
46+
this.qryId = qryId;
4747
this.fragmentId = fragmentId;
4848
this.exchangeId = exchangeId;
4949
}
@@ -52,7 +52,14 @@ public InboxCloseMessage(UUID queryId, long fragmentId, long exchangeId) {
5252
* @return Query ID.
5353
*/
5454
public UUID queryId() {
55-
return queryId;
55+
return qryId;
56+
}
57+
58+
/**
59+
* @param queryId New query ID.
60+
*/
61+
public void queryId(UUID qryId) {
62+
this.qryId = qryId;
5663
}
5764

5865
/**
@@ -62,80 +69,25 @@ public long fragmentId() {
6269
return fragmentId;
6370
}
6471

72+
/**
73+
* @param fragmentId New fragment ID.
74+
*/
75+
public void fragmentId(long fragmentId) {
76+
this.fragmentId = fragmentId;
77+
}
78+
6579
/**
6680
* @return Exchange ID.
6781
*/
6882
public long exchangeId() {
6983
return exchangeId;
7084
}
7185

72-
/** {@inheritDoc} */
73-
@Override public boolean writeTo(ByteBuffer buf, MessageWriter writer) {
74-
writer.setBuffer(buf);
75-
76-
if (!writer.isHeaderWritten()) {
77-
if (!writer.writeHeader(directType()))
78-
return false;
79-
80-
writer.onHeaderWritten();
81-
}
82-
83-
switch (writer.state()) {
84-
case 0:
85-
if (!writer.writeLong(exchangeId))
86-
return false;
87-
88-
writer.incrementState();
89-
90-
case 1:
91-
if (!writer.writeLong(fragmentId))
92-
return false;
93-
94-
writer.incrementState();
95-
96-
case 2:
97-
if (!writer.writeUuid(queryId))
98-
return false;
99-
100-
writer.incrementState();
101-
102-
}
103-
104-
return true;
105-
}
106-
107-
/** {@inheritDoc} */
108-
@Override public boolean readFrom(ByteBuffer buf, MessageReader reader) {
109-
reader.setBuffer(buf);
110-
111-
switch (reader.state()) {
112-
case 0:
113-
exchangeId = reader.readLong();
114-
115-
if (!reader.isLastRead())
116-
return false;
117-
118-
reader.incrementState();
119-
120-
case 1:
121-
fragmentId = reader.readLong();
122-
123-
if (!reader.isLastRead())
124-
return false;
125-
126-
reader.incrementState();
127-
128-
case 2:
129-
queryId = reader.readUuid();
130-
131-
if (!reader.isLastRead())
132-
return false;
133-
134-
reader.incrementState();
135-
136-
}
137-
138-
return true;
86+
/**
87+
* @param exchangeId New exchange ID.
88+
*/
89+
public void exchangeId(long exchangeId) {
90+
this.exchangeId = exchangeId;
13991
}
14092

14193
/** {@inheritDoc} */

0 commit comments

Comments
 (0)