@@ -175,6 +175,27 @@ public Env onMatchJoin(Env env, RelRN.Join join) {
175175 String privateVar = condEnv .generateVar ("private" );
176176 Env privateEnv = condEnv .addBinding ("private_" + System .identityHashCode (join ), privateVar )
177177 .addBinding ("last_private" , privateVar );
178+
179+ // For JoinExtractFilter pattern, use specific variable names
180+ if (env .rulename .equals ("JoinExtractFilter" ) &&
181+ join .ty ().semantics () == org .apache .calcite .rel .core .JoinRelType .INNER &&
182+ !(join .cond () instanceof RexRN .And )) {
183+ String leftVar = privateEnv .generateVar ("left" );
184+ String rightVar = privateEnv .generateVar ("right" );
185+ String onVar = privateEnv .generateVar ("on" );
186+ Env boundEnv = privateEnv .addBinding ("left" , leftVar )
187+ .addBinding ("right" , rightVar )
188+ .addBinding ("on" , onVar )
189+ .addBinding ("private" , privateVar );
190+ // Bind predicate operator name to onVar so filter uses it
191+ if (join .cond () instanceof RexRN .Pred pred ) {
192+ boundEnv = boundEnv .addBinding (pred .operator ().getName (), onVar );
193+ }
194+ String joinType = getJoinType (join .ty ().semantics ());
195+ String pattern = "(" + joinType + "\n $" + leftVar + ":*\n $" + rightVar + ":*\n $" + onVar + ":*\n $" + privateVar + ":*\n )" ;
196+ return boundEnv .setPattern (pattern ).focus (pattern );
197+ }
198+
178199 String joinType = getJoinType (join .ty ().semantics ());
179200 String pattern = "(" + joinType + "\n " + leftPattern + "\n " + rightPattern + "\n " + condPattern + "\n $" + privateVar + ":*\n )" ;
180201 return privateEnv .setPattern (pattern ).focus (pattern );
@@ -216,6 +237,18 @@ public Env transformJoin(Env env, RelRN.Join join) {
216237 String rightVar = env .bindings ().get ("right" );
217238 String onVar = env .bindings ().get ("on" );
218239 String privateVar = env .bindings ().get ("private" );
240+
241+ // Check if join condition is True (JoinExtractFilter pattern)
242+ if (join .cond () instanceof RexRN .True ) {
243+ String pattern = "(InnerJoin\n "
244+ + " $" + leftVar + "\n "
245+ + " $" + rightVar + "\n "
246+ + " []\n "
247+ + " $" + privateVar + "\n "
248+ + ")" ;
249+ return env .setPattern (pattern ).focus (pattern );
250+ }
251+
219252 String pattern = "(InnerJoin\n "
220253 + " (Select $" + leftVar + " (ExtractBoundConditions $" + onVar + " (OutputCols $" + leftVar + ")))\n "
221254 + " (Select $" + rightVar + " (ExtractBoundConditions $" + onVar + " (OutputCols $" + rightVar + ")))\n "
@@ -345,6 +378,22 @@ private String buildNestedIntersect(String intersectType, Seq<String> sources, S
345378
346379 @ Override
347380 public Env onMatchMinus (Env env , RelRN .Minus minus ) {
381+ if (minus .sources ().size () == 2 ) {
382+ RelRN leftSource = minus .sources ().get (0 );
383+ RelRN rightSource = minus .sources ().get (1 );
384+ if (leftSource instanceof RelRN .Empty ) {
385+ String leftVar = env .generateVar ("left" );
386+ Env leftEnv = env .addBinding ("left" , leftVar );
387+ String rightVar = leftEnv .generateVar ("right" );
388+ Env rightEnv = leftEnv .addBinding ("right" , rightVar );
389+ // Still need to match the right source to get its pattern, but we'll use our variable name
390+ Env rightSourceEnv = onMatch (rightEnv , rightSource );
391+ String pattern = "(Except\n $" + leftVar + ":* & (HasZeroRows $" + leftVar + ")\n $" + rightVar + ":*\n )" ;
392+ return rightSourceEnv .addBinding ("isPruneEmptyMinus" , "true" )
393+ .addBinding ("pruneEmptyLeft" , leftVar )
394+ .setPattern (pattern ).focus (pattern );
395+ }
396+ }
348397 if (minus .sources ().size () == 2 && minus .sources ().get (0 ) instanceof RelRN .Minus inner ) {
349398 String leftVar = env .generateVar ("left" );
350399 Env leftEnv = env .addBinding ("left" , leftVar );
@@ -821,6 +870,11 @@ private String buildNestedIntersectTransform(String intersectType, Seq<String> s
821870
822871 @ Override
823872 public Env transformMinus (Env env , RelRN .Minus minus ) {
873+ if (env .bindings ().containsKey ("isPruneEmptyMinus" )) {
874+ String leftVar = env .bindings ().get ("pruneEmptyLeft" );
875+ String pattern = "(ConstructEmptyValues (OutputCols $" + leftVar + "))" ;
876+ return env .setPattern (pattern ).focus (pattern );
877+ }
824878 String pattern = "(Except\n "
825879 + " $left\n "
826880 + " (Union\n "
@@ -970,6 +1024,26 @@ private Env transformGroupSet(Env env, Seq<RexRN> groupSet) {
9701024
9711025 @ Override
9721026 public Env transformEmpty (Env env , RelRN .Empty empty ) {
1027+ // Check for any pruneEmpty* binding (generic, not rule-specific)
1028+ // Try common pruneEmpty binding names
1029+ String pruneVar = null ;
1030+ boolean needsConstructEmptyValues = false ;
1031+ if (env .bindings ().containsKey ("pruneEmptyLeft" )) {
1032+ pruneVar = env .bindings ().get ("pruneEmptyLeft" );
1033+ needsConstructEmptyValues = true ;
1034+ } else if (env .bindings ().containsKey ("pruneEmptyInput" )) {
1035+ pruneVar = env .bindings ().get ("pruneEmptyInput" );
1036+ needsConstructEmptyValues = false ;
1037+ }
1038+ if (pruneVar != null ) {
1039+ if (needsConstructEmptyValues ) {
1040+ String pattern = "(ConstructEmptyValues (OutputCols $" + pruneVar + "))" ;
1041+ return env .setPattern (pattern ).focus (pattern );
1042+ } else {
1043+ String pattern = "$" + pruneVar ;
1044+ return env .setPattern (pattern ).focus (pattern );
1045+ }
1046+ }
9731047 if (env .bindings ().containsKey ("hasZeroRows" )) {
9741048 String inputVar = env .bindings ().getOrDefault ("zeroInput" , "input" );
9751049 String patternStr = env .pattern ();
@@ -980,11 +1054,6 @@ public Env transformEmpty(Env env, RelRN.Empty empty) {
9801054 String pattern = "$" + inputVar ;
9811055 return env .setPattern (pattern ).focus (pattern );
9821056 }
983- if (env .bindings ().containsKey ("isPruneEmptyFilter" )) {
984- String inputVar = env .bindings ().get ("pruneEmptyInput" );
985- String pattern = "$" + inputVar ;
986- return env .setPattern (pattern ).focus (pattern );
987- }
9881057 String pattern = "(ConstructEmptyValues (OutputCols $input_0))" ;
9891058 return env .setPattern (pattern ).focus (pattern );
9901059 }
@@ -1099,9 +1168,30 @@ else if (match.startsWith("(Union\n")) {
10991168 match = "(" + unionType + "\n $" + leftVar + ":* & (HasZeroRows $" + leftVar + ")\n $" + rightVar + ":* & (HasZeroRows $" + rightVar + ")\n )" ;
11001169 }
11011170 }
1171+ // Extract variable map early for potential normalization (before appending match)
1172+ java .util .Map <String , String > varMapForNormalization = extractNumberedVarMap (match );
1173+ String out = transform .pattern ();
1174+ // Normalize match pattern for JoinExtractFilter before appending
1175+ if (!varMapForNormalization .isEmpty () && match .startsWith ("(InnerJoin" ) &&
1176+ varMapForNormalization .containsKey ("left" ) && varMapForNormalization .containsKey ("right" ) &&
1177+ varMapForNormalization .containsKey ("on" ) && varMapForNormalization .containsKey ("private" )) {
1178+ // Check output pattern to see if it's JoinExtractFilter
1179+ if (out .contains ("(Select" ) && out .contains ("(InnerJoin" ) && out .contains ("[]" )) {
1180+ for (java .util .Map .Entry <String , String > e : varMapForNormalization .entrySet ()) {
1181+ String base = e .getKey ();
1182+ String numbered = e .getValue ();
1183+ match = match .replaceAll (
1184+ "\\ $" + java .util .regex .Pattern .quote (numbered .substring (1 )),
1185+ java .util .regex .Matcher .quoteReplacement ("$" + base )
1186+ );
1187+ }
1188+ }
1189+ }
1190+ if (name .equals ("PruneEmptyMinus" )) {
1191+ match = match .replaceAll ("\\ $right_\\ d+" , java .util .regex .Matcher .quoteReplacement ("$right" ));
1192+ }
11021193 sb .append (match ).append ("\n " );
11031194 sb .append ("=>\n " );
1104- String out = transform .pattern ();
11051195 if (out .startsWith ("(ConstructEmptyValues (OutputCols $" )) {
11061196 int startIdx = "(ConstructEmptyValues (OutputCols $" .length ();
11071197 int endIdx = startIdx ;
@@ -1122,25 +1212,65 @@ else if (match.startsWith("(Union\n")) {
11221212 out = "$" + numbered ;
11231213 }
11241214 }
1125- if (match .contains ("HasZeroRows" ) && match .contains ("$left" ) && out .contains ("ConstructEmptyValues" )) {
1126- int leftIdx = match .indexOf ("$left" );
1127- if (leftIdx >= 0 ) {
1128- int start = leftIdx + 1 ;
1129- int end = start ;
1130- while (end < match .length () && (Character .isLetterOrDigit (match .charAt (end )) || match .charAt (end ) == '_' )) end ++;
1131- String leftVar = match .substring (start , end );
1132- out = out .replaceAll ("(OutputCols \\ $)[a-zA-Z_][a-zA-Z0-9_]*" , "$1" + leftVar );
1215+ if (match .contains ("HasZeroRows" ) && out .contains ("ConstructEmptyValues" )) {
1216+ // Find the first source variable (appears before HasZeroRows in the pattern)
1217+ // This handles cases where HasZeroRows is on a different source but we need the first one
1218+ int hasZeroRowsIdx = match .indexOf ("(HasZeroRows $" );
1219+ if (hasZeroRowsIdx >= 0 ) {
1220+ // Find first variable before HasZeroRows
1221+ String beforeHasZeroRows = match .substring (0 , hasZeroRowsIdx );
1222+ String firstSourceVar = findFirstVar (beforeHasZeroRows );
1223+ if (firstSourceVar != null ) {
1224+ // Extract HasZeroRows variable
1225+ int start = hasZeroRowsIdx + "(HasZeroRows $" .length ();
1226+ int end = start ;
1227+ while (end < match .length () && (Character .isLetterOrDigit (match .charAt (end )) || match .charAt (end ) == '_' )) end ++;
1228+ String hasZeroRowsVar = match .substring (start , end );
1229+ // If they differ, ensure output uses first source variable
1230+ if (!hasZeroRowsVar .equals (firstSourceVar )) {
1231+ out = out .replaceAll ("(OutputCols \\ $)[a-zA-Z_][a-zA-Z0-9_]*" , "$1" + firstSourceVar );
1232+ } else if (match .contains ("$left" )) {
1233+ // Original logic: if HasZeroRows is on left, use left variable
1234+ int leftIdx = match .indexOf ("$left" );
1235+ if (leftIdx >= 0 ) {
1236+ start = leftIdx + 1 ;
1237+ end = start ;
1238+ while (end < match .length () && (Character .isLetterOrDigit (match .charAt (end )) || match .charAt (end ) == '_' )) end ++;
1239+ String leftVar = match .substring (start , end );
1240+ out = out .replaceAll ("(OutputCols \\ $)[a-zA-Z_][a-zA-Z0-9_]*" , "$1" + leftVar );
1241+ }
1242+ }
1243+ }
11331244 }
11341245 }
1135- java .util .Map <String , String > varMap = extractNumberedVarMap ( match ) ;
1246+ java .util .Map <String , String > varMap = varMapForNormalization ;
11361247 if (!varMap .isEmpty ()) {
1137- for (java .util .Map .Entry <String , String > e : varMap .entrySet ()) {
1138- String base = e .getKey ();
1139- String numbered = e .getValue ();
1140- out = out .replaceAll (
1141- "\\ $" + java .util .regex .Pattern .quote (base ) + "(?![_0-9])" ,
1142- java .util .regex .Matcher .quoteReplacement (numbered )
1143- );
1248+ // Check if this is JoinExtractFilter pattern: Select wrapping InnerJoin with []
1249+ // Distinguish from JoinCommute which has ExtractBoundConditions
1250+ boolean isJoinExtractFilterPattern = out .contains ("(Select" ) &&
1251+ out .contains ("(InnerJoin" ) && out .contains ("[]" ) &&
1252+ match .startsWith ("(InnerJoin" ) &&
1253+ varMap .containsKey ("left" ) && varMap .containsKey ("right" ) &&
1254+ varMap .containsKey ("on" ) && varMap .containsKey ("private" );
1255+ if (isJoinExtractFilterPattern ) {
1256+ // Normalize output pattern (match already appended, so only normalize output)
1257+ for (java .util .Map .Entry <String , String > e : varMap .entrySet ()) {
1258+ String base = e .getKey ();
1259+ String numbered = e .getValue ();
1260+ out = out .replaceAll (
1261+ "\\ $" + java .util .regex .Pattern .quote (numbered .substring (1 )),
1262+ java .util .regex .Matcher .quoteReplacement ("$" + base )
1263+ );
1264+ }
1265+ } else {
1266+ for (java .util .Map .Entry <String , String > e : varMap .entrySet ()) {
1267+ String base = e .getKey ();
1268+ String numbered = e .getValue ();
1269+ out = out .replaceAll (
1270+ "\\ $" + java .util .regex .Pattern .quote (base ) + "(?![_0-9])" ,
1271+ java .util .regex .Matcher .quoteReplacement (numbered )
1272+ );
1273+ }
11441274 }
11451275 }
11461276 if (name .equals ("PruneEmptyProject" )) {
0 commit comments