diff --git a/isthmus-cli/src/main/resources/META-INF/native-image/io.substrait/isthmus-cli/reachability-metadata.json b/isthmus-cli/src/main/resources/META-INF/native-image/io.substrait/isthmus-cli/reachability-metadata.json index a73d84903..97b8ccdda 100644 --- a/isthmus-cli/src/main/resources/META-INF/native-image/io.substrait/isthmus-cli/reachability-metadata.json +++ b/isthmus-cli/src/main/resources/META-INF/native-image/io.substrait/isthmus-cli/reachability-metadata.json @@ -13,6 +13,13 @@ ] } }, + { + "type": { + "proxy": [ + "org.apache.calcite.rel.metadata.BuiltInMetadata$AllPredicates" + ] + } + }, { "type": { "proxy": [ @@ -20,6 +27,13 @@ ] } }, + { + "type": { + "proxy": [ + "org.apache.calcite.rel.metadata.BuiltInMetadata$Collation" + ] + } + }, { "type": { "proxy": [ @@ -27,6 +41,13 @@ ] } }, + { + "type": { + "proxy": [ + "org.apache.calcite.rel.metadata.BuiltInMetadata$ColumnOrigin" + ] + } + }, { "type": { "proxy": [ @@ -34,6 +55,13 @@ ] } }, + { + "type": { + "proxy": [ + "org.apache.calcite.rel.metadata.BuiltInMetadata$ColumnUniqueness" + ] + } + }, { "type": { "proxy": [ @@ -41,6 +69,13 @@ ] } }, + { + "type": { + "proxy": [ + "org.apache.calcite.rel.metadata.BuiltInMetadata$CumulativeCost" + ] + } + }, { "type": { "proxy": [ @@ -48,6 +83,13 @@ ] } }, + { + "type": { + "proxy": [ + "org.apache.calcite.rel.metadata.BuiltInMetadata$DistinctRowCount" + ] + } + }, { "type": { "proxy": [ @@ -55,6 +97,13 @@ ] } }, + { + "type": { + "proxy": [ + "org.apache.calcite.rel.metadata.BuiltInMetadata$Distribution" + ] + } + }, { "type": { "proxy": [ @@ -62,6 +111,13 @@ ] } }, + { + "type": { + "proxy": [ + "org.apache.calcite.rel.metadata.BuiltInMetadata$ExplainVisibility" + ] + } + }, { "type": { "proxy": [ @@ -69,6 +125,13 @@ ] } }, + { + "type": { + "proxy": [ + "org.apache.calcite.rel.metadata.BuiltInMetadata$ExpressionLineage" + ] + } + }, { "type": { "proxy": [ @@ -76,6 +139,13 @@ ] } }, + { + "type": { + "proxy": [ + "org.apache.calcite.rel.metadata.BuiltInMetadata$FunctionalDependency" + ] + } + }, { "type": { "proxy": [ @@ -83,6 +153,13 @@ ] } }, + { + "type": { + "proxy": [ + "org.apache.calcite.rel.metadata.BuiltInMetadata$InputFieldsUsed" + ] + } + }, { "type": { "proxy": [ @@ -90,6 +167,13 @@ ] } }, + { + "type": { + "proxy": [ + "org.apache.calcite.rel.metadata.BuiltInMetadata$LowerBoundCost" + ] + } + }, { "type": { "proxy": [ @@ -97,6 +181,13 @@ ] } }, + { + "type": { + "proxy": [ + "org.apache.calcite.rel.metadata.BuiltInMetadata$MaxRowCount" + ] + } + }, { "type": { "proxy": [ @@ -104,6 +195,13 @@ ] } }, + { + "type": { + "proxy": [ + "org.apache.calcite.rel.metadata.BuiltInMetadata$Measure" + ] + } + }, { "type": { "proxy": [ @@ -111,6 +209,13 @@ ] } }, + { + "type": { + "proxy": [ + "org.apache.calcite.rel.metadata.BuiltInMetadata$Memory" + ] + } + }, { "type": { "proxy": [ @@ -118,6 +223,13 @@ ] } }, + { + "type": { + "proxy": [ + "org.apache.calcite.rel.metadata.BuiltInMetadata$MinRowCount" + ] + } + }, { "type": { "proxy": [ @@ -125,6 +237,13 @@ ] } }, + { + "type": { + "proxy": [ + "org.apache.calcite.rel.metadata.BuiltInMetadata$NodeTypes" + ] + } + }, { "type": { "proxy": [ @@ -132,6 +251,13 @@ ] } }, + { + "type": { + "proxy": [ + "org.apache.calcite.rel.metadata.BuiltInMetadata$NonCumulativeCost" + ] + } + }, { "type": { "proxy": [ @@ -139,6 +265,13 @@ ] } }, + { + "type": { + "proxy": [ + "org.apache.calcite.rel.metadata.BuiltInMetadata$Parallelism" + ] + } + }, { "type": { "proxy": [ @@ -146,6 +279,13 @@ ] } }, + { + "type": { + "proxy": [ + "org.apache.calcite.rel.metadata.BuiltInMetadata$PercentageOriginalRows" + ] + } + }, { "type": { "proxy": [ @@ -153,6 +293,13 @@ ] } }, + { + "type": { + "proxy": [ + "org.apache.calcite.rel.metadata.BuiltInMetadata$PopulationSize" + ] + } + }, { "type": { "proxy": [ @@ -160,6 +307,13 @@ ] } }, + { + "type": { + "proxy": [ + "org.apache.calcite.rel.metadata.BuiltInMetadata$Predicates" + ] + } + }, { "type": { "proxy": [ @@ -167,6 +321,13 @@ ] } }, + { + "type": { + "proxy": [ + "org.apache.calcite.rel.metadata.BuiltInMetadata$RowCount" + ] + } + }, { "type": { "proxy": [ @@ -174,6 +335,13 @@ ] } }, + { + "type": { + "proxy": [ + "org.apache.calcite.rel.metadata.BuiltInMetadata$Selectivity" + ] + } + }, { "type": { "proxy": [ @@ -181,6 +349,13 @@ ] } }, + { + "type": { + "proxy": [ + "org.apache.calcite.rel.metadata.BuiltInMetadata$Size" + ] + } + }, { "type": { "proxy": [ @@ -188,6 +363,13 @@ ] } }, + { + "type": { + "proxy": [ + "org.apache.calcite.rel.metadata.BuiltInMetadata$TableReferences" + ] + } + }, { "type": { "proxy": [ @@ -195,6 +377,13 @@ ] } }, + { + "type": { + "proxy": [ + "org.apache.calcite.rel.metadata.BuiltInMetadata$UniqueKeys" + ] + } + }, { "type": { "proxy": [ diff --git a/isthmus-cli/src/test/script/smoke.sh b/isthmus-cli/src/test/script/smoke.sh index 8a25ef0b3..9b7fd84cb 100755 --- a/isthmus-cli/src/test/script/smoke.sh +++ b/isthmus-cli/src/test/script/smoke.sh @@ -18,6 +18,13 @@ echo "${LINEITEM}" # SQL Query - Aggregate "${CMD}" 'select l_orderkey, count(l_partkey) from lineitem group by l_orderkey' --create "${LINEITEM}" +# SQL Query - Grouping-only aggregate (no aggregate function; exercises the column-uniqueness +# metadata query that must not fall back to Janino runtime codegen in the native image) +"${CMD}" 'select l_orderkey from lineitem group by l_orderkey' --create "${LINEITEM}" + +# SQL Query - DISTINCT (same metadata-query path as grouping-only aggregate) +"${CMD}" 'select distinct l_orderkey from lineitem' --create "${LINEITEM}" + # SQL Expression - Literal expression "${CMD}" --expression '10' diff --git a/isthmus/src/main/java/io/substrait/isthmus/ConverterProvider.java b/isthmus/src/main/java/io/substrait/isthmus/ConverterProvider.java index f8b4b130e..cce2741fa 100644 --- a/isthmus/src/main/java/io/substrait/isthmus/ConverterProvider.java +++ b/isthmus/src/main/java/io/substrait/isthmus/ConverterProvider.java @@ -343,11 +343,14 @@ public ExpressionRexConverter getExpressionRexConverter( * @return a new RelBuilder instance */ public RelBuilder getRelBuilder(CalciteSchema schema) { - return RelBuilder.create( - Frameworks.newConfigBuilder() - .defaultSchema(schema.plus()) - .typeSystem(getTypeSystem()) - .build()); + RelBuilder relBuilder = + RelBuilder.create( + Frameworks.newConfigBuilder() + .defaultSchema(schema.plus()) + .typeSystem(getTypeSystem()) + .build()); + Utils.useReflectiveMetadataProvider(relBuilder.getCluster()); + return relBuilder; } // Utility Getters diff --git a/isthmus/src/main/java/io/substrait/isthmus/SqlConverterBase.java b/isthmus/src/main/java/io/substrait/isthmus/SqlConverterBase.java index 30938403e..d6b30a7c3 100644 --- a/isthmus/src/main/java/io/substrait/isthmus/SqlConverterBase.java +++ b/isthmus/src/main/java/io/substrait/isthmus/SqlConverterBase.java @@ -6,9 +6,6 @@ import org.apache.calcite.plan.RelOptCluster; import org.apache.calcite.plan.RelOptCostImpl; import org.apache.calcite.plan.volcano.VolcanoPlanner; -import org.apache.calcite.rel.metadata.DefaultRelMetadataProvider; -import org.apache.calcite.rel.metadata.ProxyingMetadataHandlerProvider; -import org.apache.calcite.rel.metadata.RelMetadataQuery; import org.apache.calcite.rel.type.RelDataTypeFactory; import org.apache.calcite.rex.RexBuilder; import org.apache.calcite.sql.parser.SqlParser; @@ -57,12 +54,7 @@ protected SqlConverterBase(ConverterProvider converterProvider) { this.converterConfig = converterProvider.getSqlToRelConverterConfig(); VolcanoPlanner planner = new VolcanoPlanner(RelOptCostImpl.FACTORY, Contexts.of("hello")); this.relOptCluster = RelOptCluster.create(planner, new RexBuilder(factory)); - relOptCluster.setMetadataQuerySupplier( - () -> { - ProxyingMetadataHandlerProvider handler = - new ProxyingMetadataHandlerProvider(DefaultRelMetadataProvider.INSTANCE); - return new RelMetadataQuery(handler); - }); + Utils.useReflectiveMetadataProvider(relOptCluster); parserConfig = converterProvider.getSqlParserConfig(); } } diff --git a/isthmus/src/main/java/io/substrait/isthmus/SubstraitRelNodeConverter.java b/isthmus/src/main/java/io/substrait/isthmus/SubstraitRelNodeConverter.java index 26f063c7b..1fe0f8515 100644 --- a/isthmus/src/main/java/io/substrait/isthmus/SubstraitRelNodeConverter.java +++ b/isthmus/src/main/java/io/substrait/isthmus/SubstraitRelNodeConverter.java @@ -164,6 +164,7 @@ public static RelNode convert( .typeSystem(converterProvider.getTypeSystem()) .programs() .build()); + Utils.useReflectiveMetadataProvider(relBuilder.getCluster()); // Normalize any offset-based outer references (steps_out) to the id-based form (rel_anchor / // rel_reference) so the conversion below resolves correlations purely by anchor. Plans that are // already id-based are left unchanged. diff --git a/isthmus/src/main/java/io/substrait/isthmus/Utils.java b/isthmus/src/main/java/io/substrait/isthmus/Utils.java index 9804954eb..92f56e7a8 100644 --- a/isthmus/src/main/java/io/substrait/isthmus/Utils.java +++ b/isthmus/src/main/java/io/substrait/isthmus/Utils.java @@ -10,6 +10,10 @@ import java.util.function.BiFunction; import java.util.stream.Stream; import org.apache.calcite.jdbc.CalciteSchema; +import org.apache.calcite.plan.RelOptCluster; +import org.apache.calcite.rel.metadata.DefaultRelMetadataProvider; +import org.apache.calcite.rel.metadata.ProxyingMetadataHandlerProvider; +import org.apache.calcite.rel.metadata.RelMetadataQuery; import org.jspecify.annotations.NonNull; /** @@ -99,4 +103,28 @@ public static CalciteSchema createCalciteSchemaFromNames( return schema; } + + /** + * Configures {@code cluster} to answer relational-metadata queries with handlers built via {@link + * java.lang.reflect.Proxy reflection} rather than runtime code generation. + * + *

Calcite's default {@link RelMetadataQuery} obtains its handlers from {@code + * JaninoRelMetadataProvider}, which compiles a handler class at runtime with Janino. Runtime code + * generation is unavailable in a GraalVM native image (closed-world, ahead-of-time compiled), so + * any metadata query — e.g. the column-uniqueness check {@code RelBuilder} runs for a grouping + * {@code aggregate} or {@code DISTINCT} — fails there. {@link ProxyingMetadataHandlerProvider} + * builds the same handlers with dynamic proxies, which are supported in native images once the + * handler interfaces are registered. + * + *

Apply this to every {@link RelOptCluster} used while converting to or from Calcite so the + * native image never reaches the Janino-backed path. + * + * @param cluster the cluster to configure + */ + public static void useReflectiveMetadataProvider(final RelOptCluster cluster) { + cluster.setMetadataQuerySupplier( + () -> + new RelMetadataQuery( + new ProxyingMetadataHandlerProvider(DefaultRelMetadataProvider.INSTANCE))); + } } diff --git a/isthmus/src/main/java/io/substrait/isthmus/sql/SubstraitSqlToCalcite.java b/isthmus/src/main/java/io/substrait/isthmus/sql/SubstraitSqlToCalcite.java index 82650291b..29c0f93cf 100644 --- a/isthmus/src/main/java/io/substrait/isthmus/sql/SubstraitSqlToCalcite.java +++ b/isthmus/src/main/java/io/substrait/isthmus/sql/SubstraitSqlToCalcite.java @@ -2,6 +2,7 @@ import io.substrait.isthmus.ConverterProvider; import io.substrait.isthmus.SubstraitTypeSystem; +import io.substrait.isthmus.Utils; import io.substrait.isthmus.calcite.rel.DdlSqlToRelConverter; import java.util.List; import java.util.stream.Collectors; @@ -256,6 +257,8 @@ static RelOptCluster createDefaultRelOptCluster() { new RexBuilder(new JavaTypeFactoryImpl(SubstraitTypeSystem.TYPE_SYSTEM)); HepProgram program = HepProgram.builder().build(); RelOptPlanner emptyPlanner = new HepPlanner(program); - return RelOptCluster.create(emptyPlanner, rexBuilder); + RelOptCluster cluster = RelOptCluster.create(emptyPlanner, rexBuilder); + Utils.useReflectiveMetadataProvider(cluster); + return cluster; } }