Skip to content

Commit

Permalink
initial changes to deal with CyclicMetadataException
Browse files Browse the repository at this point in the history
  • Loading branch information
datomo committed Dec 9, 2024
1 parent 3bab892 commit 81b4b4b
Show file tree
Hide file tree
Showing 15 changed files with 67 additions and 42 deletions.
2 changes: 1 addition & 1 deletion core/src/main/java/org/polypheny/db/algebra/SingleAlg.java
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ public List<AlgNode> 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 );
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<Double> 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 ) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<Double> 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 );
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<Double> 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 );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,12 +70,14 @@ public MetadataDef<BuiltInMetadata.PercentageOriginalRows> 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;
Expand All @@ -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.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -66,7 +67,7 @@
*
* <ol>
* <li>Add a static method <code>getXyz</code> specification to this class.</li>
* <li>Add unit tests to {@code org.polypheny.db.test.RelMetadataTest}.</li>
* <li>Add unit tests to {@link AlgMetadataProvider}.</li>
* <li>Write a new provider class <code>RelMdXyz</code> 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.</li>
* <li>Add a {@code SOURCE} static member, similar to {@link AlgMdColumnOrigins#SOURCE}.</li>
* <li>Register the {@code SOURCE} object in {@link DefaultAlgMetadataProvider}.</li>
Expand Down Expand Up @@ -227,21 +228,27 @@ public Multimap<Class<? extends AlgNode>, 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<Double> 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.
*
Expand Down Expand Up @@ -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();
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 );
}

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -358,9 +358,9 @@ private static <M extends Metadata> MetadataHandler<M> load3( MetadataDef<M> 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() )
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 );
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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() );
Expand All @@ -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 );
}
}
Expand Down
4 changes: 2 additions & 2 deletions core/src/main/java/org/polypheny/db/runtime/Unit.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@

/**
* Synthetic record with zero fields.
*
* Since all instances are identical, {@code Unit} is a singleton.
* <p>
* Since all instances are identical, {@link Unit} is a singleton.
*/
public class Unit implements Comparable<Unit> {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 ),
Expand Down
21 changes: 11 additions & 10 deletions dbms/src/test/java/org/polypheny/db/polyalg/PolyAlgParsingTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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();

}
Expand Down Expand Up @@ -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." );
Expand Down Expand Up @@ -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')" );
}
Expand Down Expand Up @@ -489,7 +490,7 @@ public void mongoElementRefTest() throws NodeParseException {
}


//@Test
@Test
public void mongoInsertTest() throws NodeParseException {
testMqlRoundTrip( "db." + DOC_COLL + ".insertOne({item: \"canvas\"})" );
}
Expand Down

0 comments on commit 81b4b4b

Please sign in to comment.