From 129b1c7aef257d018835e4ee43be68c0235c8a68 Mon Sep 17 00:00:00 2001 From: Kirill Tkalenko Date: Wed, 2 Sep 2026 21:34:32 +0300 Subject: [PATCH 1/2] IGNITE-29034 Wip --- .../query/calcite/schema/IgniteSchema.java | 18 +++++ .../OperatorsExtensionIntegrationTest.java | 68 ++++++++++++++++++- 2 files changed, 85 insertions(+), 1 deletion(-) diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/schema/IgniteSchema.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/schema/IgniteSchema.java index 6d22ac2d51d33..1882b1bb96f69 100644 --- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/schema/IgniteSchema.java +++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/schema/IgniteSchema.java @@ -28,6 +28,7 @@ import org.apache.calcite.schema.FunctionParameter; import org.apache.calcite.schema.SchemaPlus; import org.apache.calcite.schema.Table; +import org.apache.calcite.schema.TableMacro; import org.apache.calcite.schema.impl.AbstractSchema; import org.apache.calcite.tools.FrameworkConfig; import org.apache.ignite.IgniteException; @@ -37,6 +38,12 @@ * Ignite schema. */ public class IgniteSchema extends AbstractSchema { + /** */ + private static final String DUAL_TBL_NAME = "DUAL"; + + /** */ + private static final String DUAL_TBL_VIEW = "SELECT 'X' AS DUMMY"; + /** */ private final String schemaName; @@ -143,6 +150,17 @@ public SchemaPlus register(SchemaPlus parent, FrameworkConfig frameworkCfg) { viewMap.forEach((name, sql) -> newSchema.add(name, new ViewTableMacroImpl(sql, newSchema, frameworkCfg))); + registerDualTableIfSupported(newSchema, frameworkCfg); + return newSchema; } + + /** */ + private static void registerDualTableIfSupported(SchemaPlus schema, FrameworkConfig frameworkCfg) { + if (frameworkCfg != null && frameworkCfg.getParserConfig().conformance().isSupportedDualTable() + && schema.tables().get(DUAL_TBL_NAME) == null + && schema.getFunctions(DUAL_TBL_NAME).stream().noneMatch(TableMacro.class::isInstance)) { + schema.add(DUAL_TBL_NAME, new ViewTableMacroImpl(DUAL_TBL_VIEW, schema, frameworkCfg)); + } + } } diff --git a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/OperatorsExtensionIntegrationTest.java b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/OperatorsExtensionIntegrationTest.java index 35a6c1bf96cb9..0b697edde2cd0 100644 --- a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/OperatorsExtensionIntegrationTest.java +++ b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/OperatorsExtensionIntegrationTest.java @@ -50,6 +50,8 @@ 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.SqlConformance; +import org.apache.calcite.sql.validate.SqlDelegatingConformance; import org.apache.calcite.sql.validate.SqlValidator; import org.apache.calcite.sql2rel.SqlRexContext; import org.apache.calcite.sql2rel.SqlRexConvertlet; @@ -57,6 +59,7 @@ import org.apache.calcite.tools.Frameworks; import org.apache.calcite.util.BuiltInMethod; import org.apache.calcite.util.Optionality; +import org.apache.ignite.configuration.CacheConfiguration; import org.apache.ignite.configuration.IgniteConfiguration; import org.apache.ignite.internal.processors.query.calcite.CalciteQueryProcessor; import org.apache.ignite.internal.processors.query.calcite.exec.ExecutionContext; @@ -79,6 +82,15 @@ * Tests SQL engine extension with plugin. */ public class OperatorsExtensionIntegrationTest extends AbstractBasicIntegrationTest { + /** */ + private static final SqlConformance TEST_CONFORMANCE = new SqlDelegatingConformance( + CalciteQueryProcessor.FRAMEWORK_CONFIG.getParserConfig().conformance()) { + /** {@inheritDoc} */ + @Override public boolean isSupportedDualTable() { + return true; + } + }; + /** {@inheritDoc} */ @Override protected IgniteConfiguration getConfiguration(String igniteInstanceName) throws Exception { return super.getConfiguration(igniteInstanceName) @@ -90,12 +102,15 @@ public class OperatorsExtensionIntegrationTest extends AbstractBasicIntegrationT @Override public @Nullable T createComponent(PluginContext ctx, Class cls) { if (FrameworkConfig.class.equals(cls)) { FrameworkConfig cfg = Frameworks.newConfigBuilder(CalciteQueryProcessor.FRAMEWORK_CONFIG) + .parserConfig(CalciteQueryProcessor.FRAMEWORK_CONFIG.getParserConfig() + .withConformance(TEST_CONFORMANCE)) .convertletTable(new ConvertletTable()) .operatorTable(SqlOperatorTables.chain( new OperatorTable().init(), CalciteQueryProcessor.FRAMEWORK_CONFIG.getOperatorTable())) .sqlValidatorConfig( ((IgniteSqlValidator.Config)CalciteQueryProcessor.FRAMEWORK_CONFIG.getSqlValidatorConfig()) - .withSqlNodeRewriter(new SqlRewriter())) + .withSqlNodeRewriter(new SqlRewriter()) + .withConformance(TEST_CONFORMANCE)) .context(Contexts.chain( CalciteQueryProcessor.FRAMEWORK_CONFIG.getContext(), Contexts.of(IgniteSqlSemantics.builder() @@ -238,6 +253,57 @@ public void testPaginationRoundingPolicy() { .check(); } + /** */ + @Test + public void testDualTable() { + assertQuery("SELECT 1 + 1 FROM dual").returns(2).check(); + + assertQuery("SELECT * FROM DUAL") + .columnNames("DUMMY") + .returns("X") + .check(); + + assertQuery("SELECT DUMMY FROM DUAL") + .columnNames("DUMMY") + .returns("X") + .check(); + + assertQuery("SELECT LAG(rate, 1, rate) OVER (ORDER BY period) FROM " + + "(SELECT 1 AS rate, 1 AS period FROM dual)") + .returns(1) + .check(); + } + + /** */ + @Test + public void testDualTableInNewSchema() { + client.getOrCreateCache(new CacheConfiguration() + .setName("CUSTOM_SCHEMA_MARKER") + .setSqlSchema("CUSTOM_SCHEMA") + .setIndexedTypes(Integer.class, Integer.class)); + + assertQuery("SELECT DUMMY FROM CUSTOM_SCHEMA.DUAL") + .columnNames("DUMMY") + .returns("X") + .check(); + } + + /** */ + @Test + public void testUserDefinedDualView() { + sql("CREATE VIEW PUBLIC.DUAL AS SELECT 'USER' AS DUMMY"); + + try { + assertQuery("SELECT DUMMY FROM PUBLIC.DUAL") + .columnNames("DUMMY") + .returns("USER") + .check(); + } + finally { + sql("DROP VIEW IF EXISTS PUBLIC.DUAL"); + } + } + /** Rewrites LTRIM with 2 parameters. */ public static SqlCall rewriteLtrim(SqlValidator validator, SqlCall call) { if (call.operandCount() != 2) From 55eed25fbf9f063d885a5a98744fe5baf7e5825c Mon Sep 17 00:00:00 2001 From: Kirill Tkalenko Date: Mon, 7 Sep 2026 09:45:39 +0300 Subject: [PATCH 2/2] IGNITE-29034 After review 1.0 --- .../query/calcite/schema/IgniteSchema.java | 4 +-- .../OperatorsExtensionIntegrationTest.java | 28 +++++++++++++++++-- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/schema/IgniteSchema.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/schema/IgniteSchema.java index 1882b1bb96f69..445002902d2d2 100644 --- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/schema/IgniteSchema.java +++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/schema/IgniteSchema.java @@ -42,7 +42,7 @@ public class IgniteSchema extends AbstractSchema { private static final String DUAL_TBL_NAME = "DUAL"; /** */ - private static final String DUAL_TBL_VIEW = "SELECT 'X' AS DUMMY"; + private static final String DUAL_TBL_VIEW = "SELECT * FROM (VALUES ('X')) AS T(DUMMY)"; /** */ private final String schemaName; @@ -157,7 +157,7 @@ public SchemaPlus register(SchemaPlus parent, FrameworkConfig frameworkCfg) { /** */ private static void registerDualTableIfSupported(SchemaPlus schema, FrameworkConfig frameworkCfg) { - if (frameworkCfg != null && frameworkCfg.getParserConfig().conformance().isSupportedDualTable() + if (frameworkCfg != null && frameworkCfg.getSqlValidatorConfig().conformance().isSupportedDualTable() && schema.tables().get(DUAL_TBL_NAME) == null && schema.getFunctions(DUAL_TBL_NAME).stream().noneMatch(TableMacro.class::isInstance)) { schema.add(DUAL_TBL_NAME, new ViewTableMacroImpl(DUAL_TBL_VIEW, schema, frameworkCfg)); diff --git a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/OperatorsExtensionIntegrationTest.java b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/OperatorsExtensionIntegrationTest.java index 0b697edde2cd0..004580b915c45 100644 --- a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/OperatorsExtensionIntegrationTest.java +++ b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/OperatorsExtensionIntegrationTest.java @@ -61,6 +61,8 @@ import org.apache.calcite.util.Optionality; import org.apache.ignite.configuration.CacheConfiguration; import org.apache.ignite.configuration.IgniteConfiguration; +import org.apache.ignite.internal.IgniteEx; +import org.apache.ignite.internal.processors.query.IgniteSQLException; import org.apache.ignite.internal.processors.query.calcite.CalciteQueryProcessor; import org.apache.ignite.internal.processors.query.calcite.exec.ExecutionContext; import org.apache.ignite.internal.processors.query.calcite.exec.RowHandler; @@ -93,6 +95,11 @@ public class OperatorsExtensionIntegrationTest extends AbstractBasicIntegrationT /** {@inheritDoc} */ @Override protected IgniteConfiguration getConfiguration(String igniteInstanceName) throws Exception { + return getConfiguration(igniteInstanceName, TEST_CONFORMANCE); + } + + /** */ + private IgniteConfiguration getConfiguration(String igniteInstanceName, SqlConformance conformance) throws Exception { return super.getConfiguration(igniteInstanceName) .setPluginProviders(new AbstractTestPluginProvider() { @Override public String name() { @@ -103,14 +110,14 @@ public class OperatorsExtensionIntegrationTest extends AbstractBasicIntegrationT if (FrameworkConfig.class.equals(cls)) { FrameworkConfig cfg = Frameworks.newConfigBuilder(CalciteQueryProcessor.FRAMEWORK_CONFIG) .parserConfig(CalciteQueryProcessor.FRAMEWORK_CONFIG.getParserConfig() - .withConformance(TEST_CONFORMANCE)) + .withConformance(conformance)) .convertletTable(new ConvertletTable()) .operatorTable(SqlOperatorTables.chain( new OperatorTable().init(), CalciteQueryProcessor.FRAMEWORK_CONFIG.getOperatorTable())) .sqlValidatorConfig( ((IgniteSqlValidator.Config)CalciteQueryProcessor.FRAMEWORK_CONFIG.getSqlValidatorConfig()) .withSqlNodeRewriter(new SqlRewriter()) - .withConformance(TEST_CONFORMANCE)) + .withConformance(conformance)) .context(Contexts.chain( CalciteQueryProcessor.FRAMEWORK_CONFIG.getContext(), Contexts.of(IgniteSqlSemantics.builder() @@ -274,6 +281,23 @@ public void testDualTable() { .check(); } + /** */ + @Test + public void testDualWithisFromRequired() throws Exception { + SqlConformance conformance = new SqlDelegatingConformance(TEST_CONFORMANCE) { + /** {@inheritDoc} */ + @Override public boolean isFromRequired() { + return true; + } + }; + + try (IgniteEx c = startClientGrid(getConfiguration("from-required-client", conformance))) { + assertThrows(c, "SELECT 1", IgniteSQLException.class, "SELECT must have a FROM clause"); + + assertQuery(c, "SELECT 1 + 1 FROM dual").returns(2).check(); + } + } + /** */ @Test public void testDualTableInNewSchema() {