diff --git a/core/src/main/java/org/polypheny/db/algebra/SingleAlg.java b/core/src/main/java/org/polypheny/db/algebra/SingleAlg.java index ab781d5149..173aa6bfce 100644 --- a/core/src/main/java/org/polypheny/db/algebra/SingleAlg.java +++ b/core/src/main/java/org/polypheny/db/algebra/SingleAlg.java @@ -78,7 +78,7 @@ public List getInputs() { @Override public double estimateTupleCount( AlgMetadataQuery mq ) { // Not necessarily correct, but a better default than AbstractAlgNode's 1.0 - return mq.getTupleCount( input ); + return mq.getTupleCount( input ).orElse( Double.MAX_VALUE ); } diff --git a/core/src/main/java/org/polypheny/db/algebra/core/Intersect.java b/core/src/main/java/org/polypheny/db/algebra/core/Intersect.java index 0eb4f59d92..86f4ac01ec 100644 --- a/core/src/main/java/org/polypheny/db/algebra/core/Intersect.java +++ b/core/src/main/java/org/polypheny/db/algebra/core/Intersect.java @@ -65,7 +65,7 @@ public double estimateTupleCount( AlgMetadataQuery mq ) { // REVIEW jvs: I just pulled this out of a hat. double dRows = Double.MAX_VALUE; for ( AlgNode input : inputs ) { - dRows = Math.min( dRows, mq.getTupleCount( input ) ); + dRows = Math.min( dRows, mq.getTupleCount( input ).orElse( Double.MAX_VALUE ) ); } dRows *= 0.25; return dRows; diff --git a/core/src/main/java/org/polypheny/db/algebra/enumerable/EnumerableJoin.java b/core/src/main/java/org/polypheny/db/algebra/enumerable/EnumerableJoin.java index 681c98e844..666cee36d7 100644 --- a/core/src/main/java/org/polypheny/db/algebra/enumerable/EnumerableJoin.java +++ b/core/src/main/java/org/polypheny/db/algebra/enumerable/EnumerableJoin.java @@ -37,6 +37,7 @@ import com.google.common.collect.ImmutableList; import java.util.List; import java.util.Objects; +import java.util.Optional; import java.util.Set; import org.apache.calcite.linq4j.tree.BlockBuilder; import org.apache.calcite.linq4j.tree.Expression; @@ -120,8 +121,11 @@ public EnumerableJoin copy( AlgTraitSet traitSet, RexNode condition, AlgNode lef @Override public AlgOptCost computeSelfCost( AlgPlanner planner, AlgMetadataQuery mq ) { - double rowCount = mq.getTupleCount( this ); - + Optional count = mq.getTupleCount( this ); + if ( count.isEmpty() ) { + return planner.getCostFactory().makeInfiniteCost(); + } + double rowCount = count.orElse( null ); // Joins can be flipped, and for many algorithms, both versions are viable and have the same cost. // To make the results stable between versions of the planner, make one of the versions slightly more expensive. if ( Objects.requireNonNull( joinType ) == JoinAlgType.RIGHT ) { diff --git a/core/src/main/java/org/polypheny/db/algebra/enumerable/EnumerableMergeJoin.java b/core/src/main/java/org/polypheny/db/algebra/enumerable/EnumerableMergeJoin.java index dd9848fb33..2e731cf9d6 100644 --- a/core/src/main/java/org/polypheny/db/algebra/enumerable/EnumerableMergeJoin.java +++ b/core/src/main/java/org/polypheny/db/algebra/enumerable/EnumerableMergeJoin.java @@ -38,6 +38,7 @@ import java.lang.reflect.Type; import java.util.ArrayList; import java.util.List; +import java.util.Optional; import java.util.Set; import org.apache.calcite.linq4j.tree.BlockBuilder; import org.apache.calcite.linq4j.tree.Expression; @@ -125,8 +126,11 @@ public AlgOptCost computeSelfCost( AlgPlanner planner, AlgMetadataQuery mq ) { // We assume that the inputs are sorted. The price of sorting them has already been paid. The cost of the join is therefore proportional to the input and output size. final double rightRowCount = right.estimateTupleCount( mq ); final double leftRowCount = left.estimateTupleCount( mq ); - final double rowCount = mq.getTupleCount( this ); - final double d = leftRowCount + rightRowCount + rowCount; + Optional rowCount = mq.getTupleCount( this ); + if ( rowCount.isEmpty() ) { + return planner.getCostFactory().makeInfiniteCost(); + } + final double d = leftRowCount + rightRowCount + rowCount.get(); return planner.getCostFactory().makeCost( d, 0, 0 ); } diff --git a/core/src/main/java/org/polypheny/db/algebra/enumerable/EnumerableSemiJoin.java b/core/src/main/java/org/polypheny/db/algebra/enumerable/EnumerableSemiJoin.java index 88a6743944..60a3c0b72f 100644 --- a/core/src/main/java/org/polypheny/db/algebra/enumerable/EnumerableSemiJoin.java +++ b/core/src/main/java/org/polypheny/db/algebra/enumerable/EnumerableSemiJoin.java @@ -36,6 +36,7 @@ import com.google.common.collect.ImmutableList; import java.util.List; +import java.util.Optional; import org.apache.calcite.linq4j.tree.BlockBuilder; import org.apache.calcite.linq4j.tree.Expression; import org.apache.calcite.linq4j.tree.Expressions; @@ -116,7 +117,11 @@ public SemiJoin copy( AlgTraitSet traitSet, RexNode condition, AlgNode left, Alg @Override public AlgOptCost computeSelfCost( AlgPlanner planner, AlgMetadataQuery mq ) { - double rowCount = mq.getTupleCount( this ); + Optional count = mq.getTupleCount( this ); + if ( count.isEmpty() ) { + return planner.getCostFactory().makeInfiniteCost(); + } + double rowCount = count.get(); // Right-hand input is the "build", and hopefully small, input. final double rightRowCount = right.estimateTupleCount( mq ); diff --git a/core/src/main/java/org/polypheny/db/algebra/enumerable/JavaTupleFormat.java b/core/src/main/java/org/polypheny/db/algebra/enumerable/JavaTupleFormat.java index a3b1a8c51f..41090dfa0a 100644 --- a/core/src/main/java/org/polypheny/db/algebra/enumerable/JavaTupleFormat.java +++ b/core/src/main/java/org/polypheny/db/algebra/enumerable/JavaTupleFormat.java @@ -43,6 +43,7 @@ import org.apache.calcite.linq4j.tree.MethodCallExpression; import org.apache.calcite.linq4j.tree.Types; import org.polypheny.db.adapter.java.JavaTypeFactory; +import org.polypheny.db.algebra.metadata.CyclicMetadataException; import org.polypheny.db.algebra.type.AlgDataType; import org.polypheny.db.interpreter.Row; import org.polypheny.db.runtime.Unit; diff --git a/core/src/main/java/org/polypheny/db/algebra/metadata/AlgMdPercentageOriginalRows.java b/core/src/main/java/org/polypheny/db/algebra/metadata/AlgMdPercentageOriginalRows.java index 991819b501..cfd8271ad7 100644 --- a/core/src/main/java/org/polypheny/db/algebra/metadata/AlgMdPercentageOriginalRows.java +++ b/core/src/main/java/org/polypheny/db/algebra/metadata/AlgMdPercentageOriginalRows.java @@ -70,12 +70,14 @@ public MetadataDef getDef() { } + @SuppressWarnings("unused") // used by codegen public Double getPercentageOriginalRows( Aggregate alg, AlgMetadataQuery mq ) { // REVIEW jvs: The assumption here seems to be that aggregation does not apply any filtering, so it does not modify the percentage. That's very much oversimplified. return mq.getPercentageOriginalRows( alg.getInput() ); } + @SuppressWarnings("unused") // used by codegen public Double getPercentageOriginalRows( Union alg, AlgMetadataQuery mq ) { double numerator = 0.0; double denominator = 0.0; @@ -99,6 +101,7 @@ public Double getPercentageOriginalRows( Union alg, AlgMetadataQuery mq ) { } + @SuppressWarnings("unused") // used by codegen public Double getPercentageOriginalRows( Join alg, AlgMetadataQuery mq ) { // Assume any single-table filter conditions have already been pushed down. diff --git a/core/src/main/java/org/polypheny/db/algebra/metadata/AlgMetadataQuery.java b/core/src/main/java/org/polypheny/db/algebra/metadata/AlgMetadataQuery.java index 7770ce549d..a7c61b654c 100644 --- a/core/src/main/java/org/polypheny/db/algebra/metadata/AlgMetadataQuery.java +++ b/core/src/main/java/org/polypheny/db/algebra/metadata/AlgMetadataQuery.java @@ -43,6 +43,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Optional; import java.util.Set; import lombok.extern.slf4j.Slf4j; import org.polypheny.db.algebra.AlgCollation; @@ -66,7 +67,7 @@ * *
    *
  1. Add a static method getXyz specification to this class.
  2. - *
  3. Add unit tests to {@code org.polypheny.db.test.RelMetadataTest}.
  4. + *
  5. Add unit tests to {@link AlgMetadataProvider}.
  6. *
  7. Write a new provider class RelMdXyz in this package. Follow the pattern from an existing class such as {@link AlgMdColumnOrigins}, overloading on all of the logical algebra expressions to which the query applies.
  8. *
  9. Add a {@code SOURCE} static member, similar to {@link AlgMdColumnOrigins#SOURCE}.
  10. *
  11. Register the {@code SOURCE} object in {@link DefaultAlgMetadataProvider}.
  12. @@ -227,21 +228,27 @@ public Multimap, AlgNode> getNodeTypes( AlgNode alg ) { * @param alg the algebra expression * @return estimated tuple count, or null if no reliable estimate can be determined */ - public Double getTupleCount( AlgNode alg ) { + public Optional getTupleCount( AlgNode alg ) { for ( ; ; ) { try { Double result = rowCountHandler.getTupleCount( alg, this ); - return validateResult( result ); + return Optional.of( validateResult( result ) ); } catch ( JaninoRelMetadataProvider.NoHandler e ) { rowCountHandler = revise( e.algClass, TupleCount.DEF ); } catch ( CyclicMetadataException e ) { log.warn( "Cyclic metadata detected while computing row count for {}", alg ); - return null; + return Optional.empty(); } } } + @SuppressWarnings("unused") // used by codegen + public double getTupleCountOrMax( AlgNode alg ) { + return getTupleCount( alg ).orElse( Double.MAX_VALUE ); + } + + /** * Returns the {@link BuiltInMetadata.MaxRowCount#getMaxRowCount()} statistic. * @@ -306,7 +313,7 @@ public AlgOptCost getNonCumulativeCost( AlgNode alg ) { } catch ( JaninoRelMetadataProvider.NoHandler e ) { nonCumulativeCostHandler = revise( e.algClass, BuiltInMetadata.NonCumulativeCost.DEF ); } catch ( CyclicMetadataException e ) { - return nonCumulativeCostHandler.getNonCumulativeCost( alg, this ); + return alg.getCluster().getPlanner().getCostFactory().makeInfiniteCost(); } } } diff --git a/core/src/main/java/org/polypheny/db/algebra/metadata/CyclicMetadataException.java b/core/src/main/java/org/polypheny/db/algebra/metadata/CyclicMetadataException.java index efbbaaffed..3bcfa00ffa 100644 --- a/core/src/main/java/org/polypheny/db/algebra/metadata/CyclicMetadataException.java +++ b/core/src/main/java/org/polypheny/db/algebra/metadata/CyclicMetadataException.java @@ -34,22 +34,20 @@ package org.polypheny.db.algebra.metadata; +import org.polypheny.db.catalog.exceptions.GenericRuntimeException; + /** * Exception that indicates that a cycle has been detected while computing metadata. */ -public class CyclicMetadataException extends RuntimeException { - - /** - * Singleton instance. Since this exception is thrown for signaling purposes, rather than on an actual error, re-using a singleton instance saves the effort of constructing an exception instance. - */ - public static final CyclicMetadataException INSTANCE = new CyclicMetadataException(); +public class CyclicMetadataException extends GenericRuntimeException { /** * Creates a CyclicMetadataException. + * Has to be {@code public} used by code generation. */ - private CyclicMetadataException() { - super(); + public CyclicMetadataException( String message ) { + super( message ); } } diff --git a/core/src/main/java/org/polypheny/db/algebra/metadata/JaninoRelMetadataProvider.java b/core/src/main/java/org/polypheny/db/algebra/metadata/JaninoRelMetadataProvider.java index 6d5832cd0d..8955c02b03 100644 --- a/core/src/main/java/org/polypheny/db/algebra/metadata/JaninoRelMetadataProvider.java +++ b/core/src/main/java/org/polypheny/db/algebra/metadata/JaninoRelMetadataProvider.java @@ -358,9 +358,9 @@ private static MetadataHandler load3( MetadataDef def .append( " if (v == " ) .append( NullSentinel.class.getName() ) .append( ".ACTIVE) {\n" ) - .append( " throw " ) + .append( " throw new " ) .append( CyclicMetadataException.class.getName() ) - .append( ".INSTANCE;\n" ) + .append( "(\"failed during load3.\");\n" ) .append( " }\n" ) .append( " if (v == " ) .append( NullSentinel.class.getName() ) diff --git a/core/src/main/java/org/polypheny/db/algebra/metadata/ReflectiveAlgMetadataProvider.java b/core/src/main/java/org/polypheny/db/algebra/metadata/ReflectiveAlgMetadataProvider.java index 6a4382a5d6..0de05867fd 100644 --- a/core/src/main/java/org/polypheny/db/algebra/metadata/ReflectiveAlgMetadataProvider.java +++ b/core/src/main/java/org/polypheny/db/algebra/metadata/ReflectiveAlgMetadataProvider.java @@ -53,6 +53,7 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import org.polypheny.db.algebra.AlgNode; +import org.polypheny.db.catalog.exceptions.GenericRuntimeException; import org.polypheny.db.rex.RexNode; import org.polypheny.db.util.BuiltInMethod; import org.polypheny.db.util.ImmutableNullableList; @@ -173,14 +174,15 @@ private static AlgMetadataProvider reflectiveSource( final MetadataHandler ta } key1 = List.of( args2 ); } - if ( mq.map.put( key1, NullSentinel.INSTANCE ) != null ) { - throw CyclicMetadataException.INSTANCE; + Object value = mq.map.put( key1, NullSentinel.INSTANCE ); + if ( value != null ) { + throw new CyclicMetadataException( String.format("Already found key %s with value %s", key1, value ) ); } try { return handlerMethod.invoke( target, args1 ); } catch ( InvocationTargetException | UndeclaredThrowableException e ) { Util.throwIfUnchecked( e.getCause() ); - throw new RuntimeException( e.getCause() ); + throw new GenericRuntimeException( e.getCause() ); } finally { mq.map.remove( key1 ); } diff --git a/core/src/main/java/org/polypheny/db/algebra/polyalg/PolyAlgMetadata.java b/core/src/main/java/org/polypheny/db/algebra/polyalg/PolyAlgMetadata.java index 3150f186e4..c16818e9f7 100644 --- a/core/src/main/java/org/polypheny/db/algebra/polyalg/PolyAlgMetadata.java +++ b/core/src/main/java/org/polypheny/db/algebra/polyalg/PolyAlgMetadata.java @@ -174,11 +174,11 @@ private void updateGlobalStats( AlgNode node ) { updateGlobalStats( child ); } AlgMetadataQuery mq = node.getCluster().getMetadataQuery(); - updateMaxCosts( mq.getNonCumulativeCost( node ), mq.getTupleCount( node ) ); + updateMaxCosts( mq.getNonCumulativeCost( node ), mq.getTupleCount( node ).orElse( -1D ) ); } - public void updateMaxCosts( AlgOptCost nonCumulative, double tupleCount ) { + public void updateMaxCosts( AlgOptCost nonCumulative, Double tupleCount ) { update( "tupleCount", tupleCount ); update( "tuplesCost", nonCumulative.getRows() ); update( "cpuCost", nonCumulative.getCpu() ); @@ -192,9 +192,9 @@ public void setMaxCumulativeCosts( AlgOptCost cumulative ) { } - public void update( String key, double value ) { + public void update( String key, Double value ) { double curr = maxValues.getOrDefault( key, 0d ); - if ( value > curr ) { + if ( value != null && value > curr ) { maxValues.put( key, value ); } } diff --git a/core/src/main/java/org/polypheny/db/runtime/Unit.java b/core/src/main/java/org/polypheny/db/runtime/Unit.java index 361b29c79f..ef465597ce 100644 --- a/core/src/main/java/org/polypheny/db/runtime/Unit.java +++ b/core/src/main/java/org/polypheny/db/runtime/Unit.java @@ -36,8 +36,8 @@ /** * Synthetic record with zero fields. - * - * Since all instances are identical, {@code Unit} is a singleton. + *

    + * Since all instances are identical, {@link Unit} is a singleton. */ public class Unit implements Comparable { diff --git a/core/src/main/java/org/polypheny/db/util/BuiltInMethod.java b/core/src/main/java/org/polypheny/db/util/BuiltInMethod.java index ad98b921e3..45bc6ae5fc 100644 --- a/core/src/main/java/org/polypheny/db/util/BuiltInMethod.java +++ b/core/src/main/java/org/polypheny/db/util/BuiltInMethod.java @@ -396,7 +396,7 @@ public enum BuiltInMethod { COLLATIONS( Collation.class, "collations" ), DISTRIBUTION( Distribution.class, "distribution" ), NODE_TYPES( NodeTypes.class, "getNodeTypes" ), - TUPLE_COUNT( TupleCount.class, "getTupleCount" ), + TUPLE_COUNT( TupleCount.class, "getTupleCountOrMax" ), MAX_ROW_COUNT( MaxRowCount.class, "getMaxRowCount" ), MIN_ROW_COUNT( MinRowCount.class, "getMinRowCount" ), DISTINCT_ROW_COUNT( DistinctRowCount.class, "getDistinctRowCount", ImmutableBitSet.class, RexNode.class ), diff --git a/dbms/src/test/java/org/polypheny/db/polyalg/PolyAlgParsingTest.java b/dbms/src/test/java/org/polypheny/db/polyalg/PolyAlgParsingTest.java index 41f6659ea4..0878bd7530 100644 --- a/dbms/src/test/java/org/polypheny/db/polyalg/PolyAlgParsingTest.java +++ b/dbms/src/test/java/org/polypheny/db/polyalg/PolyAlgParsingTest.java @@ -77,12 +77,13 @@ public class PolyAlgParsingTest { private static final String GRAPH_NAME = "polyAlgGraph"; private static final String DOC_NAME = MqlTestTemplate.namespace; private static final String DOC_COLL = "polyalgdocs"; + private static TestHelper testHelper; @BeforeAll public static void start() throws SQLException { //noinspection ResultOfMethodCallIgnored - TestHelper.getInstance(); + testHelper = TestHelper.getInstance(); addTestData(); } @@ -192,19 +193,19 @@ private static void testQueryRoundTrip( String query, QueryLanguage ql, String n } } } - assertNotNull( logical ); - assertNotNull( allocation ); - assertNotNull( physical ); // Physical is not yet tested further since it is only partially implemented - try { - transaction.commit(); // execute PolyAlg creates new transaction - } catch ( TransactionException e ) { - throw new RuntimeException( e ); + assertNotNull( logical ); + assertNotNull( allocation ); + assertNotNull( physical ); // Physical is not yet tested further since it is only partially implemented + + }finally { + transaction.commit(); // execute PolyAlg creates new transaction, as long as only DQLs are tested like this } if ( transactionManager.getNumberOfActiveTransactions() > 0 ) { throw new RuntimeException(); } + // Check that parsing and executing again returns the same result String resultFromLogical = executePolyAlg( logical, PlanType.LOGICAL, ql ); assertEquals( result, resultFromLogical, "Result from query does not match result when executing the logical plan." ); @@ -410,7 +411,7 @@ public void sqlJoinWithRenameTest() throws NodeParseException { } - //@Test + @Test public void sqlInsertTest() throws NodeParseException { testSqlRoundTrip( "INSERT INTO polyalg_test VALUES (7, 'Mike', 12, 'Male')" ); } @@ -489,7 +490,7 @@ public void mongoElementRefTest() throws NodeParseException { } - //@Test + @Test public void mongoInsertTest() throws NodeParseException { testMqlRoundTrip( "db." + DOC_COLL + ".insertOne({item: \"canvas\"})" ); }