3434import org .eclipse .rdf4j .query .QueryEvaluationException ;
3535import org .eclipse .rdf4j .query .algebra .AbstractQueryModelNode ;
3636import org .eclipse .rdf4j .query .algebra .BindingSetAssignment ;
37- import org .eclipse .rdf4j .query .algebra .Extension ;
3837import org .eclipse .rdf4j .query .algebra .Join ;
3938import org .eclipse .rdf4j .query .algebra .LeftJoin ;
4039import org .eclipse .rdf4j .query .algebra .StatementPattern ;
@@ -100,16 +99,23 @@ public void optimize(TupleExpr tupleExpr, Dataset dataset, BindingSet bindings)
10099 tupleExpr .visit (new JoinVisitor (statistics , trackResultSize , tripleSource ));
101100 }
102101
103- private static class JoinVisitor extends AbstractSimpleQueryModelVisitor <RuntimeException > {
102+ /**
103+ * This can be extended by subclasses to allow for adjustments to the optimization process.
104+ */
105+ @ SuppressWarnings ("InnerClassMayBeStatic" )
106+ protected class JoinVisitor extends AbstractSimpleQueryModelVisitor <RuntimeException > {
104107
105108 private final EvaluationStatistics statistics ;
106109 private final TripleSource tripleSource ;
110+ private final boolean trackResultSize ;
107111 Set <String > boundVars = new HashSet <>();
112+ private double currentHighestCost = 1 ;
108113
109114 private JoinVisitor (EvaluationStatistics statistics , boolean trackResultSize , TripleSource tripleSource ) {
110115 super (trackResultSize );
111116 this .statistics = statistics ;
112117 this .tripleSource = tripleSource ;
118+ this .trackResultSize = trackResultSize ;
113119 }
114120
115121 @ Override
@@ -145,7 +151,6 @@ private void optimizePriorityJoin(Set<String> origBoundVars, TupleExpr join) {
145151
146152 @ Override
147153 public void meet (Join node ) {
148-
149154 Set <String > origBoundVars = boundVars ;
150155 try {
151156 boundVars = new HashSet <>(boundVars );
@@ -155,10 +160,13 @@ public void meet(Join node) {
155160
156161 // get all extensions (BIND clause)
157162 List <TupleExpr > orderedExtensions = getExtensionTupleExprs (joinArgs );
163+ optimizeInNewScope (orderedExtensions );
158164 joinArgs .removeAll (orderedExtensions );
159165
160166 // get all subselects and order them
161- List <TupleExpr > orderedSubselects = reorderSubselects (getSubSelects (joinArgs ));
167+ List <TupleExpr > subSelects = getSubSelects (joinArgs );
168+ optimizeInNewScope (subSelects );
169+ List <TupleExpr > orderedSubselects = reorderSubselects (subSelects );
162170 joinArgs .removeAll (orderedSubselects );
163171
164172 // Reorder the subselects and extensions to a more optimal sequence
@@ -215,6 +223,7 @@ public void meet(Join node) {
215223 // order all other join arguments based on available statistics
216224 while (!joinArgs .isEmpty ()) {
217225 TupleExpr tupleExpr = selectNextTupleExpr (joinArgs , cardinalityMap , varsMap , varFreqMap );
226+ this .currentHighestCost = Math .max (currentHighestCost , tupleExpr .getCostEstimate ());
218227
219228 joinArgs .remove (tupleExpr );
220229 orderedJoinArgs .addLast (tupleExpr );
@@ -321,6 +330,12 @@ public void meet(Join node) {
321330 }
322331 }
323332
333+ private void optimizeInNewScope (List <TupleExpr > subSelects ) {
334+ for (TupleExpr subSelect : subSelects ) {
335+ subSelect .visit (new JoinVisitor (statistics , trackResultSize , tripleSource ));
336+ }
337+ }
338+
324339 private boolean joinSizeIsTooDifferent (double cardinality , double second ) {
325340 if (cardinality > second && cardinality / MERGE_JOIN_CARDINALITY_SIZE_DIFF_MULTIPLIER > second ) {
326341 return true ;
@@ -393,16 +408,6 @@ protected <M extends Map<Var, Integer>> void fillVarFreqMap(List<Var> varList, M
393408 }
394409 }
395410
396- protected List <Extension > getExtensions (List <TupleExpr > expressions ) {
397- List <Extension > extensions = new ArrayList <>();
398- for (TupleExpr expr : expressions ) {
399- if (expr instanceof Extension ) {
400- extensions .add ((Extension ) expr );
401- }
402- }
403- return extensions ;
404- }
405-
406411 private List <TupleExpr > getExtensionTupleExprs (List <TupleExpr > expressions ) {
407412 if (expressions .isEmpty ()) {
408413 return List .of ();
@@ -424,6 +429,14 @@ private List<TupleExpr> getExtensionTupleExprs(List<TupleExpr> expressions) {
424429 return extensions ;
425430 }
426431
432+ /**
433+ * This method returns all direct sub-selects in the given list of expressions.
434+ * <p>
435+ * This method is meant to be possible to override by subclasses.
436+ *
437+ * @param expressions
438+ * @return
439+ */
427440 protected List <TupleExpr > getSubSelects (List <TupleExpr > expressions ) {
428441 if (expressions .isEmpty ()) {
429442 return List .of ();
@@ -647,6 +660,11 @@ protected double getTupleExprCost(TupleExpr tupleExpr, Map<TupleExpr, Double> ca
647660 cost = cardinalityMap .get (tupleExpr );
648661 }
649662
663+ // Adding 5 to the cost allows us to order tuple expressions based on which variables are already bound even
664+ // if the statistics returns a cardinality of 0. This is useful for cases where the statistics are
665+ // inaccurate, such as when querying the data added in the current transaction.
666+ cost += 5 ;
667+
650668 List <Var > vars = varsMap .get (tupleExpr );
651669
652670 // Compensate for variables that are bound earlier in the evaluation
@@ -656,8 +674,15 @@ protected double getTupleExprCost(TupleExpr tupleExpr, Map<TupleExpr, Double> ca
656674 int nonConstantVarCount = vars .size () - constantVars ;
657675
658676 if (nonConstantVarCount > 0 ) {
659- double exp = (double ) unboundVars .size () / nonConstantVarCount ;
660- cost = Math .pow (cost , exp );
677+ int boundVarCount = nonConstantVarCount - unboundVars .size ();
678+ if (boundVarCount == 0 ) {
679+ // Cartesian Product!
680+ cost = cost * currentHighestCost ;
681+ } else {
682+ double exp = (double ) unboundVars .size () / nonConstantVarCount ;
683+ cost = Math .pow (cost , exp );
684+ }
685+
661686 }
662687
663688 if (unboundVars .isEmpty ()) {
@@ -771,7 +796,7 @@ private void mergeJoinForCrossJoin(Deque<TupleExpr> orderedJoinArgs, Set<Var> su
771796 }
772797 }
773798
774- private static class StatementPatternVarCollector extends StatementPatternVisitor {
799+ private class StatementPatternVarCollector extends StatementPatternVisitor {
775800
776801 private final TupleExpr tupleExpr ;
777802 private List <Var > vars ;
0 commit comments