Skip to content

Commit 7cc21d1

Browse files
committed
[CALCITE-7427] Connection config for enabling RuleMatchVisualize
1 parent 90712c8 commit 7cc21d1

File tree

8 files changed

+388
-4
lines changed

8 files changed

+388
-4
lines changed

core/src/main/java/org/apache/calcite/config/CalciteConnectionConfig.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,9 @@ public interface CalciteConnectionConfig extends ConnectionConfig {
111111
/** Returns the value of {@link CalciteConnectionProperty#TOPDOWN_OPT}. */
112112
boolean topDownOpt();
113113

114+
/** Returns the value of {@link CalciteConnectionProperty#RULE_VISUALIZER_DIR}. */
115+
@Nullable String ruleVisualizerDir();
116+
114117
/** Returns the value of {@link CalciteConnectionProperty#META_TABLE_FACTORY},
115118
* or a default meta table factory if not set. If
116119
* {@code defaultMetaTableFactory} is not null, the result is never null. */

core/src/main/java/org/apache/calcite/config/CalciteConnectionConfigImpl.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,11 @@ public boolean isSet(CalciteConnectionProperty property) {
215215
.getBoolean();
216216
}
217217

218+
@Override public @Nullable String ruleVisualizerDir() {
219+
return CalciteConnectionProperty.RULE_VISUALIZER_DIR.wrap(properties)
220+
.getString();
221+
}
222+
218223
@Override public <T> @PolyNull T metaTableFactory(
219224
Class<T> metaTableFactoryClass,
220225
@PolyNull T defaultMetaTableFactory) {

core/src/main/java/org/apache/calcite/config/CalciteConnectionProperty.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,12 @@ public enum CalciteConnectionProperty implements ConnectionProperty {
186186
LENIENT_OPERATOR_LOOKUP("lenientOperatorLookup", Type.BOOLEAN, false, false),
187187

188188
/** Whether to enable top-down optimization in Volcano planner. */
189-
TOPDOWN_OPT("topDownOpt", Type.BOOLEAN, CalciteSystemProperty.TOPDOWN_OPT.value(), false);
189+
TOPDOWN_OPT("topDownOpt", Type.BOOLEAN, CalciteSystemProperty.TOPDOWN_OPT.value(), false),
190+
191+
/** Directory path for RuleMatchVisualizer output.
192+
* If set, enables visualization of the rule matching process during query optimization.
193+
* The visualizer will create HTML and JSON files in the specified directory. */
194+
RULE_VISUALIZER_DIR("ruleVisualizerDir", Type.STRING, null, false);
190195

191196
private final String camelName;
192197
private final Type type;

core/src/main/java/org/apache/calcite/jdbc/CalciteConnectionImpl.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@
6767
import org.apache.calcite.tools.RelRunner;
6868
import org.apache.calcite.util.BuiltInMethod;
6969
import org.apache.calcite.util.Holder;
70+
import org.apache.calcite.util.RuleMatchVisualizerHook;
7071
import org.apache.calcite.util.Util;
7172

7273
import com.google.common.collect.ImmutableList;
@@ -188,6 +189,18 @@ void init() {
188189
true, true);
189190
}
190191
}
192+
193+
// Enable RuleMatchVisualizer if configured
194+
CalciteConnectionConfig cfg = config();
195+
String vizDir = cfg.ruleVisualizerDir();
196+
if (vizDir != null && !vizDir.isEmpty()) {
197+
try {
198+
RuleMatchVisualizerHook.INSTANCE.enableFromConnection(this);
199+
} catch (Exception e) {
200+
// Log but don't fail connection if visualizer setup fails
201+
System.err.println("Warning: Failed to enable RuleMatchVisualizer: " + e.getMessage());
202+
}
203+
}
191204
}
192205

193206
@Override public <T> T unwrap(Class<T> iface) throws SQLException {

core/src/main/java/org/apache/calcite/plan/visualizer/RuleMatchVisualizer.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,8 @@ public class RuleMatchVisualizer implements RelOptListener {
8080
private static final String INITIAL = "INITIAL";
8181
private static final String FINAL = "FINAL";
8282
public static final String DEFAULT_SET = "default";
83+
public static final String HTML_FILE_PREFIX = "planner-viz";
84+
public static final String DATA_FILE_PREFIX = "planner-viz-data";
8385

8486
// default HTML template can be edited at
8587
// core/src/main/resources/org/apache/calcite/plan/visualizer/viz-template.html
@@ -393,10 +395,10 @@ public void writeToFile() {
393395
requireNonNull(cl.getResourceAsStream(templatePath));
394396
String htmlTemplate = IOUtils.toString(resourceAsStream, UTF_8);
395397

396-
String htmlFileName = "planner-viz" + outputSuffix + ".html";
397-
String dataFileName = "planner-viz-data" + outputSuffix + ".js";
398+
String htmlFileName = HTML_FILE_PREFIX + outputSuffix + ".html";
399+
String dataFileName = DATA_FILE_PREFIX + outputSuffix + ".js";
398400

399-
String replaceString = "src=\"planner-viz-data.js\"";
401+
String replaceString = "src=\"" + DATA_FILE_PREFIX + ".js\"";
400402
int replaceIndex = htmlTemplate.indexOf(replaceString);
401403
String htmlContent = htmlTemplate.substring(0, replaceIndex)
402404
+ "src=\"" + dataFileName + "\""
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to you under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package org.apache.calcite.util;
18+
19+
import org.apache.calcite.config.CalciteConnectionConfig;
20+
import org.apache.calcite.jdbc.CalciteConnection;
21+
import org.apache.calcite.plan.RelOptPlanner;
22+
import org.apache.calcite.plan.hep.HepPlanner;
23+
import org.apache.calcite.plan.visualizer.RuleMatchVisualizer;
24+
import org.apache.calcite.runtime.Hook;
25+
26+
import java.io.File;
27+
import java.util.HashMap;
28+
import java.util.Locale;
29+
import java.util.Map;
30+
import java.util.concurrent.atomic.AtomicInteger;
31+
import java.util.function.Consumer;
32+
33+
/**
34+
* Utility class to enable RuleMatchVisualizer for Calcite connections.
35+
*
36+
* <p>This class provides hooks to automatically attach a RuleMatchVisualizer
37+
* to planners when a connection specifies the ruleVisualizerDir property.
38+
*
39+
* <p>Usage in JDBC URL:
40+
* <blockquote><pre>
41+
* jdbc:calcite:ruleVisualizerDir=/tmp/calcite-viz
42+
* </pre></blockquote>
43+
*
44+
* <p>Or programmatically:
45+
* <blockquote><pre>
46+
* RuleMatchVisualizerHook.enable("/tmp/calcite-viz");
47+
* </pre></blockquote>
48+
*/
49+
public class RuleMatchVisualizerHook {
50+
public static final RuleMatchVisualizerHook INSTANCE = new RuleMatchVisualizerHook();
51+
52+
private final Map<RelOptPlanner, RuleMatchVisualizer> visualizerMap = new HashMap<>();
53+
private final AtomicInteger queryCounter = new AtomicInteger(0);
54+
55+
private Hook.Closeable hookCloseable = Hook.Closeable.EMPTY;
56+
57+
/** Private constructor to prevent instantiation. */
58+
private RuleMatchVisualizerHook() {}
59+
60+
/**
61+
* Enables the visualizer for all subsequent queries with the specified output directory.
62+
*
63+
* @param outputDir Directory where visualization files will be created
64+
*/
65+
public synchronized void enable(String outputDir) {
66+
hookCloseable.close();
67+
68+
// Ensure the output directory exists
69+
File dir = new File(outputDir);
70+
if (!dir.exists()) {
71+
boolean madeDir = dir.mkdirs();
72+
assert madeDir : "Failed to create directory: " + outputDir;
73+
}
74+
75+
// Install the hook
76+
hookCloseable = Hook.PLANNER.addThread((Consumer<RelOptPlanner>) planner -> {
77+
attachVisualizer(planner, outputDir);
78+
});
79+
}
80+
81+
/**
82+
* Enables the visualizer using the connection's configuration.
83+
* This method checks if the connection has the ruleVisualizerDir property set.
84+
*
85+
* @param connection The Calcite connection
86+
*/
87+
public synchronized void enableFromConnection(CalciteConnection connection) {
88+
CalciteConnectionConfig config = connection.config();
89+
String vizDir = config.ruleVisualizerDir();
90+
91+
if (vizDir != null && !vizDir.isEmpty()) {
92+
enable(vizDir);
93+
}
94+
}
95+
96+
/**
97+
* Disables the visualizer.
98+
*/
99+
public synchronized void disable() {
100+
hookCloseable.close();
101+
102+
// Write any pending visualizations
103+
for (RuleMatchVisualizer viz : visualizerMap.values()) {
104+
viz.writeToFile();
105+
}
106+
visualizerMap.clear();
107+
}
108+
109+
/**
110+
* Attaches a visualizer to the given planner.
111+
*/
112+
private void attachVisualizer(RelOptPlanner planner, String outputDir) {
113+
114+
// Check if we've already attached a visualizer to this planner
115+
if (visualizerMap.containsKey(planner)) {
116+
return;
117+
}
118+
119+
int queryNum = queryCounter.incrementAndGet();
120+
int queryStart = (int) System.currentTimeMillis() / 1000;
121+
String suffix = String.format(Locale.ROOT, "query_%d_%d", queryNum, queryStart);
122+
123+
// Create and attach the visualizer
124+
RuleMatchVisualizer visualizer = new RuleMatchVisualizer(outputDir, suffix);
125+
visualizer.attachTo(planner);
126+
visualizerMap.put(planner, visualizer);
127+
128+
// For HepPlanner, we need to manually write the output
129+
if (planner instanceof HepPlanner) {
130+
// Add a hook to write the visualization after the planner finishes
131+
Hook.PLAN_BEFORE_IMPLEMENTATION.addThread(relRoot -> {
132+
RuleMatchVisualizer viz = visualizerMap.get(planner);
133+
if (viz != null) {
134+
viz.writeToFile();
135+
visualizerMap.remove(planner);
136+
}
137+
});
138+
}
139+
// VolcanoPlanner automatically calls writeToFile() when done
140+
141+
System.out.println("RuleMatchVisualizer enabled: Output will be written to "
142+
+ outputDir + File.separator + suffix + "*");
143+
}
144+
145+
/**
146+
* Checks the system property and enables visualization if set.
147+
* This can be called at application startup.
148+
*/
149+
public void checkSystemProperty() {
150+
String vizDir = System.getProperty("calcite.visualizer.dir");
151+
if (vizDir != null && !vizDir.isEmpty()) {
152+
enable(vizDir);
153+
}
154+
}
155+
}

core/src/test/java/org/apache/calcite/test/JdbcTest.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -975,6 +975,7 @@ static void checkMockDdl(AtomicInteger counter, boolean hasCommit,
975975
assertTrue(names.contains("SCHEMA"));
976976
assertTrue(names.contains("TIME_ZONE"));
977977
assertTrue(names.contains("MATERIALIZATIONS_ENABLED"));
978+
assertTrue(names.contains("RULE_VISUALIZER_DIR"));
978979
}
979980

980981
/**

0 commit comments

Comments
 (0)