tkalkirill commented on code in PR #13225:
URL: https://github.com/apache/ignite/pull/13225#discussion_r3473823656
##########
modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/UserDefinedFunctionsIntegrationTest.java:
##########
@@ -332,15 +332,18 @@ public void testTableFunctions() throws Exception {
assertThrows("SELECT * from raiseException(?, ?, ?)",
RuntimeException.class, "Test exception",
1, "test", true);
+ var empObj1 = new Employer("emp1", 1000d);
+ var empObj10 = new Employer("emp10", 10000d);
+
// Object type.
- assertQuery("SELECT * from withObjectType(1)")
- .returns(1, new Employer("emp1", 1000d))
- .returns(10, new Employer("emp10", 10000d))
+ assertQuery("SELECT * from withObjectType(1) ORDER BY ID")
+ .returns(1, client.binary().toBinary(empObj1))
Review Comment:
Why is a binary object expected here if the function returns a POJO?
##########
modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/IgniteSqlFunctions.java:
##########
@@ -119,15 +119,15 @@ public static BigDecimal toBigDecimal(boolean val, int
precision, int scale) {
}
/** CAST(VARCHAR AS DECIMAL). */
- public static BigDecimal toBigDecimal(String s, int precision, int scale) {
+ public static @Nullable BigDecimal toBigDecimal(String s, int precision,
int scale) {
Review Comment:
Forgot `@Nullable String s`.
##########
modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/IgniteSqlFunctions.java:
##########
@@ -119,15 +119,15 @@ public static BigDecimal toBigDecimal(boolean val, int
precision, int scale) {
}
/** CAST(VARCHAR AS DECIMAL). */
- public static BigDecimal toBigDecimal(String s, int precision, int scale) {
+ public static @Nullable BigDecimal toBigDecimal(String s, int precision,
int scale) {
if (s == null)
return null;
return toBigDecimal(new BigDecimal(s.trim()), precision, scale);
}
/** Converts {@code val} to a {@link BigDecimal} with the given {@code
precision} and {@code scale}. */
- public static BigDecimal toBigDecimal(Number val, int precision, int
scale) {
+ public static @Nullable BigDecimal toBigDecimal(Number val, int precision,
int scale) {
Review Comment:
Forgot `@Nullable Number val`.
##########
modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/IndexScan.java:
##########
@@ -204,7 +204,7 @@ protected TreeIndex<IndexRow> treeIndex() {
}
/** From Row to IndexRow convertor. */
- protected IndexRow row2indexRow(Row bound) {
+ protected @Nullable IndexRow row2indexRow(Row bound) {
Review Comment:
Forgot `@Nullable Row bound`.
##########
modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/IgniteSqlFunctions.java:
##########
@@ -146,7 +146,7 @@ public static BigDecimal toBigDecimal(Number val, int
precision, int scale) {
}
/** Cast object depending on type to DECIMAL. */
- public static BigDecimal toBigDecimal(Object o, int precision, int scale) {
+ public static @Nullable BigDecimal toBigDecimal(Object o, int precision,
int scale) {
Review Comment:
Forgot `@Nullable Object o`.
##########
modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/SelectByKeyFieldTest.java:
##########
@@ -338,6 +353,104 @@ private void
checkCompositePkWithDifferentCmpOperations(boolean useBinaryObject)
}
}
+ /** */
+ @Test
+ public void testResultsWithUnexpectedParam() {
+ sql(
+ "create table PUBLIC.PERSON(id int, name varchar, surname varchar,
age int, primary key(id, name)) with \"key_type=%s\""
+ .formatted("UndefinedClassName")
+ );
+
+ checkResultsWithUnexpectedKey(new Object());
+ }
+
+ /** */
+ @Test
+ public void testResultsWithoutKeyTypeWithUnexpectedParam() {
+ sql("create table PUBLIC.PERSON(id int, name varchar, surname varchar,
age int, primary key(id, name))");
+
+ checkResultsWithUnexpectedKey(new Object());
+ }
+
+ /** */
+ @Test
+ public void testResultsWithoutKeyTypeWithUnexpectedBOAsParam() {
+ sql("create table PUBLIC.PERSON(id int, name varchar, surname varchar,
age int, primary key(id, name))");
+
+ Object arg = client.binary().builder("UserDefinedBinary").build();
+
+ checkResultsWithUnexpectedKey(arg);
+ }
+
+ /** Check results with search for _key different from defined in schema. */
+ private void checkResultsWithUnexpectedKey(Object arg) {
+ String qryTemplate = "select /*+ DISABLE_RULE('%s') */ _key from
PUBLIC.PERSON where _key %s ? ORDER BY _key";
Review Comment:
Why disable the rule?
##########
modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/SelectByKeyFieldTest.java:
##########
@@ -338,6 +353,104 @@ private void
checkCompositePkWithDifferentCmpOperations(boolean useBinaryObject)
}
}
+ /** */
+ @Test
+ public void testResultsWithUnexpectedParam() {
+ sql(
+ "create table PUBLIC.PERSON(id int, name varchar, surname varchar,
age int, primary key(id, name)) with \"key_type=%s\""
+ .formatted("UndefinedClassName")
+ );
+
+ checkResultsWithUnexpectedKey(new Object());
+ }
+
+ /** */
+ @Test
+ public void testResultsWithoutKeyTypeWithUnexpectedParam() {
+ sql("create table PUBLIC.PERSON(id int, name varchar, surname varchar,
age int, primary key(id, name))");
+
+ checkResultsWithUnexpectedKey(new Object());
+ }
+
+ /** */
+ @Test
+ public void testResultsWithoutKeyTypeWithUnexpectedBOAsParam() {
+ sql("create table PUBLIC.PERSON(id int, name varchar, surname varchar,
age int, primary key(id, name))");
+
+ Object arg = client.binary().builder("UserDefinedBinary").build();
+
+ checkResultsWithUnexpectedKey(arg);
+ }
+
+ /** Check results with search for _key different from defined in schema. */
+ private void checkResultsWithUnexpectedKey(Object arg) {
+ String qryTemplate = "select /*+ DISABLE_RULE('%s') */ _key from
PUBLIC.PERSON where _key %s ? ORDER BY _key";
+
+ fillTable();
+
+ for (CmpOp cmpOp : CmpOp.values()) {
+ List<List<?>> res = null;
+
+ for (boolean tableScan : List.of(true, false)) {
+ String qry = qryTemplate.formatted(tableScan ?
+ "LogicalIndexScanConverterRule" :
"LogicalTableScanConverterRule", cmpOp.comp);
+
+ if (res == null)
+ res = sql(qry, arg);
+ else {
+ List<List<?>> secondRes = sql(qry, arg);
+
+ assertEquals("Different results size", res.size(),
secondRes.size());
+
+ for (int pos = 0; pos < secondRes.size(); pos++)
+ assertEquals(res.get(pos).get(0),
secondRes.get(pos).get(0));
+ }
+
+ assertQuery(qry)
+ .withParams(arg)
+ .matches(tableScan ?
+ QueryChecker.containsTableScan("PUBLIC", "PERSON") :
+ QueryChecker.containsIndexScan("PUBLIC", "PERSON",
PRIMARY_KEY_INDEX)
+ )
+ .check();
Review Comment:
And what will the return value be?
If I’ve understood the test correctly, we’re attempting a search using a key
type (or class) that differs from the one defined in the schema. I would expect
an error—but here, everything will be fine?
##########
modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/SelectByKeyFieldTest.java:
##########
@@ -304,32 +193,158 @@ private void checkCompositePk(
}
/** */
- private void checkCompositePkWithDifferentCmpOperations(boolean
useBinaryObject) {
+ @Test
+ public void testCompositePkSearchByPartOfKeyTableScan() {
+ compositePkEqualitySearchByPartOfKey(true);
+ }
+
+ /** */
+ @Test
+ public void testCompositePkSearchByPartOfKeyIdxScan() {
+ compositePkEqualitySearchByPartOfKey(false);
+ }
+
+ /**
+ * Tests composite primary key equality search using only part of the key
(single column).
+ * Verifies that queries with equality comparison on either the {@code id}
or {@code name} column
+ * return correct results using either table scan or index scan depending
on the {@code tableScan} flag.
+ *
+ * @param tableScan {@code true} to test with table scan, {@code false} to
test with index scan.
+ */
+ public void compositePkEqualitySearchByPartOfKey(boolean tableScan) {
sql("create table PUBLIC.PERSON(id int, name varchar, surname varchar,
age int, primary key(id, name))");
- for (int i = 0; i < 10; i++) {
- sql(
- "insert into PUBLIC.PERSON(id, name, surname, age) values (?,
?, ?, ?)",
- i, "foo" + i, "bar" + i, 18 + i
- );
- }
+ fillTable();
+
+ List<List<?>> sqlRs = sql("select _key, id, name from PUBLIC.PERSON
order by id");
+ BinaryObject _key = (BinaryObject)sqlRs.get(6).get(0);
+ int id = (Integer)sqlRs.get(6).get(1);
+ String name = (String)sqlRs.get(6).get(2);
+
+ // Select by uniq id.
+ assertQuery("select /*+ DISABLE_RULE('" + (tableScan ?
"LogicalIndexScanConverterRule" : "LogicalTableScanConverterRule") +
+ "') */ id, name, age, _key from PUBLIC.PERSON where id = ?")
+ .withParams(id)
+ .matches(tableScan ?
+ QueryChecker.containsTableScan("PUBLIC", "PERSON") :
+ QueryChecker.containsIndexScan("PUBLIC", "PERSON",
PRIMARY_KEY_INDEX)
+ )
+ .columnNames("ID", "NAME", "AGE", KEY_FIELD_NAME)
+ .returns(id, name, 24, _key)
+ .check();
+
+ // Select by uniq name.
+ assertQuery("select /*+ DISABLE_RULE('" + (tableScan ?
"LogicalIndexScanConverterRule" : "LogicalTableScanConverterRule") +
+ "') */ id, name, age, _key from PUBLIC.PERSON where name = ?")
+ .withParams(name)
+ .matches(tableScan ?
+ QueryChecker.containsTableScan("PUBLIC", "PERSON") :
+ QueryChecker.containsIndexScan("PUBLIC", "PERSON",
PRIMARY_KEY_INDEX)
+ )
+ .columnNames("ID", "NAME", "AGE", KEY_FIELD_NAME)
+ .returns(id, name, 24, _key)
+ .check();
+ }
+
+ /** */
+ @Test
+ public void testCompositePkWithOrderByKeyTableScan() {
+ compositePkWithOrderByKey(true);
+ }
+
+ /** */
+ @Test
+ public void testCompositePkWithOrderByKeyIdxScan() {
+ compositePkWithOrderByKey(false);
+ }
+
+ /**
+ * Tests composite primary key ordering by {@code _key}.
+ * Verifies that ordering by the composite primary key produces correct
results
+ * when comparing binary objects using {@link #binaryObjectCmpForDml}.
+ */
+ public void compositePkWithOrderByKey(boolean tableScan) {
+ sql("create table PUBLIC.PERSON(id int, name varchar, surname varchar,
age int, primary key(id, name))");
+
+ fillTable();
+
+ List<List<?>> sqlRs = sql("select id, name, age, _key from
PUBLIC.PERSON");
+ sqlRs.sort((o1, o2) -> binaryObjectCmpForDml(o1.get(3), o2.get(3)));
+
+ QueryChecker qryChecker = assertQuery("select /*+ DISABLE_RULE('" +
+ (tableScan ? "LogicalIndexScanConverterRule" :
"LogicalTableScanConverterRule") +
+ "') */ id, name, age, _key from PUBLIC.PERSON order by _key")
+ .matches(tableScan ?
+ QueryChecker.containsTableScan("PUBLIC", "PERSON") :
+ QueryChecker.containsIndexScan("PUBLIC", "PERSON",
PRIMARY_KEY_INDEX)
+ )
+ .columnNames("ID", "NAME", "AGE", KEY_FIELD_NAME);
+
+ sqlRs.forEach(objects ->
qryChecker.returns(objects.toArray(Object[]::new)));
+
+ qryChecker.check();
+ }
+
+ /** */
+ @Test
+ public void testBinaryCompositePkComparisonsWithTableScan() {
+ checkCompositePkWithDifferentCmpOperations(true, true);
+ }
+
+ /** */
+ @Test
+ public void testBinaryCompositePkComparisonsWithIdxScan() {
+ checkCompositePkWithDifferentCmpOperations(true, false);
+ }
+
+ /** */
+ @Test
+ public void testJavaObjCompositePkComparisonsWithTableScan() {
+ checkCompositePkWithDifferentCmpOperations(false, true);
+ }
+
+ /** */
+ @Test
+ public void testJavaObjCompositePkComparisonsWithIdxScan() {
+ checkCompositePkWithDifferentCmpOperations(false, false);
+ }
+
+ /**
+ * Checks composite primary key comparisons with different comparison
operations.
+ * Creates a table with composite key (id, name) using {@link
PersonCompositeKey}, inserts test data,
+ * and verifies that all comparison operations (EQ, NE, LT, LE, GT, GE)
work correctly with both table scan
+ * and index scan query strategies.
+ *
+ * @param useBinaryObject {@code true} to use binary object representation
for key comparison,
+ * {@code false} to use deserialized object.
+ * @param tableScan {@code true} to test with table scan, {@code false} to
test with index scan.
+ */
+ private void checkCompositePkWithDifferentCmpOperations(boolean
useBinaryObject, boolean tableScan) {
+ sql(String.format(
+ "create table PUBLIC.PERSON(id int, name varchar, surname varchar,
age int, primary key(id, name)) with \"key_type=%s\"",
+ PersonCompositeKey.class.getName()
+ ));
+
+ fillTable();
List<List<?>> sqlRs = sql("select id, name, age, _key from
PUBLIC.PERSON order by id");
- BinaryObjectImpl _key8 = (BinaryObjectImpl)sqlRs.get(8).get(3);
- for (CmpOp cmpOp : CmpOp.values()) {
- if (cmpOp == CmpOp.EQ)
- continue;
+ Object key8 = sqlRs.get(8).get(3);
+ for (CmpOp cmpOp : CmpOp.values()) {
List<List<?>> expRows = sqlRs.stream()
- .filter(objects ->
cmpOp.expRowByKeyPred.test((BinaryObjectImpl)objects.get(3), _key8))
- .collect(toList());
+ .filter(objects ->
cmpOp.expRowByKeyPred.test((BinaryObjectImpl)objects.get(3), key8))
+ .toList();
QueryChecker qryChecker = assertQuery(String.format(
- "select id, name, age, _key from PUBLIC.PERSON where _key %s
?", cmpOp.sql
+ "select /*+ DISABLE_RULE('" + (tableScan ?
"LogicalIndexScanConverterRule" : "LogicalTableScanConverterRule") +
Review Comment:
Why disable the rule?
##########
modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/SelectByKeyFieldTest.java:
##########
@@ -304,32 +193,158 @@ private void checkCompositePk(
}
/** */
- private void checkCompositePkWithDifferentCmpOperations(boolean
useBinaryObject) {
+ @Test
+ public void testCompositePkSearchByPartOfKeyTableScan() {
+ compositePkEqualitySearchByPartOfKey(true);
+ }
+
+ /** */
+ @Test
+ public void testCompositePkSearchByPartOfKeyIdxScan() {
+ compositePkEqualitySearchByPartOfKey(false);
+ }
+
+ /**
+ * Tests composite primary key equality search using only part of the key
(single column).
+ * Verifies that queries with equality comparison on either the {@code id}
or {@code name} column
+ * return correct results using either table scan or index scan depending
on the {@code tableScan} flag.
+ *
+ * @param tableScan {@code true} to test with table scan, {@code false} to
test with index scan.
+ */
+ public void compositePkEqualitySearchByPartOfKey(boolean tableScan) {
sql("create table PUBLIC.PERSON(id int, name varchar, surname varchar,
age int, primary key(id, name))");
- for (int i = 0; i < 10; i++) {
- sql(
- "insert into PUBLIC.PERSON(id, name, surname, age) values (?,
?, ?, ?)",
- i, "foo" + i, "bar" + i, 18 + i
- );
- }
+ fillTable();
+
+ List<List<?>> sqlRs = sql("select _key, id, name from PUBLIC.PERSON
order by id");
+ BinaryObject _key = (BinaryObject)sqlRs.get(6).get(0);
+ int id = (Integer)sqlRs.get(6).get(1);
+ String name = (String)sqlRs.get(6).get(2);
+
+ // Select by uniq id.
+ assertQuery("select /*+ DISABLE_RULE('" + (tableScan ?
"LogicalIndexScanConverterRule" : "LogicalTableScanConverterRule") +
+ "') */ id, name, age, _key from PUBLIC.PERSON where id = ?")
+ .withParams(id)
+ .matches(tableScan ?
+ QueryChecker.containsTableScan("PUBLIC", "PERSON") :
+ QueryChecker.containsIndexScan("PUBLIC", "PERSON",
PRIMARY_KEY_INDEX)
+ )
+ .columnNames("ID", "NAME", "AGE", KEY_FIELD_NAME)
+ .returns(id, name, 24, _key)
+ .check();
+
+ // Select by uniq name.
+ assertQuery("select /*+ DISABLE_RULE('" + (tableScan ?
"LogicalIndexScanConverterRule" : "LogicalTableScanConverterRule") +
+ "') */ id, name, age, _key from PUBLIC.PERSON where name = ?")
+ .withParams(name)
+ .matches(tableScan ?
+ QueryChecker.containsTableScan("PUBLIC", "PERSON") :
+ QueryChecker.containsIndexScan("PUBLIC", "PERSON",
PRIMARY_KEY_INDEX)
+ )
+ .columnNames("ID", "NAME", "AGE", KEY_FIELD_NAME)
+ .returns(id, name, 24, _key)
+ .check();
+ }
+
+ /** */
+ @Test
+ public void testCompositePkWithOrderByKeyTableScan() {
+ compositePkWithOrderByKey(true);
+ }
+
+ /** */
+ @Test
+ public void testCompositePkWithOrderByKeyIdxScan() {
+ compositePkWithOrderByKey(false);
+ }
+
+ /**
+ * Tests composite primary key ordering by {@code _key}.
+ * Verifies that ordering by the composite primary key produces correct
results
+ * when comparing binary objects using {@link #binaryObjectCmpForDml}.
+ */
+ public void compositePkWithOrderByKey(boolean tableScan) {
+ sql("create table PUBLIC.PERSON(id int, name varchar, surname varchar,
age int, primary key(id, name))");
+
+ fillTable();
+
+ List<List<?>> sqlRs = sql("select id, name, age, _key from
PUBLIC.PERSON");
+ sqlRs.sort((o1, o2) -> binaryObjectCmpForDml(o1.get(3), o2.get(3)));
+
+ QueryChecker qryChecker = assertQuery("select /*+ DISABLE_RULE('" +
+ (tableScan ? "LogicalIndexScanConverterRule" :
"LogicalTableScanConverterRule") +
+ "') */ id, name, age, _key from PUBLIC.PERSON order by _key")
+ .matches(tableScan ?
+ QueryChecker.containsTableScan("PUBLIC", "PERSON") :
+ QueryChecker.containsIndexScan("PUBLIC", "PERSON",
PRIMARY_KEY_INDEX)
+ )
+ .columnNames("ID", "NAME", "AGE", KEY_FIELD_NAME);
+
+ sqlRs.forEach(objects ->
qryChecker.returns(objects.toArray(Object[]::new)));
+
+ qryChecker.check();
+ }
+
+ /** */
+ @Test
+ public void testBinaryCompositePkComparisonsWithTableScan() {
+ checkCompositePkWithDifferentCmpOperations(true, true);
+ }
+
+ /** */
+ @Test
+ public void testBinaryCompositePkComparisonsWithIdxScan() {
+ checkCompositePkWithDifferentCmpOperations(true, false);
+ }
+
+ /** */
+ @Test
+ public void testJavaObjCompositePkComparisonsWithTableScan() {
+ checkCompositePkWithDifferentCmpOperations(false, true);
+ }
+
+ /** */
+ @Test
+ public void testJavaObjCompositePkComparisonsWithIdxScan() {
+ checkCompositePkWithDifferentCmpOperations(false, false);
+ }
+
+ /**
+ * Checks composite primary key comparisons with different comparison
operations.
+ * Creates a table with composite key (id, name) using {@link
PersonCompositeKey}, inserts test data,
+ * and verifies that all comparison operations (EQ, NE, LT, LE, GT, GE)
work correctly with both table scan
+ * and index scan query strategies.
+ *
+ * @param useBinaryObject {@code true} to use binary object representation
for key comparison,
+ * {@code false} to use deserialized object.
+ * @param tableScan {@code true} to test with table scan, {@code false} to
test with index scan.
+ */
+ private void checkCompositePkWithDifferentCmpOperations(boolean
useBinaryObject, boolean tableScan) {
+ sql(String.format(
+ "create table PUBLIC.PERSON(id int, name varchar, surname varchar,
age int, primary key(id, name)) with \"key_type=%s\"",
+ PersonCompositeKey.class.getName()
+ ));
+
+ fillTable();
List<List<?>> sqlRs = sql("select id, name, age, _key from
PUBLIC.PERSON order by id");
- BinaryObjectImpl _key8 = (BinaryObjectImpl)sqlRs.get(8).get(3);
- for (CmpOp cmpOp : CmpOp.values()) {
- if (cmpOp == CmpOp.EQ)
- continue;
+ Object key8 = sqlRs.get(8).get(3);
+ for (CmpOp cmpOp : CmpOp.values()) {
List<List<?>> expRows = sqlRs.stream()
- .filter(objects ->
cmpOp.expRowByKeyPred.test((BinaryObjectImpl)objects.get(3), _key8))
- .collect(toList());
+ .filter(objects ->
cmpOp.expRowByKeyPred.test((BinaryObjectImpl)objects.get(3), key8))
Review Comment:
Why are BO and not POJO unquestionably expected here, as opposed to in other
scenarios?
##########
modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/util/TypeUtils.java:
##########
@@ -355,12 +355,12 @@ public static boolean hasScale(RelDataType type) {
}
/** */
- public static Object toInternal(DataContext ctx, Object val) {
+ public static @Nullable Object toInternal(DataContext ctx, Object val) {
Review Comment:
Forgot `@Nullable Object val`.
##########
modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/util/TypeUtils.java:
##########
@@ -355,12 +355,12 @@ public static boolean hasScale(RelDataType type) {
}
/** */
- public static Object toInternal(DataContext ctx, Object val) {
+ public static @Nullable Object toInternal(DataContext ctx, Object val) {
return val == null ? null : toInternal(ctx, val, val.getClass());
}
/** */
- public static Object toInternal(DataContext ctx, Object val, Type
storageType) {
+ public static @Nullable Object toInternal(DataContext ctx, Object val,
Type storageType) {
Review Comment:
Forgot `@Nullable Object val`.
##########
modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/ExecutionContext.java:
##########
@@ -302,11 +314,16 @@ public IgniteLogger logger() {
return baseDataContext.get(name);
}
- /** */
+ /** Returns a parameter with internal representation or {@link
BinaryObject}. */
public Object getParameter(String name, Type storageType) {
assert name.startsWith("?") : name;
- return TypeUtils.toInternal(this, params.get(name), storageType);
+ Object param = params.get(name);
+
+ if (param != null && storageType == java.lang.Object.class && !(param
instanceof BinaryObject))
+ return binaryMarshaller.apply(param);
Review Comment:
Do I understand correctly that parameters will now be unconditionally
converted to a BO?
For instance, if a UDF or UDTF expects a specific parameter, will it receive
a BO instead?
If that is the case, it is not very convenient, and users might come to rely
on passing and receiving POJO in functions exactly as they are.
##########
modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/ExecutionServiceImpl.java:
##########
@@ -472,6 +475,10 @@ public void injectService(InjectResourcesService
injectSvc) {
udfQryLimit.set(ctx.config().getQueryThreadPoolSize() - 1);
+ binaryMarshaller = obj -> {
+ return ctx.cacheObjects().binary().toBinary(obj);
+ };
Review Comment:
```suggestion
binaryMarshaller = obj -> ctx.cacheObjects().binary().toBinary(obj);
```
##########
modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/ExecutionServiceImpl.java:
##########
@@ -210,6 +210,9 @@ public class ExecutionServiceImpl<Row> extends
AbstractService implements Execut
/** */
private final Map<String, FragmentPlan> fragmentPlanCache = new
GridBoundedConcurrentLinkedHashMap<>(1024);
+ /** Binary marshaller. */
Review Comment:
Same about documentation.
##########
modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/TableFunctionScan.java:
##########
@@ -36,34 +37,57 @@ public class TableFunctionScan<Row> implements
Iterable<Row> {
/** */
private final RowFactory<Row> rowFactory;
+ /** */
+ Function<Object, Object> binaryMarshaller;
+
+ /** */
+ private static final String ERR_SIZE_TEMPLATE = "Unable to process table
function data: row length [%d]" +
+ " doesn't match defined columns number [%d].";
+
/** */
public TableFunctionScan(
RelDataType rowType,
Supplier<Iterable<?>> dataSupplier,
- RowFactory<Row> rowFactory
+ RowFactory<Row> rowFactory,
+ Function<Object, Object> marshaller
) {
this.rowType = rowType;
this.dataSupplier = dataSupplier;
this.rowFactory = rowFactory;
+ binaryMarshaller = marshaller;
}
/** {@inheritDoc} */
@Override public Iterator<Row> iterator() {
return F.iterator(dataSupplier.get(), this::convertToRow, true);
}
+ /** */
+ private static void rowSizeChecker(int rowSize, int fldCount) {
+ if (rowSize != fldCount)
+ throw new IgniteSQLException(ERR_SIZE_TEMPLATE.formatted(rowSize,
fldCount));
+ }
+
/** */
private Row convertToRow(Object rowContainer) {
if (rowContainer.getClass() != Object[].class &&
!Collection.class.isAssignableFrom(rowContainer.getClass()))
throw new IgniteSQLException("Unable to process table function
data: row type is neither Collection or Object[].");
- Object[] rowArr = rowContainer.getClass() == Object[].class
- ? (Object[])rowContainer
- : ((Collection<?>)rowContainer).toArray();
+ if (rowContainer instanceof Object[])
+ rowSizeChecker(((Object[])rowContainer).length,
rowType.getFieldCount());
+ else
+ rowSizeChecker(((Collection<?>)rowContainer).size(),
rowType.getFieldCount());
- if (rowArr.length != rowType.getFieldCount()) {
- throw new IgniteSQLException("Unable to process table function
data: row length [" + rowArr.length
- + "] doesn't match defined columns number [" +
rowType.getFieldCount() + "].");
+ Object[] rowArr;
Review Comment:
Do I understand correctly that there will be an unconditional conversion
into a BO of the result string returned by the query (conceptually, the
ResultSet)? If so, why?
##########
modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/TableFunctionScan.java:
##########
@@ -36,34 +37,57 @@ public class TableFunctionScan<Row> implements
Iterable<Row> {
/** */
private final RowFactory<Row> rowFactory;
+ /** */
+ Function<Object, Object> binaryMarshaller;
+
+ /** */
+ private static final String ERR_SIZE_TEMPLATE = "Unable to process table
function data: row length [%d]" +
+ " doesn't match defined columns number [%d].";
+
/** */
public TableFunctionScan(
RelDataType rowType,
Supplier<Iterable<?>> dataSupplier,
- RowFactory<Row> rowFactory
+ RowFactory<Row> rowFactory,
+ Function<Object, Object> marshaller
) {
this.rowType = rowType;
this.dataSupplier = dataSupplier;
this.rowFactory = rowFactory;
+ binaryMarshaller = marshaller;
}
/** {@inheritDoc} */
@Override public Iterator<Row> iterator() {
return F.iterator(dataSupplier.get(), this::convertToRow, true);
}
+ /** */
+ private static void rowSizeChecker(int rowSize, int fldCount) {
+ if (rowSize != fldCount)
+ throw new IgniteSQLException(ERR_SIZE_TEMPLATE.formatted(rowSize,
fldCount));
+ }
+
/** */
private Row convertToRow(Object rowContainer) {
if (rowContainer.getClass() != Object[].class &&
!Collection.class.isAssignableFrom(rowContainer.getClass()))
throw new IgniteSQLException("Unable to process table function
data: row type is neither Collection or Object[].");
- Object[] rowArr = rowContainer.getClass() == Object[].class
- ? (Object[])rowContainer
- : ((Collection<?>)rowContainer).toArray();
+ if (rowContainer instanceof Object[])
+ rowSizeChecker(((Object[])rowContainer).length,
rowType.getFieldCount());
+ else
+ rowSizeChecker(((Collection<?>)rowContainer).size(),
rowType.getFieldCount());
- if (rowArr.length != rowType.getFieldCount()) {
- throw new IgniteSQLException("Unable to process table function
data: row length [" + rowArr.length
- + "] doesn't match defined columns number [" +
rowType.getFieldCount() + "].");
+ Object[] rowArr;
+
+ if (rowContainer.getClass().isArray()) {
+ rowArr = (Object[])rowContainer;
+ for (int pos = 0; pos < rowArr.length; ++pos)
+ rowArr[pos] = binaryMarshaller.apply(rowArr[pos]);
+ }
+ else {
+ Collection<?> coll = (Collection<?>)rowContainer;
+ rowArr = coll.stream().map(e ->
binaryMarshaller.apply(e)).toArray();
Review Comment:
Won't this in "hot code"? Perhaps using a loop would be better?
##########
modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/IndexWrappedKeyScan.java:
##########
@@ -54,7 +54,7 @@ public IndexWrappedKeyScan(
}
/** */
- @Override protected IndexRow row2indexRow(Row bound) {
+ @Override @Nullable protected IndexRow row2indexRow(Row bound) {
Review Comment:
Forgot `@Nullable Row bound`.
##########
modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/IndexWrappedKeyScan.java:
##########
@@ -63,22 +63,29 @@ public IndexWrappedKeyScan(
Object key = rowHnd.get(QueryUtils.KEY_COL, bound);
assert key != null : String.format("idxName=%s, bound=%s", idx.name(),
Commons.toString(rowHnd, bound));
- if (key instanceof BinaryObject)
- return binaryObject2indexRow((BinaryObject)key);
+ String idxTypeName =
idx.indexDefinition().typeDescriptor().keyTypeName();
- throw new IgniteException(String.format(
- "Unsupported type for index boundary: [expected=%s, current=%s]",
- BinaryObject.class.getName(), key.getClass().getName()
- ));
+ if (key instanceof BinaryObject bo) {
+ try {
+ String searchTypeName = bo.type().typeName();
+
+ if (!idxTypeName.equals(searchTypeName))
+ // The same behavior as for table scan.
+ return null;
+ }
+ catch (BinaryObjectException ex) {
+ // The same behavior as for table scan.
Review Comment:
Where exactly is the behavior the same?
##########
modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/ExecutionContext.java:
##########
@@ -148,10 +149,14 @@ public class ExecutionContext<Row> extends
AbstractQueryContext implements DataC
/** Entries holder per execution thread. */
private static final ThreadLocal<Collection<QueryTxEntry>> txEntriesHolder
= new ThreadLocal<>();
+ /** Binary marshaller. */
Review Comment:
There isn't enough documentation—can it convert to and from BO, or only one
way?
##########
modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/SelectByKeyFieldTest.java:
##########
@@ -304,32 +193,158 @@ private void checkCompositePk(
}
/** */
- private void checkCompositePkWithDifferentCmpOperations(boolean
useBinaryObject) {
+ @Test
+ public void testCompositePkSearchByPartOfKeyTableScan() {
+ compositePkEqualitySearchByPartOfKey(true);
+ }
+
+ /** */
+ @Test
+ public void testCompositePkSearchByPartOfKeyIdxScan() {
+ compositePkEqualitySearchByPartOfKey(false);
+ }
+
+ /**
+ * Tests composite primary key equality search using only part of the key
(single column).
+ * Verifies that queries with equality comparison on either the {@code id}
or {@code name} column
+ * return correct results using either table scan or index scan depending
on the {@code tableScan} flag.
+ *
+ * @param tableScan {@code true} to test with table scan, {@code false} to
test with index scan.
+ */
+ public void compositePkEqualitySearchByPartOfKey(boolean tableScan) {
sql("create table PUBLIC.PERSON(id int, name varchar, surname varchar,
age int, primary key(id, name))");
- for (int i = 0; i < 10; i++) {
- sql(
- "insert into PUBLIC.PERSON(id, name, surname, age) values (?,
?, ?, ?)",
- i, "foo" + i, "bar" + i, 18 + i
- );
- }
+ fillTable();
+
+ List<List<?>> sqlRs = sql("select _key, id, name from PUBLIC.PERSON
order by id");
+ BinaryObject _key = (BinaryObject)sqlRs.get(6).get(0);
+ int id = (Integer)sqlRs.get(6).get(1);
+ String name = (String)sqlRs.get(6).get(2);
+
+ // Select by uniq id.
+ assertQuery("select /*+ DISABLE_RULE('" + (tableScan ?
"LogicalIndexScanConverterRule" : "LogicalTableScanConverterRule") +
+ "') */ id, name, age, _key from PUBLIC.PERSON where id = ?")
+ .withParams(id)
+ .matches(tableScan ?
+ QueryChecker.containsTableScan("PUBLIC", "PERSON") :
+ QueryChecker.containsIndexScan("PUBLIC", "PERSON",
PRIMARY_KEY_INDEX)
+ )
+ .columnNames("ID", "NAME", "AGE", KEY_FIELD_NAME)
+ .returns(id, name, 24, _key)
+ .check();
+
+ // Select by uniq name.
+ assertQuery("select /*+ DISABLE_RULE('" + (tableScan ?
"LogicalIndexScanConverterRule" : "LogicalTableScanConverterRule") +
+ "') */ id, name, age, _key from PUBLIC.PERSON where name = ?")
+ .withParams(name)
+ .matches(tableScan ?
+ QueryChecker.containsTableScan("PUBLIC", "PERSON") :
+ QueryChecker.containsIndexScan("PUBLIC", "PERSON",
PRIMARY_KEY_INDEX)
+ )
+ .columnNames("ID", "NAME", "AGE", KEY_FIELD_NAME)
+ .returns(id, name, 24, _key)
+ .check();
+ }
+
+ /** */
+ @Test
+ public void testCompositePkWithOrderByKeyTableScan() {
+ compositePkWithOrderByKey(true);
+ }
+
+ /** */
+ @Test
+ public void testCompositePkWithOrderByKeyIdxScan() {
+ compositePkWithOrderByKey(false);
+ }
+
+ /**
+ * Tests composite primary key ordering by {@code _key}.
+ * Verifies that ordering by the composite primary key produces correct
results
+ * when comparing binary objects using {@link #binaryObjectCmpForDml}.
+ */
+ public void compositePkWithOrderByKey(boolean tableScan) {
+ sql("create table PUBLIC.PERSON(id int, name varchar, surname varchar,
age int, primary key(id, name))");
+
+ fillTable();
+
+ List<List<?>> sqlRs = sql("select id, name, age, _key from
PUBLIC.PERSON");
+ sqlRs.sort((o1, o2) -> binaryObjectCmpForDml(o1.get(3), o2.get(3)));
+
+ QueryChecker qryChecker = assertQuery("select /*+ DISABLE_RULE('" +
Review Comment:
Why disable the rule?
##########
modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/SelectByKeyFieldTest.java:
##########
@@ -304,32 +193,158 @@ private void checkCompositePk(
}
/** */
- private void checkCompositePkWithDifferentCmpOperations(boolean
useBinaryObject) {
+ @Test
+ public void testCompositePkSearchByPartOfKeyTableScan() {
+ compositePkEqualitySearchByPartOfKey(true);
+ }
+
+ /** */
+ @Test
+ public void testCompositePkSearchByPartOfKeyIdxScan() {
+ compositePkEqualitySearchByPartOfKey(false);
+ }
+
+ /**
+ * Tests composite primary key equality search using only part of the key
(single column).
+ * Verifies that queries with equality comparison on either the {@code id}
or {@code name} column
+ * return correct results using either table scan or index scan depending
on the {@code tableScan} flag.
+ *
+ * @param tableScan {@code true} to test with table scan, {@code false} to
test with index scan.
+ */
+ public void compositePkEqualitySearchByPartOfKey(boolean tableScan) {
sql("create table PUBLIC.PERSON(id int, name varchar, surname varchar,
age int, primary key(id, name))");
- for (int i = 0; i < 10; i++) {
- sql(
- "insert into PUBLIC.PERSON(id, name, surname, age) values (?,
?, ?, ?)",
- i, "foo" + i, "bar" + i, 18 + i
- );
- }
+ fillTable();
+
+ List<List<?>> sqlRs = sql("select _key, id, name from PUBLIC.PERSON
order by id");
+ BinaryObject _key = (BinaryObject)sqlRs.get(6).get(0);
+ int id = (Integer)sqlRs.get(6).get(1);
+ String name = (String)sqlRs.get(6).get(2);
+
+ // Select by uniq id.
+ assertQuery("select /*+ DISABLE_RULE('" + (tableScan ?
"LogicalIndexScanConverterRule" : "LogicalTableScanConverterRule") +
+ "') */ id, name, age, _key from PUBLIC.PERSON where id = ?")
+ .withParams(id)
+ .matches(tableScan ?
+ QueryChecker.containsTableScan("PUBLIC", "PERSON") :
+ QueryChecker.containsIndexScan("PUBLIC", "PERSON",
PRIMARY_KEY_INDEX)
+ )
+ .columnNames("ID", "NAME", "AGE", KEY_FIELD_NAME)
+ .returns(id, name, 24, _key)
+ .check();
+
+ // Select by uniq name.
+ assertQuery("select /*+ DISABLE_RULE('" + (tableScan ?
"LogicalIndexScanConverterRule" : "LogicalTableScanConverterRule") +
+ "') */ id, name, age, _key from PUBLIC.PERSON where name = ?")
+ .withParams(name)
+ .matches(tableScan ?
+ QueryChecker.containsTableScan("PUBLIC", "PERSON") :
+ QueryChecker.containsIndexScan("PUBLIC", "PERSON",
PRIMARY_KEY_INDEX)
+ )
+ .columnNames("ID", "NAME", "AGE", KEY_FIELD_NAME)
+ .returns(id, name, 24, _key)
+ .check();
+ }
+
+ /** */
+ @Test
+ public void testCompositePkWithOrderByKeyTableScan() {
+ compositePkWithOrderByKey(true);
+ }
+
+ /** */
+ @Test
+ public void testCompositePkWithOrderByKeyIdxScan() {
+ compositePkWithOrderByKey(false);
+ }
+
+ /**
+ * Tests composite primary key ordering by {@code _key}.
+ * Verifies that ordering by the composite primary key produces correct
results
+ * when comparing binary objects using {@link #binaryObjectCmpForDml}.
+ */
+ public void compositePkWithOrderByKey(boolean tableScan) {
+ sql("create table PUBLIC.PERSON(id int, name varchar, surname varchar,
age int, primary key(id, name))");
+
+ fillTable();
+
+ List<List<?>> sqlRs = sql("select id, name, age, _key from
PUBLIC.PERSON");
+ sqlRs.sort((o1, o2) -> binaryObjectCmpForDml(o1.get(3), o2.get(3)));
Review Comment:
Why are BO compared instead of PDJO?
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]