@@ -49,6 +49,9 @@ final class SketchJoinOrderPlanner {
4949 private static final int FRONTIER_LIMIT = 4 ;
5050 private static final int GREEDY_BEAM_WIDTH = 4 ;
5151 private static final double STRUCTURAL_WORK_EQUIVALENCE_ROWS = 4.0d ;
52+ private static final double BRIDGE_UNLOCK_MAX_STEP_WORK_RATIO = 2.0d ;
53+ private static final int COMPLETED_DEFERRED_FILTER_CONNECTIONS = 2 ;
54+ private static final double DEFERRED_FILTER_ORDERING_MAX_PASS_RATIO = 0.5d ;
5255 private static final String TRACE_DIAGNOSTICS_PROPERTY = "rdf4j.optimizer.sketchPlanner.traceDiagnostics" ;
5356 static final double SMALL_BINDING_SET_ASSIGNMENT_MAX_ROWS = 64.0d ;
5457 private static final Logger logger = LoggerFactory .getLogger (SketchJoinOrderPlanner .class );
@@ -65,6 +68,7 @@ final class SketchJoinOrderPlanner {
6568 private final long [] joinVarMasks ;
6669 private final long [] connectivityVarMasks ;
6770 private final long [] bindingVarMasks ;
71+ private final long [] runtimeVarMasks ;
6872 private final long [] deferredFilterRequiredVarMasks ;
6973 private final int stateMemoSize ;
7074 private final long [] variablesMaskByState ;
@@ -146,6 +150,7 @@ private SketchJoinOrderPlanner(SketchBasedJoinEstimator estimator,
146150 this .joinVarMasks = buildFactorMasks (this .factors , variableIds , PlanFactorMaskKind .JOIN );
147151 this .connectivityVarMasks = buildFactorMasks (this .factors , variableIds , PlanFactorMaskKind .CONNECTIVITY );
148152 this .bindingVarMasks = buildFactorMasks (this .factors , variableIds , PlanFactorMaskKind .BINDING );
153+ this .runtimeVarMasks = buildRuntimeVarMasks (this .factors , variableIds );
149154 this .deferredFilterRequiredVarMasks = buildDeferredFilterRequiredVarMasks (this .deferredFilters , variableIds );
150155 this .stateMemoSize = stateMemoSize (this .factors .size ());
151156 this .variablesMaskByState = new long [stateMemoSize ];
@@ -257,7 +262,7 @@ private FactorBuildResult buildFactors(List<TupleExpr> expressions) {
257262 if (estimate == null ) {
258263 return new FactorBuildResult (List .of (), unsupportedPath (tupleExpr ), tupleExpr );
259264 }
260- Set <String > bindingVars = plannerVariables (tupleExpr . getBindingNames () );
265+ Set <String > bindingVars = factorBindingVars (tupleExpr );
261266 providedVars .addAll (bindingVars );
262267 pendingFactors .add (new PendingPlanFactor (i , tupleExpr , estimate , bindingVars ));
263268 }
@@ -266,11 +271,21 @@ private FactorBuildResult buildFactors(List<TupleExpr> expressions) {
266271 Set <String > connectivityVars = connectivityVars (pending .tupleExpr (), pending .joinVars (),
267272 providedVars );
268273 builtFactors .add (new PlanFactor (pending .index (), pending .tupleExpr (), pending .estimate (),
269- pending .joinVars (), connectivityVars , Set . copyOf ( pending .tupleExpr (). getBindingNames () )));
274+ pending .joinVars (), connectivityVars , pending .joinVars ( )));
270275 }
271276 return new FactorBuildResult (List .copyOf (builtFactors ), null , null );
272277 }
273278
279+ private static Set <String > factorBindingVars (TupleExpr tupleExpr ) {
280+ if (tupleExpr instanceof BindingSetAssignment ) {
281+ return plannerVariables (tupleExpr .getBindingNames ());
282+ }
283+ if (tupleExpr instanceof Filter filter ) {
284+ return factorBindingVars (filter .getArg ());
285+ }
286+ return plannerVariables (VarNameCollector .process (tupleExpr ));
287+ }
288+
274289 private static Set <String > plannerVariables (Set <String > variables ) {
275290 if (variables == null || variables .isEmpty ()) {
276291 return Set .of ();
@@ -357,6 +372,14 @@ private static long[] buildFactorMasks(List<PlanFactor> factors, Map<String, Int
357372 return masks ;
358373 }
359374
375+ private static long [] buildRuntimeVarMasks (List <PlanFactor > factors , Map <String , Integer > variableIds ) {
376+ long [] masks = new long [factors .size ()];
377+ for (int i = 0 ; i < factors .size (); i ++) {
378+ masks [i ] = variableMask (factorBindingVars (factors .get (i ).tupleExpr ()), variableIds );
379+ }
380+ return masks ;
381+ }
382+
360383 private static long [] buildDeferredFilterRequiredVarMasks (List <JoinOrderPlanner .FilterConstraint > deferredFilters ,
361384 Map <String , Integer > variableIds ) {
362385 long [] masks = new long [deferredFilters .size ()];
@@ -1224,6 +1247,118 @@ private double newlyUnlockedFilterPassRatio(long previousMask, long nextMask) {
12241247 return found ? passRatio : 1.0d ;
12251248 }
12261249
1250+ private int compareBoundGuardOrder (List <Integer > left , List <Integer > right ) {
1251+ int size = Math .min (left .size (), right .size ());
1252+ long leftMask = 0L ;
1253+ long rightMask = 0L ;
1254+ for (int i = 0 ; i < size ; i ++) {
1255+ int leftIndex = left .get (i );
1256+ int rightIndex = right .get (i );
1257+ if (leftIndex != rightIndex ) {
1258+ boolean leftGuard = isBoundGuardFactor (leftIndex , leftMask );
1259+ boolean rightGuard = isBoundGuardFactor (rightIndex , rightMask );
1260+ if (leftGuard != rightGuard ) {
1261+ return leftGuard ? -1 : 1 ;
1262+ }
1263+ }
1264+ leftMask |= bit (leftIndex );
1265+ rightMask |= bit (rightIndex );
1266+ }
1267+ return 0 ;
1268+ }
1269+
1270+ private boolean isBoundGuardFactor (int factorIndex , long previousMask ) {
1271+ if (isBindingSetAssignment (factorIndex )) {
1272+ return false ;
1273+ }
1274+ long factorVars = runtimeVarMasks [factorIndex ];
1275+ if (factorVars == 0L ) {
1276+ return false ;
1277+ }
1278+ long previousBound = boundVariableMask (previousMask );
1279+ return (factorVars & previousBound ) != 0L && (factorVars & ~previousBound ) == 0L ;
1280+ }
1281+
1282+ private int compareBridgeUnlockOrder (StatePlan left , StatePlan right ) {
1283+ int size = Math .min (left .order ().size (), right .order ().size ());
1284+ long leftMask = 0L ;
1285+ long rightMask = 0L ;
1286+ for (int i = 0 ; i < size ; i ++) {
1287+ int leftIndex = left .order ().get (i ).intValue ();
1288+ int rightIndex = right .order ().get (i ).intValue ();
1289+ if (leftIndex != rightIndex ) {
1290+ boolean leftBridge = unlocksFutureJoin (leftIndex , leftMask );
1291+ boolean rightBridge = unlocksFutureJoin (rightIndex , rightMask );
1292+ if (leftBridge == rightBridge ) {
1293+ return 0 ;
1294+ }
1295+ boolean leftLeaf = isLeafUnlock (leftIndex , leftMask );
1296+ boolean rightLeaf = isLeafUnlock (rightIndex , rightMask );
1297+ if (leftBridge && rightLeaf
1298+ && bridgeUnlockStepComparable (left .physicalStepRanks ().get (i ),
1299+ right .physicalStepRanks ().get (i ))) {
1300+ return -1 ;
1301+ }
1302+ if (rightBridge && leftLeaf
1303+ && bridgeUnlockStepComparable (right .physicalStepRanks ().get (i ),
1304+ left .physicalStepRanks ().get (i ))) {
1305+ return 1 ;
1306+ }
1307+ return 0 ;
1308+ }
1309+ leftMask |= bit (leftIndex );
1310+ rightMask |= bit (rightIndex );
1311+ }
1312+ return 0 ;
1313+ }
1314+
1315+ private boolean unlocksFutureJoin (int factorIndex , long previousMask ) {
1316+ return futureRuntimeJoinConnectionCount (factorIndex , previousMask | bit (factorIndex ), previousMask ) > 0 ;
1317+ }
1318+
1319+ private boolean isLeafUnlock (int factorIndex , long previousMask ) {
1320+ long nextMask = previousMask | bit (factorIndex );
1321+ return futureRuntimeJoinConnectionCount (factorIndex , nextMask , previousMask ) == 0
1322+ && futureDeferredFilterConnectionCount (factorIndex , nextMask , previousMask ) == 0 ;
1323+ }
1324+
1325+ private int futureRuntimeJoinConnectionCount (int factorIndex , long nextMask , long previousMask ) {
1326+ long previousBound = runtimeBoundVariableMask (previousMask );
1327+ long introducedVars = runtimeVarMasks [factorIndex ] & ~previousBound ;
1328+ if (introducedVars == 0L ) {
1329+ return 0 ;
1330+ }
1331+
1332+ long nextBound = runtimeBoundVariableMask (nextMask );
1333+ int futureConnections = 0 ;
1334+ for (int i = 0 ; i < factors .size (); i ++) {
1335+ if (!contains (nextMask , i )
1336+ && (runtimeVarMasks [i ] & introducedVars ) != 0L
1337+ && (runtimeVarMasks [i ] & ~nextBound ) != 0L ) {
1338+ futureConnections ++;
1339+ }
1340+ }
1341+ return futureConnections ;
1342+ }
1343+
1344+ private long runtimeBoundVariableMask (long mask ) {
1345+ long bound = initiallyBoundVarMask ;
1346+ for (int i = 0 ; i < factors .size (); i ++) {
1347+ if (contains (mask , i )) {
1348+ bound |= runtimeVarMasks [i ];
1349+ }
1350+ }
1351+ return bound ;
1352+ }
1353+
1354+ private boolean bridgeUnlockStepComparable (PhysicalStepRank bridgeStep , PhysicalStepRank leafStep ) {
1355+ if (!isFiniteNonNegative (bridgeStep .workRows ()) || !isFiniteNonNegative (leafStep .workRows ())) {
1356+ return false ;
1357+ }
1358+ return bridgeStep .workRows () <= leafStep .workRows () * BRIDGE_UNLOCK_MAX_STEP_WORK_RATIO
1359+ + STRUCTURAL_WORK_EQUIVALENCE_ROWS ;
1360+ }
1361+
12271362 private List <String > newlyUnlockedFilterLabels (long previousMask , long nextMask ) {
12281363 if (deferredFilters .isEmpty ()) {
12291364 return List .of ();
@@ -1507,8 +1642,25 @@ private boolean isBetter(StatePlan candidate, StatePlan incumbent) {
15071642 if (incumbent == null ) {
15081643 return true ;
15091644 }
1645+ boolean structurallyComparableWork = structurallyComparableWork (candidate .totalWork (), incumbent .totalWork ());
1646+ if (structurallyComparableWork && candidate .mask () == incumbent .mask ()) {
1647+ int deferredFilterComparison = compareDeferredFilterOrder (candidate .order (), incumbent .order ());
1648+ if (deferredFilterComparison != 0 ) {
1649+ return deferredFilterComparison < 0 ;
1650+ }
1651+ int boundGuardComparison = compareBoundGuardOrder (candidate .order (), incumbent .order ());
1652+ if (boundGuardComparison != 0 ) {
1653+ return boundGuardComparison < 0 ;
1654+ }
1655+ }
1656+ if (candidate .mask () == incumbent .mask ()) {
1657+ int bridgeUnlockComparison = compareBridgeUnlockOrder (candidate , incumbent );
1658+ if (bridgeUnlockComparison != 0 ) {
1659+ return bridgeUnlockComparison < 0 ;
1660+ }
1661+ }
15101662 int connectivityComparison = compareOrderConnectivity (candidate .order (), incumbent .order ());
1511- if (connectivityComparison != 0 && structurallyComparableWork ( candidate . totalWork (), incumbent . totalWork ()) ) {
1663+ if (connectivityComparison != 0 && structurallyComparableWork ) {
15121664 return connectivityComparison < 0 ;
15131665 }
15141666 int workComparison = Double .compare (candidate .totalWork (), incumbent .totalWork ());
@@ -1536,6 +1688,75 @@ private boolean structurallyComparableWork(double leftWorkRows, double rightWork
15361688 return Math .abs (leftWorkRows - rightWorkRows ) <= STRUCTURAL_WORK_EQUIVALENCE_ROWS ;
15371689 }
15381690
1691+ private int compareDeferredFilterOrder (List <Integer > left , List <Integer > right ) {
1692+ if (deferredFilters .isEmpty ()) {
1693+ return 0 ;
1694+ }
1695+ DeferredFilterOrderRank leftRank = deferredFilterOrderRank (left );
1696+ DeferredFilterOrderRank rightRank = deferredFilterOrderRank (right );
1697+ if (leftRank .filterCount () == 0 || rightRank .filterCount () == 0 ) {
1698+ return 0 ;
1699+ }
1700+ int unlockComparison = compareFiniteAscending (leftRank .unlockScore (), rightRank .unlockScore ());
1701+ if (unlockComparison != 0 ) {
1702+ return unlockComparison ;
1703+ }
1704+ return compareFiniteAscending (leftRank .windowScore (), rightRank .windowScore ());
1705+ }
1706+
1707+ private DeferredFilterOrderRank deferredFilterOrderRank (List <Integer > order ) {
1708+ long orderBoundVars = initiallyBoundVarMask ;
1709+ for (Integer factorIndex : order ) {
1710+ orderBoundVars |= bindingVarMasks [factorIndex .intValue ()];
1711+ }
1712+
1713+ int filterCount = 0 ;
1714+ double unlockScore = 0.0d ;
1715+ double windowScore = 0.0d ;
1716+ for (int i = 0 ; i < deferredFilters .size (); i ++) {
1717+ long requiredVars = deferredFilterRequiredVarMasks [i ];
1718+ if (requiredVars == 0L
1719+ || (initiallyBoundVarMask & requiredVars ) == requiredVars
1720+ || (orderBoundVars & requiredVars ) != requiredVars ) {
1721+ continue ;
1722+ }
1723+ DeferredFilterPlacement placement = deferredFilterPlacement (order , requiredVars );
1724+ double weight = deferredFilterOrderingWeight (deferredFilters .get (i ));
1725+ if (weight <= 0.0d ) {
1726+ continue ;
1727+ }
1728+ filterCount ++;
1729+ unlockScore += weight * placement .unlockStep ();
1730+ windowScore += weight * placement .windowWidth ();
1731+ }
1732+ return new DeferredFilterOrderRank (filterCount , unlockScore , windowScore );
1733+ }
1734+
1735+ private DeferredFilterPlacement deferredFilterPlacement (List <Integer > order , long requiredVars ) {
1736+ long bound = initiallyBoundVarMask ;
1737+ int firstStep = -1 ;
1738+ for (int step = 0 ; step < order .size (); step ++) {
1739+ long introducedVars = bindingVarMasks [order .get (step ).intValue ()] & ~bound ;
1740+ if (firstStep < 0 && (introducedVars & requiredVars ) != 0L ) {
1741+ firstStep = step ;
1742+ }
1743+ bound |= bindingVarMasks [order .get (step ).intValue ()];
1744+ if ((bound & requiredVars ) == requiredVars ) {
1745+ return new DeferredFilterPlacement (step , firstStep < 0 ? 1 : step - firstStep + 1 );
1746+ }
1747+ }
1748+ return new DeferredFilterPlacement (order .size (), order .size () + 1 );
1749+ }
1750+
1751+ private double deferredFilterOrderingWeight (JoinOrderPlanner .FilterConstraint filter ) {
1752+ double passRatio = filter .getEstimatedPassRatio ();
1753+ if (Double .isFinite (passRatio ) && passRatio >= 0.0d
1754+ && passRatio <= DEFERRED_FILTER_ORDERING_MAX_PASS_RATIO ) {
1755+ return 1.0d + (DEFERRED_FILTER_ORDERING_MAX_PASS_RATIO - passRatio );
1756+ }
1757+ return 0.0d ;
1758+ }
1759+
15391760 private int comparePhysicalStepOrder (StatePlan left , StatePlan right ) {
15401761 if (factorCostModel == null ) {
15411762 return 0 ;
@@ -1638,9 +1859,12 @@ private int futureJoinConnectionCount(int factorIndex, long nextMask, long previ
16381859 return 0 ;
16391860 }
16401861
1862+ long nextBound = boundVariableMask (nextMask );
16411863 int futureConnections = 0 ;
16421864 for (int i = 0 ; i < factors .size (); i ++) {
1643- if (!contains (nextMask , i ) && (joinVarMasks [i ] & introducedVars ) != 0L ) {
1865+ if (!contains (nextMask , i )
1866+ && (joinVarMasks [i ] & introducedVars ) != 0L
1867+ && (bindingVarMasks [i ] & ~nextBound ) != 0L ) {
16441868 futureConnections ++;
16451869 }
16461870 }
@@ -1660,10 +1884,16 @@ private int futureDeferredFilterConnectionCount(int factorIndex, long nextMask,
16601884 long nextBound = boundVariableMask (nextMask );
16611885 int futureConnections = 0 ;
16621886 for (int i = 0 ; i < deferredFilters .size (); i ++) {
1887+ if (deferredFilterOrderingWeight (deferredFilters .get (i )) <= 0.0d ) {
1888+ continue ;
1889+ }
16631890 long requiredVars = deferredFilterRequiredVarMasks [i ];
16641891 if ((previousBound & requiredVars ) == requiredVars
1665- || (requiredVars & introducedVars ) == 0L
1666- || (nextBound & requiredVars ) == requiredVars ) {
1892+ || (requiredVars & introducedVars ) == 0L ) {
1893+ continue ;
1894+ }
1895+ if ((nextBound & requiredVars ) == requiredVars ) {
1896+ futureConnections += completedDeferredFilterConnectionWeight (deferredFilters .get (i ));
16671897 continue ;
16681898 }
16691899 long missingVars = requiredVars & ~nextBound ;
@@ -1676,6 +1906,10 @@ private int futureDeferredFilterConnectionCount(int factorIndex, long nextMask,
16761906 return futureConnections ;
16771907 }
16781908
1909+ private int completedDeferredFilterConnectionWeight (JoinOrderPlanner .FilterConstraint filter ) {
1910+ return deferredFilterOrderingWeight (filter ) > 0.0d ? COMPLETED_DEFERRED_FILTER_CONNECTIONS : 0 ;
1911+ }
1912+
16791913 private static int compareOrder (List <Integer > left , List <Integer > right ) {
16801914 int size = Math .min (left .size (), right .size ());
16811915 for (int i = 0 ; i < size ; i ++) {
@@ -1848,6 +2082,12 @@ private record PhysicalStepRank(boolean comparable, int missingLookupComponents,
18482082 double factorOutputRows , double workRows , int lookupComponents , double accessRowsBeforeFilter ) {
18492083 }
18502084
2085+ private record DeferredFilterOrderRank (int filterCount , double unlockScore , double windowScore ) {
2086+ }
2087+
2088+ private record DeferredFilterPlacement (int unlockStep , int windowWidth ) {
2089+ }
2090+
18512091 private record FactorBuildResult (List <PlanFactor > factors ,
18522092 SketchBasedJoinEstimator .SketchPlannerPath rejectionPath ,
18532093 TupleExpr rejectedFactor ) {
0 commit comments