Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,9 @@ public interface CalciteConnectionConfig extends ConnectionConfig {
/** Returns the value of {@link CalciteConnectionProperty#TOPDOWN_OPT}. */
boolean topDownOpt();

/** Returns the value of {@link CalciteConnectionProperty#RULE_VISUALIZER_DIR}. */
@Nullable String ruleVisualizerDir();

/** Returns the value of {@link CalciteConnectionProperty#META_TABLE_FACTORY},
* or a default meta table factory if not set. If
* {@code defaultMetaTableFactory} is not null, the result is never null. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,11 @@ public boolean isSet(CalciteConnectionProperty property) {
.getBoolean();
}

@Override public @Nullable String ruleVisualizerDir() {
return CalciteConnectionProperty.RULE_VISUALIZER_DIR.wrap(properties)
.getString();
}

@Override public <T> @PolyNull T metaTableFactory(
Class<T> metaTableFactoryClass,
@PolyNull T defaultMetaTableFactory) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,12 @@ public enum CalciteConnectionProperty implements ConnectionProperty {
LENIENT_OPERATOR_LOOKUP("lenientOperatorLookup", Type.BOOLEAN, false, false),

/** Whether to enable top-down optimization in Volcano planner. */
TOPDOWN_OPT("topDownOpt", Type.BOOLEAN, CalciteSystemProperty.TOPDOWN_OPT.value(), false);
TOPDOWN_OPT("topDownOpt", Type.BOOLEAN, CalciteSystemProperty.TOPDOWN_OPT.value(), false),

/** Directory path for RuleMatchVisualizer output.
* If set, enables visualization of the rule matching process during query optimization.
* The visualizer will create HTML and JSON files in the specified directory. */
RULE_VISUALIZER_DIR("ruleVisualizerDir", Type.STRING, null, false);

private final String camelName;
private final Type type;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
import org.apache.calcite.tools.RelRunner;
import org.apache.calcite.util.BuiltInMethod;
import org.apache.calcite.util.Holder;
import org.apache.calcite.util.RuleMatchVisualizerHook;
import org.apache.calcite.util.Util;

import com.google.common.collect.ImmutableList;
Expand Down Expand Up @@ -188,6 +189,18 @@ void init() {
true, true);
}
}

// Enable RuleMatchVisualizer if configured
CalciteConnectionConfig cfg = config();
String vizDir = cfg.ruleVisualizerDir();
if (vizDir != null && !vizDir.isEmpty()) {
try {
RuleMatchVisualizerHook.INSTANCE.enableFromConnection(this);
} catch (Exception e) {
// Log but don't fail connection if visualizer setup fails
System.err.println("Warning: Failed to enable RuleMatchVisualizer: " + e.getMessage());
}
}
}

