Skip to content

Conversation

@tjbanghart
Copy link
Contributor

@tjbanghart tjbanghart commented Oct 25, 2025

@tjbanghart tjbanghart force-pushed the calcite-7247 branch 5 times, most recently from 03007c4 to 008d47e Compare October 25, 2025 23:12
@tjbanghart tjbanghart changed the title [CALCITE-7427] Connection config for enabling RuleMatchVisualize [CALCITE-7427] Add connection config to enable RuleMatchVisualizer Oct 25, 2025
@sonarqubecloud
Copy link

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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does this enable visualization or just dumping the data to be visualized in a specific directory?

* 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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You use plural "planners". If there are multiple planners how are the visualizations for each of them distinguished? The output file name seems to be hardwired.

// Enable visualizer
RuleMatchVisualizerHook.INSTANCE.enable(vizDir);

// Directory should be created
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The hook should create the directory?

assertThat(dataFiles, hasSize(1));
assertThat(htmlFiles, hasSize(1));
assertThat(dataFiles.get(0).getName(), containsString(DATA_FILE_PREFIX));
assertThat(htmlFiles.get(0).getName(), containsString(HTML_FILE_PREFIX));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should these files be deleted?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

JUnit takes care of this, see TempDir.

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

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a few too many spaces around here

*/
private void attachVisualizer(RelOptPlanner planner, String outputDir) {

// Check if we've already attached a visualizer to this planner
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"we" is probably not appropriate.


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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, the names are generated; this probably should be documented

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would suggest using plan_%d_%d, as a query might be compiled in several stages and have multiple plans. As far as I know, Calcite does not provide a query id. Some projects that use Calcite may do, though, and I wonder whether there's an easy way to include this in the file name. The query id could be passed as a setter/getter on the RelOptPlanner.

visualizer.attachTo(planner);
visualizerMap.put(planner, visualizer);

// For HepPlanner, we need to manually write the output
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure what is "manual" here.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A VolcanoPlanner will automatically write the output when its done with the planning. However, the writeToFile() needs to be called explicitly for a HepPlanner. So maybe explicitly would be a better word.

hookCloseable.close();

// Write any pending visualizations
for (RuleMatchVisualizer viz : visualizerMap.values()) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I find it strange that "disable" causes writing.
Maybe the method should be called "close", or "flush", or the write calls should be moved somewhere else.

@thomasrebele
Copy link
Contributor

@tjbanghart, thank you for the PR! It makes it easier to use the RuleMatchVisualizer.
When I use the visualizer, I always have to add the same code: an AtomicInteger to distinguish the queries and some code to define the output. I would appreciate to get it into Calcite.


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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would suggest using plan_%d_%d, as a query might be compiled in several stages and have multiple plans. As far as I know, Calcite does not provide a query id. Some projects that use Calcite may do, though, and I wonder whether there's an easy way to include this in the file name. The query id could be passed as a setter/getter on the RelOptPlanner.

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());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would prefer using a LOGGER. Not sure whether a new method in org.apache.calcite.util.trace.CalciteTrace is needed, or whether a LOGGER can be defined as in org.apache.calcite.runtime.ResultSetEnumerable#LOGGER.

visualizer.attachTo(planner);
visualizerMap.put(planner, visualizer);

// For HepPlanner, we need to manually write the output
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A VolcanoPlanner will automatically write the output when its done with the planning. However, the writeToFile() needs to be called explicitly for a HepPlanner. So maybe explicitly would be a better word.

// VolcanoPlanner automatically calls writeToFile() when done

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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use a LOGGER here as well.

/**
* Disables the visualizer.
*/
public synchronized void disable() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The disable() should be called somewhere as well. The Handler defined in org.apache.calcite.jdbc.Driver#createHandler could override onConnectionClose to call it. I would strongly recommend to make the calls to "enable" and "disable" in the same class. So either moving the init code to Driver#createHandler as well, or creating another method in CalciteConnectionImpl to complement the init() method.

*
* @param connection The Calcite connection
*/
public synchronized void enableFromConnection(CalciteConnection connection) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code in CalciteConnectionImpl already checks for an existing dir, so it could just call the enable method directly.

assertThat(dataFiles, hasSize(1));
assertThat(htmlFiles, hasSize(1));
assertThat(dataFiles.get(0).getName(), containsString(DATA_FILE_PREFIX));
assertThat(htmlFiles.get(0).getName(), containsString(HTML_FILE_PREFIX));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

JUnit takes care of this, see TempDir.

@tjbanghart
Copy link
Contributor Author

Thank you both for the reviews! I'll be able to revisit shortly.

@thomasrebele since you're here, is there a way to highlight the cheapest path in the visualizer? If not is there an existing issue to add this functionality?

@thomasrebele
Copy link
Contributor

thomasrebele commented Nov 5, 2025

@tjbanghart, could you explain what you mean by the cheapest path?

I've got a draft to improve the visualizer. You can have a look at #4506. If you have ideas for improvement, feel free to add a comment to https://issues.apache.org/jira/browse/CALCITE-7133 or create a new ticket and tag me in a comment.

The improvement has a feature to show a sub-graph connecting some specified nodes (and hiding all other nodes). Maybe that's close enough to your use case?

@github-actions
Copy link

github-actions bot commented Dec 6, 2025

This pull request has been marked as stale due to 30 days of inactivity. It will be closed in 90 days if no further activity occurs. If you think that’s incorrect or this pull request requires a review, please simply write any comment. If closed, you can revive the PR at any time and @mention a reviewer or discuss it on the [email protected] list. Thank you for your contributions.

@github-actions github-actions bot added the stale label Dec 6, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants