From d30a1f7d0eca5b358d8ebd1aa6ab6fde7330af9c Mon Sep 17 00:00:00 2001 From: Yushin Liang Date: Sat, 7 Jun 2025 01:21:57 +0800 Subject: [PATCH 1/2] added minus operator and generated code for MinusMerge --- .vscode/settings.json | 3 +- src/main/java/org/qed/CodeGenerator.java | 10 ++++ .../org/qed/Generated/CalciteGenerator.java | 49 +++++++++++++++++++ .../java/org/qed/Generated/CalciteTester.java | 1 + .../java/org/qed/Generated/MinusMerge.java | 39 +++++++++++++++ .../Generated/RRuleInstances/MinusMerge.java | 26 ++++++++++ .../qed/Generated/Tests/MinusMergeTest.java | 48 ++++++++++++++++++ src/main/java/org/qed/RelRN.java | 12 +++++ 8 files changed, 187 insertions(+), 1 deletion(-) create mode 100644 src/main/java/org/qed/Generated/MinusMerge.java create mode 100644 src/main/java/org/qed/Generated/RRuleInstances/MinusMerge.java create mode 100644 src/main/java/org/qed/Generated/Tests/MinusMergeTest.java diff --git a/.vscode/settings.json b/.vscode/settings.json index c5f3f6b..a760fd6 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,4 @@ { - "java.configuration.updateBuildConfiguration": "interactive" + "java.configuration.updateBuildConfiguration": "interactive", + "java.debug.settings.onBuildFailureProceed": true } \ No newline at end of file diff --git a/src/main/java/org/qed/CodeGenerator.java b/src/main/java/org/qed/CodeGenerator.java index 85e1f6f..bf7da08 100644 --- a/src/main/java/org/qed/CodeGenerator.java +++ b/src/main/java/org/qed/CodeGenerator.java @@ -26,6 +26,7 @@ default E onMatch(E env, RelRN pattern) { case RelRN.Join join -> onMatchJoin(env, join); case RelRN.Union union -> onMatchUnion(env, union); case RelRN.Intersect intersect -> onMatchIntersect(env, intersect); + case RelRN.Minus minus -> onMatchMinus(env, minus); case RelRN.Empty empty -> onMatchEmpty(env, empty); default -> onMatchCustom(env, pattern); }; @@ -62,6 +63,7 @@ default E transform(E env, RelRN target) { case RelRN.Join join -> transformJoin(env, join); case RelRN.Union union -> transformUnion(env, union); case RelRN.Intersect intersect -> transformIntersect(env, intersect); + case RelRN.Minus minus -> transformMinus(env, minus); case RelRN.Empty empty -> transformEmpty(env, empty); default -> transformCustom(env, target); }; @@ -121,6 +123,10 @@ default E onMatchIntersect(E env, RelRN.Intersect intersect) { return unimplementedOnMatch(env, intersect); } + default E onMatchMinus(E env, RelRN.Minus minus) { + return unimplementedOnMatch(env, minus); + } + default E onMatchCustom(E env, RelRN custom) { return unimplementedOnMatch(env, custom); } @@ -193,6 +199,10 @@ default E transformIntersect(E env, RelRN.Intersect intersect) { return unimplementedTransform(env, intersect); } + default E transformMinus(E env, RelRN.Minus minus) { + return unimplementedTransform(env, minus); + } + default E transformCustom(E env, RelRN custom) { return unimplementedTransform(env, custom); } diff --git a/src/main/java/org/qed/Generated/CalciteGenerator.java b/src/main/java/org/qed/Generated/CalciteGenerator.java index 6792cca..a5f9418 100644 --- a/src/main/java/org/qed/Generated/CalciteGenerator.java +++ b/src/main/java/org/qed/Generated/CalciteGenerator.java @@ -185,6 +185,36 @@ public Env onMatchIntersect(Env env, RelRN.Intersect intersect) { return current_env.grow("operand(" + operatorClass + ".class).inputs(" + inputsBuilder.toString() + ")"); } + @Override + public Env onMatchMinus(Env env, RelRN.Minus minus) { + // Get the all flag from the minus + boolean all = minus.all(); + + // Process each source in the minus + var current_env = env; + var skeletons = Seq.empty(); + + // Process all sources in the sequence + for (var source : minus.sources()) { + var next_env = current_env.next(); + var source_env = onMatch(next_env, source); + skeletons = skeletons.appended(source_env.skeleton()); + current_env = source_env; + } + + // Build the input skeletons string for the operand + StringBuilder inputsBuilder = new StringBuilder(); + for (int i = 0; i < skeletons.size(); i++) { + if (i > 0) { + inputsBuilder.append(", "); + } + inputsBuilder.append(skeletons.get(i).toString()); + } + + // Create the minus operand + return current_env.grow("operand(LogicalMinus.class).inputs(" + inputsBuilder.toString() + ")"); + } + @Override public Env onMatchField(Env env, RexRN.Field field) { // Generate a unique symbolic name for this field @@ -320,6 +350,25 @@ public Env transformIntersect(Env env, RelRN.Intersect intersect) { return current_env.focus(current_env.current() + "." + methodName + "(" + all + ", " + sourceCount + ")"); } + @Override + public Env transformMinus(Env env, RelRN.Minus minus) { + // Get the all flag from the minus + boolean all = minus.all(); + + // The number of sources + int sourceCount = minus.sources().size(); + + // Transform each source + var current_env = env; + for (var source : minus.sources()) { + current_env = transform(current_env, source); + } + + // Use the minus method with the all flag and source count + // This matches the expected Calcite RelBuilder.minus(boolean all, int n) signature + return current_env.focus(current_env.current() + ".minus(" + all + ", " + sourceCount + ")"); + } + @Override public Env transformField(Env env, RexRN.Field field) { // In Calcite, field references are typically created with a "field" method diff --git a/src/main/java/org/qed/Generated/CalciteTester.java b/src/main/java/org/qed/Generated/CalciteTester.java index aefde13..786ebec 100644 --- a/src/main/java/org/qed/Generated/CalciteTester.java +++ b/src/main/java/org/qed/Generated/CalciteTester.java @@ -84,6 +84,7 @@ public static void runAllTests() { org.qed.Generated.Tests.FilterSetOpTransposeTest.runTest(); org.qed.Generated.Tests.JoinExtractFilterTest.runTest(); org.qed.Generated.Tests.SemiJoinFilterTransposeTest.runTest(); + org.qed.Generated.Tests.MinusMergeTest.runTest(); } catch (Exception e) { System.out.println("Test failed: " + e.getMessage()); e.printStackTrace(); diff --git a/src/main/java/org/qed/Generated/MinusMerge.java b/src/main/java/org/qed/Generated/MinusMerge.java new file mode 100644 index 0000000..a519ec2 --- /dev/null +++ b/src/main/java/org/qed/Generated/MinusMerge.java @@ -0,0 +1,39 @@ +package org.qed.Generated; + +import org.apache.calcite.plan.RelOptRuleCall; +import org.apache.calcite.plan.RelRule; +import org.apache.calcite.rel.RelNode; +import org.apache.calcite.rel.core.JoinRelType; +import org.apache.calcite.rel.logical.*; + +public class MinusMerge extends RelRule { + protected MinusMerge(Config config) { + super(config); + } + + @Override + public void onMatch(RelOptRuleCall call) { + var var_5 = call.builder(); + call.transformTo(var_5.push(call.rel(2)).push(call.rel(3)).push(call.rel(4)).union(false, 2).minus(false, 2).build()); + } + + public interface Config extends EmptyConfig { + Config DEFAULT = new Config() {}; + + @Override + default MinusMerge toRule() { + return new MinusMerge(this); + } + + @Override + default String description() { + return "MinusMerge"; + } + + @Override + default RelRule.OperandTransform operandSupplier() { + return s_4 -> s_4.operand(LogicalMinus.class).inputs(s_2 -> s_2.operand(LogicalMinus.class).inputs(s_0 -> s_0.operand(RelNode.class).anyInputs(), s_1 -> s_1.operand(RelNode.class).anyInputs()), s_3 -> s_3.operand(RelNode.class).anyInputs()); + } + + } +} diff --git a/src/main/java/org/qed/Generated/RRuleInstances/MinusMerge.java b/src/main/java/org/qed/Generated/RRuleInstances/MinusMerge.java new file mode 100644 index 0000000..a080685 --- /dev/null +++ b/src/main/java/org/qed/Generated/RRuleInstances/MinusMerge.java @@ -0,0 +1,26 @@ +package org.qed.Generated.RRuleInstances; + +import kala.collection.Map; +import kala.collection.Seq; +import org.apache.calcite.rel.core.JoinRelType; +import org.apache.calcite.sql.fun.SqlStdOperatorTable; +import org.qed.RelRN; +import org.qed.RexRN; +import org.qed.RRule; +import org.qed.RuleBuilder; + +public record MinusMerge() implements RRule { + static final RelRN a = RelRN.scan("A", "Common_Type"); + static final RelRN b = RelRN.scan("B", "Common_Type"); + static final RelRN c = RelRN.scan("C", "Common_Type"); + + @Override + public RelRN before() { + return a.minus(false, b).minus(false, c); + } + + @Override + public RelRN after() { + return a.minus(false, b.union(false, c)); + } +} diff --git a/src/main/java/org/qed/Generated/Tests/MinusMergeTest.java b/src/main/java/org/qed/Generated/Tests/MinusMergeTest.java new file mode 100644 index 0000000..bc9985e --- /dev/null +++ b/src/main/java/org/qed/Generated/Tests/MinusMergeTest.java @@ -0,0 +1,48 @@ +package org.qed.Generated.Tests; + +import kala.collection.Seq; +import kala.tuple.Tuple; +import org.qed.Generated.CalciteTester; +import org.qed.RelType; +import org.qed.Generated.RRuleInstances.UnionMerge; +import org.qed.RuleBuilder; + +/** + * Test for the MinusMerge rule. + */ +public class MinusMergeTest { + + /** + * Run test for MinusMerge rule. + */ + public static void runTest() { + var tester = new CalciteTester(); + var builder = RuleBuilder.create(); + var table = builder.createQedTable(Seq.of( + Tuple.of(RelType.fromString("INTEGER", true), false) + )); + builder.addTable(table); + + var scan1 = builder.scan(table.getName()).build(); + var scan2 = builder.scan(table.getName()).build(); + var scan3 = builder.scan(table.getName()).build(); + + // (A − B) − C + var before = builder.push(scan1).push(scan2).minus(false, 2).push(scan3).minus(false, 2).build(); + + // A − (B ∪ C) + var union = builder.push(scan2).push(scan3).union(false).build(); + var after = builder.push(scan1).push(union).minus(false, 2).build(); + + var runner = CalciteTester.loadRule(org.qed.Generated.MinusMerge.Config.DEFAULT.toRule()); + tester.verify(runner, before, after); + } + + /** + * Main method to run this test independently. + */ + public static void main(String[] args) { + System.out.println("Running MinusMerge test..."); + runTest(); + } +} \ No newline at end of file diff --git a/src/main/java/org/qed/RelRN.java b/src/main/java/org/qed/RelRN.java index ec09567..becc662 100644 --- a/src/main/java/org/qed/RelRN.java +++ b/src/main/java/org/qed/RelRN.java @@ -104,6 +104,10 @@ default Intersect intersect(boolean all, RelRN... sources) { return new Intersect(all, Seq.of(this).appendedAll(sources)); } + default Minus minus (boolean all, RelRN... sources) { + return new Minus(all, Seq.of(this).appendedAll(sources)); + } + default Empty empty() { return new Empty(this); } @@ -180,6 +184,14 @@ public RelNode semantics() { } } + record Minus(boolean all, Seq sources) implements RelRN { + + @Override + public RelNode semantics() { + return RuleBuilder.create().pushAll(sources.map(RelRN::semantics)).minus(all, sources.size()).build(); + } + } + record Empty(RelRN sourceType) implements RelRN { @Override From 2342786e4be796a722eb64b7427c3cde06d003bc Mon Sep 17 00:00:00 2001 From: Yushin Liang Date: Mon, 9 Jun 2025 22:00:16 +0800 Subject: [PATCH 2/2] Ignore .vscode/ folder --- .gitignore | 1 + .vscode/settings.json | 4 ---- 2 files changed, 1 insertion(+), 4 deletions(-) delete mode 100644 .vscode/settings.json diff --git a/.gitignore b/.gitignore index 1b536bd..a91fdb9 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ target/ *.iml .mvn/wrapper/maven-wrapper.jar +.vscode/ diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index a760fd6..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "java.configuration.updateBuildConfiguration": "interactive", - "java.debug.settings.onBuildFailureProceed": true -} \ No newline at end of file