@Override public <T> T unwrap(Class<T> iface) throws SQLException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,8 @@ public class RuleMatchVisualizer implements RelOptListener {
private static final String INITIAL = "INITIAL";
private static final String FINAL = "FINAL";
public static final String DEFAULT_SET = "default";
public static final String HTML_FILE_PREFIX = "planner-viz";
public static final String DATA_FILE_PREFIX = "planner-viz-data";

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

String htmlFileName = "planner-viz" + outputSuffix + ".html";
String dataFileName = "planner-viz-data" + outputSuffix + ".js";
String htmlFileName = HTML_FILE_PREFIX + outputSuffix + ".html";
String dataFileName = DATA_FILE_PREFIX + outputSuffix + ".js";

String replaceString = "src=\"planner-viz-data.js\"";
String replaceString = "src=\"" + DATA_FILE_PREFIX + ".js\"";
int replaceIndex = htmlTemplate.indexOf(replaceString);
String htmlContent = htmlTemplate.substring(0, replaceIndex)
+ "src=\"" + dataFileName + "\""
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to you under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.calcite.util;

import org.apache.calcite.config.CalciteConnectionConfig;
import org.apache.calcite.jdbc.CalciteConnection;
import org.apache.calcite.plan.RelOptPlanner;
import org.apache.calcite.plan.hep.HepPlanner;
import org.apache.calcite.plan.visualizer.RuleMatchVisualizer;
import org.apache.calcite.runtime.Hook;

import java.io.File;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;

/**
* Utility class to enable RuleMatchVisualizer for Calcite connections.
*
* <p>This class provides hooks to automatically attach a RuleMatchVisualizer
* to planners when a connection specifies the ruleVisualizerDir property.
*
* <p>Usage in JDBC URL:
* <blockquote><pre>
* jdbc:calcite:ruleVisualizerDir=/tmp/calcite-viz
* </pre></blockquote>
*
* <p>Or programmatically:
* <blockquote><pre>
* RuleMatchVisualizerHook.enable("/tmp/calcite-viz");
* </pre></blockquote>
*/
public class RuleMatchVisualizerHook {
public static final RuleMatchVisualizerHook INSTANCE = new RuleMatchVisualizerHook();

private final Map<RelOptPlanner, RuleMatchVisualizer> visualizerMap = new HashMap<>();
private final AtomicInteger queryCounter = new AtomicInteger(0);

private Hook.Closeable hookCloseable = Hook.Closeable.EMPTY;

/** Private constructor to prevent instantiation. */
private RuleMatchVisualizerHook() {}

/**
* Enables the visualizer for all subsequent queries with the specified output directory.
*
* @param outputDir Directory where visualization files will be created
*/
public synchronized void enable(String outputDir) {
hookCloseable.close();

// Ensure the output directory exists
File dir = new File(outputDir);
if (!dir.exists()) {
boolean madeDir = dir.mkdirs();
assert madeDir : "Failed to create directory: " + outputDir;
}

// Install the hook
hookCloseable = Hook.PLANNER.addThread((Consumer<RelOptPlanner>) planner -> {
attachVisualizer(planner, outputDir);
});
}

/**
* Enables the visualizer using the connection's configuration.
* This method checks if the connection has the ruleVisualizerDir property set.
*
* @param connection The Calcite connection
*/
public synchronized void enableFromConnection(CalciteConnection connection) {
CalciteConnectionConfig config = connection.config();
String vizDir = config.ruleVisualizerDir();

if (vizDir != null && !vizDir.isEmpty()) {
enable(vizDir);
}
}

/**
* Disables the visualizer.
*/
public synchronized void disable() {
hookCloseable.close();

// Write any pending visualizations
for (RuleMatchVisualizer viz : visualizerMap.values()) {
viz.writeToFile();
}
visualizerMap.clear();
}

/**
* Attaches a visualizer to the given planner.
*/
private void attachVisualizer(RelOptPlanner planner, String outputDir) {

// Check if we've already attached a visualizer to this planner
if (visualizerMap.containsKey(planner)) {
return;
}

int queryNum = queryCounter.incrementAndGet();
int queryStart = (int) System.currentTimeMillis() / 1000;
String suffix = String.format(Locale.ROOT, "query_%d_%d", queryNum, queryStart);

// Create and attach the visualizer
RuleMatchVisualizer visualizer = new RuleMatchVisualizer(outputDir, suffix);
visualizer.attachTo(planner);
visualizerMap.put(planner, visualizer);

// For HepPlanner, we need to manually write the output
if (planner instanceof HepPlanner) {
// Add a hook to write the visualization after the planner finishes
Hook.PLAN_BEFORE_IMPLEMENTATION.addThread(relRoot -> {
RuleMatchVisualizer viz = visualizerMap.get(planner);
if (viz != null) {
viz.writeToFile();
visualizerMap.remove(planner);
}
});
}
// VolcanoPlanner automatically calls writeToFile() when done

System.out.println("RuleMatchVisualizer enabled: Output will be written to "
+ outputDir + File.separator + suffix + "*");
}

/**
* Checks the system property and enables visualization if set.
* This can be called at application startup.
*/
public void checkSystemProperty() {
String vizDir = System.getProperty("calcite.visualizer.dir");
if (vizDir != null && !vizDir.isEmpty()) {
enable(vizDir);
}
}
}
1 change: 1 addition & 0 deletions core/src/test/java/org/apache/calcite/test/JdbcTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -975,6 +975,7 @@ static void checkMockDdl(AtomicInteger counter, boolean hasCommit,
assertTrue(names.contains("SCHEMA"));
assertTrue(names.contains("TIME_ZONE"));
assertTrue(names.contains("MATERIALIZATIONS_ENABLED"));
assertTrue(names.contains("RULE_VISUALIZER_DIR"));
}

/**
Expand Down
Loading
Loading