This is an automated email from the ASF dual-hosted git repository.
tkalkirill pushed a commit to branch ignite-28223-add-rowid
in repository https://gitbox.apache.org/repos/asf/ignite.git
The following commit(s) were added to refs/heads/ignite-28223-add-rowid by this
push:
new d7f08aa28e3 IGNITE-28223-add-rowid wip
d7f08aa28e3 is described below
commit d7f08aa28e32d5c70bb75ce54ef2489fa1a16d97
Author: Kirill Tkalenko <[email protected]>
AuthorDate: Mon Mar 23 10:32:37 2026 +0300
IGNITE-28223-add-rowid wip
---
.../calcite/integration/RowIdPseudoColumnTest.java | 233 +++++++++++++++++++--
1 file changed, 211 insertions(+), 22 deletions(-)
diff --git
a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/RowIdPseudoColumnTest.java
b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/RowIdPseudoColumnTest.java
index abf07392f40..3707200d081 100644
---
a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/RowIdPseudoColumnTest.java
+++
b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/RowIdPseudoColumnTest.java
@@ -17,10 +17,15 @@
package org.apache.ignite.internal.processors.query.calcite.integration;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.util.Base64;
import java.util.List;
import org.apache.calcite.adapter.enumerable.NullPolicy;
import org.apache.calcite.linq4j.tree.Expressions;
-import org.apache.calcite.linq4j.tree.UnaryExpression;
import org.apache.calcite.sql.SqlBasicCall;
import org.apache.calcite.sql.SqlCall;
import org.apache.calcite.sql.SqlFunction;
@@ -32,13 +37,14 @@ import org.apache.calcite.sql.fun.SqlStdOperatorTable;
import org.apache.calcite.sql.parser.SqlParserPos;
import org.apache.calcite.sql.type.OperandTypes;
import org.apache.calcite.sql.type.ReturnTypes;
+import org.apache.calcite.sql.type.SqlTypeName;
import org.apache.calcite.sql.util.ReflectiveSqlOperatorTable;
import org.apache.calcite.sql.util.SqlOperatorTables;
import org.apache.calcite.sql.validate.SqlValidator;
import org.apache.calcite.tools.FrameworkConfig;
import org.apache.calcite.tools.Frameworks;
import org.apache.ignite.IgniteCheckedException;
-import org.apache.ignite.Ignition;
+import org.apache.ignite.IgniteException;
import org.apache.ignite.calcite.CalciteQueryEngineConfiguration;
import org.apache.ignite.calcite.PseudoColumnDescriptor;
import org.apache.ignite.calcite.PseudoColumnProvider;
@@ -48,12 +54,17 @@ import org.apache.ignite.configuration.SqlConfiguration;
import org.apache.ignite.indexing.IndexingQueryEngineConfiguration;
import org.apache.ignite.internal.IgniteEx;
import org.apache.ignite.internal.IgnitionEx;
+import org.apache.ignite.internal.processors.cache.CacheObjectContext;
import org.apache.ignite.internal.processors.query.QueryUtils;
import
org.apache.ignite.internal.processors.query.calcite.CalciteQueryProcessor;
+import
org.apache.ignite.internal.processors.query.calcite.PseudoColumnValueExtractorContextEx;
import org.apache.ignite.internal.processors.query.calcite.QueryChecker;
import
org.apache.ignite.internal.processors.query.calcite.exec.ExecutionContext;
import
org.apache.ignite.internal.processors.query.calcite.exec.exp.RexImpTable;
import
org.apache.ignite.internal.processors.query.calcite.prepare.IgniteSqlCallRewriteTable;
+import org.apache.ignite.internal.util.tostring.GridToStringInclude;
+import org.apache.ignite.internal.util.typedef.internal.S;
+import org.apache.ignite.internal.util.typedef.internal.U;
import org.apache.ignite.plugin.AbstractTestPluginProvider;
import org.apache.ignite.plugin.PluginContext;
import org.jetbrains.annotations.Nullable;
@@ -85,18 +96,76 @@ public class RowIdPseudoColumnTest extends
AbstractBasicIntegrationTest {
for (int i = 0; i < 2; i++)
sql("insert into PUBLIC.PERSON(id, name) values(?, ?)", i, "foo" +
i);
- assertQuery("select id, name, rowid from PUBLIC.PERSON where rowid =
'0'")
+ List<List<?>> selectRs = sql("select rowid from PUBLIC.PERSON order by
id");
+ Object rowId0 = selectRs.get(0).get(0);
+ Object rowId1 = selectRs.get(1).get(0);
+
+ assertQuery("select id, name, rowid from PUBLIC.PERSON where rowid =
?")
+ .withParams(rowId0)
+ .columnNames("ID", "NAME", "ROWID")
+ .matches(QueryChecker.containsIndexScan("PUBLIC", "PERSON",
"_key_PK"))
+ .returns(0, "foo0", rowId0)
+ .check();
+
+ assertQuery("select p.id, p.name, p.rowid from PUBLIC.PERSON as p
where p.rowid = ?")
+ .withParams(rowId1)
.columnNames("ID", "NAME", "ROWID")
.matches(QueryChecker.containsIndexScan("PUBLIC", "PERSON",
"_key_PK"))
- .returns(0, "foo0", "0")
+ .returns(1, "foo1", rowId1)
+ .check();
+ }
+
+ @Test
+ public void testCompositePrimaryKey() {
+ sql("create table PUBLIC.PERSON(id int, name varchar, age int, primary
key (id, name))");
+
+ for (int i = 0; i < 2; i++)
+ sql("insert into PUBLIC.PERSON(id, name, age) values(?, ?, ?)", i,
"foo" + i, 18 + i);
+
+ List<List<?>> selectRs = sql("select rowid, _key from PUBLIC.PERSON
order by id");
+ Object rowId0 = selectRs.get(0).get(0);
+ Object rowId1 = selectRs.get(1).get(0);
+
+ // TODO: IGNITE-28223-add-rowid Вот тут проблема, для составных ПК он
не умеет пока делать хитрости в начале
+ // надо будет попровить _key для бинарного объекта а потом уже сюда
вернуться
+ assertQuery("select id, name, age, rowid from PUBLIC.PERSON where
rowid = ?")
+ .withParams(rowId0)
+ .columnNames("ID", "NAME", "AGE", "ROWID")
+ .matches(QueryChecker.containsIndexScan("PUBLIC", "PERSON",
"_key_PK"))
+ .returns(0, "foo0", 18, rowId0)
+ .check();
+
+ assertQuery("select p.id, p.name, p.age, p.rowid from PUBLIC.PERSON
where p.rowid = ?")
+ .withParams(rowId1)
+ .columnNames("ID", "NAME", "AGE", "ROWID")
+ .matches(QueryChecker.containsIndexScan("PUBLIC", "PERSON",
"_key_PK"))
+ .returns(1, "foo1", 19, rowId1)
.check();
}
/** */
- public static @Nullable Integer toKeyFromRowId(ExecutionContext<?> ctx,
@Nullable String rowId) {
- IgniteEx n = (IgniteEx) Ignition.ignite(ctx.localNodeId());
+ public static @Nullable Object toKeyFromRowId(ExecutionContext<?> ctx,
@Nullable String rowIdBase64) {
+ if (rowIdBase64 == null)
+ return null;
+
+ RowId rowId = RowId.fromBase64String(rowIdBase64);
- return rowId == null ? null : Integer.parseInt(rowId);
+ // TODO: IGNITE-28223-add-rowid Вот тут сейчас линейный поиск, надо бы
переделать, идеи:
+ // 1. В ExecutionContext положить IgniteEx - думаю самое простое и
лучшее
+ // 2. В org.apache.ignite.internal.IgnitionEx.grid(java.util.UUID)
сделать map UUID -> IgniteEx - думаю хуже
+ IgniteEx n = (IgniteEx) IgnitionEx.grid(ctx.localNodeId());
+
+ CacheObjectContext coctx =
n.context().cache().cacheGroup(rowId.cacheGrpId).cacheObjectContext();
+
+ byte[] bs = coctx.restoreIfNecessary(rowId.valBytes);
+
+ try {
+ return coctx.unmarshal(bs, null);
+ } catch (IgniteCheckedException e) {
+ throw new IgniteException(
+ String.format("Failed to unmarshal %s: [rowIdBase64=%s]",
RowId.class.getSimpleName(), rowIdBase64), e
+ );
+ }
}
/** */
@@ -128,9 +197,9 @@ public class RowIdPseudoColumnTest extends
AbstractBasicIntegrationTest {
RowIdPseudoColumnOperatorTable.TO_KEY_FROM_ROW_ID,
RexImpTable.createRexCallImplementor((translator, call,
translatedOperands) -> {
var str = Expressions.convert_(translatedOperands.get(0),
String.class);
- var execCtx = Expressions.convert_(translator.getRoot(),
ExecutionContext.class);
+ var ectx = Expressions.convert_(translator.getRoot(),
ExecutionContext.class);
- return Expressions.call(RowIdPseudoColumnTest.class,
"toKeyFromRowId", execCtx, str);
+ return Expressions.call(RowIdPseudoColumnTest.class,
"toKeyFromRowId", ectx, str);
}, NullPolicy.ANY, false)
);
@@ -151,24 +220,55 @@ public class RowIdPseudoColumnTest extends
AbstractBasicIntegrationTest {
SqlNode left = operands.get(0);
SqlNode right = operands.get(1);
- if (!isRowId(left))
+ SqlIdentifier rowIdId = rowIdIdentifier(left);
+ SqlNode rowIdVal = right;
+
+ if (rowIdId == null) {
+ rowIdId = rowIdIdentifier(right);
+ rowIdVal = left;
+ }
+
+ if (rowIdId == null)
return call;
SqlParserPos pos = call.getParserPosition();
- SqlIdentifier keyId = new SqlIdentifier(QueryUtils.KEY_FIELD_NAME,
pos);
- SqlCall toKey =
RowIdPseudoColumnOperatorTable.TO_KEY_FROM_ROW_ID.createCall(pos, right);
+ SqlIdentifier keyId = toKeyIdentifier(rowIdId, pos);
+ SqlCall toKey =
RowIdPseudoColumnOperatorTable.TO_KEY_FROM_ROW_ID.createCall(pos, rowIdVal);
return new SqlBasicCall(SqlStdOperatorTable.EQUALS, List.of(keyId,
toKey), pos);
}
/** */
- private static boolean isRowId(SqlNode node) {
- if (!(node instanceof SqlIdentifier))
- return false;
+ private static @Nullable SqlIdentifier rowIdIdentifier(SqlNode node) {
+ // Validator may wrap identifier with CAST/other unary calls during
type coercion.
+ // Unwrap a simple CAST chain first to still recognize ROWID.
+ SqlNode cur = node;
+
+ while (cur instanceof SqlCall) {
+ SqlCall c = (SqlCall)cur;
- SqlIdentifier identifier = (SqlIdentifier)node;
+ if (c.getKind() != SqlKind.CAST || c.operandCount() < 1)
+ break;
+
+ cur = c.operand(0);
+ }
+
+ if (!(cur instanceof SqlIdentifier))
+ return null;
+
+ SqlIdentifier identifier = (SqlIdentifier)cur;
+
+ String lastName = identifier.names.get(identifier.names.size() - 1);
+
+ return RowIdPseudoColumn.COLUMN_NAME.equalsIgnoreCase(lastName) ?
identifier : null;
+ }
+
+ /** */
+ private static SqlIdentifier toKeyIdentifier(SqlIdentifier rowIdId,
SqlParserPos pos) {
+ if (rowIdId.names.size() == 1)
+ return new SqlIdentifier(QueryUtils.KEY_FIELD_NAME, pos);
- return identifier.names.size() == 1 &&
"ROWID".equalsIgnoreCase(identifier.getSimple());
+ return rowIdId.skipLast(1).plus(QueryUtils.KEY_FIELD_NAME, pos);
}
/** */
@@ -177,18 +277,26 @@ public class RowIdPseudoColumnTest extends
AbstractBasicIntegrationTest {
public static final SqlFunction TO_KEY_FROM_ROW_ID = new SqlFunction(
"TO_KEY_FROM_ROW_ID",
SqlKind.OTHER_FUNCTION,
- ReturnTypes.INTEGER,
+ ReturnTypes.explicit(SqlTypeName.ANY),
null,
- OperandTypes.STRING,
+ OperandTypes.ANY,
SqlFunctionCategory.USER_DEFINED_FUNCTION
- );
+ ) {
+ /** {@inheritDoc} */
+ @Override public boolean isDeterministic() {
+ return false; // важно: чтобы Calcite не константно сворачивал
в Java-объект
+ }
+ };
}
/** */
private static class RowIdPseudoColumn implements PseudoColumnDescriptor {
+ /** */
+ private static final String COLUMN_NAME = "ROWID";
+
/** {@inheritDoc} */
@Override public String name() {
- return "ROWID";
+ return COLUMN_NAME;
}
/** {@inheritDoc} */
@@ -208,7 +316,88 @@ public class RowIdPseudoColumnTest extends
AbstractBasicIntegrationTest {
/** {@inheritDoc} */
@Override public Object value(PseudoColumnValueExtractorContext ctx)
throws IgniteCheckedException {
- return ctx.source(true, true).toString();
+ RowId rowId = RowId.of((PseudoColumnValueExtractorContextEx) ctx);
+
+ return RowId.toBase64String(rowId);
+ }
+ }
+
+ /** */
+ private static class RowId {
+ /** */
+ @GridToStringInclude
+ private final int cacheGrpId;
+
+ /** */
+ @GridToStringInclude
+ private final byte[] valBytes;
+
+ /** */
+ private RowId(int cacheGrpId, byte[] valBytes) {
+ this.cacheGrpId = cacheGrpId;
+ this.valBytes = valBytes;
+ }
+
+ /** {@inheritDoc} */
+ @Override public String toString() {
+ return S.toString(RowId.class, this);
+ }
+
+ /** */
+ private static RowId of(PseudoColumnValueExtractorContextEx ctx) {
+ byte[] valBytes;
+
+ try {
+ valBytes =
ctx.source().key().valueBytes(ctx.cacheCtx().cacheObjectContext());
+ }
+ catch (IgniteCheckedException e) {
+ throw new IgniteException(
+ String.format(
+ "Failed to get valBytes for %s: [cacheName=%s,
key=%s]",
+ RowId.class.getSimpleName(), ctx.cacheCtx().name(),
ctx.source(true, false)),
+ e
+ );
+ }
+
+ return new RowId(ctx.cacheCtx().groupId(), valBytes);
+ }
+
+ /** */
+ private static String toBase64String(RowId rowId) {
+ byte[] bs;
+
+ try (
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ DataOutputStream dos = new DataOutputStream(baos)
+ ) {
+ dos.writeInt(rowId.cacheGrpId);
+ U.writeByteArray(dos, rowId.valBytes);
+
+ bs = baos.toByteArray();
+ } catch (IOException e) {
+ throw new IgniteException(
+ String.format("Failed to serialize %s: [value=%s]",
RowId.class.getSimpleName(), rowId), e
+ );
+ }
+
+ return Base64.getEncoder().encodeToString(bs);
+ }
+
+ /** */
+ private static RowId fromBase64String(String s) {
+ byte[] decoded = Base64.getDecoder().decode(s);
+
+ try (
+ ByteArrayInputStream bais = new ByteArrayInputStream(decoded);
+ DataInputStream dis = new DataInputStream(bais)
+ ) {
+ return new RowId(dis.readInt(), U.readByteArray(dis));
+ }
+ catch (IOException e) {
+ throw new IgniteException(
+ String.format("Failed to deserialize %s: [value=%s]",
RowId.class.getSimpleName(), s), e
+ );
+ }
}
}
}