Skip to content

Commit e17b7e4

Browse files
authored
fix: render race branches in explain output (#276)
Treat RACE nodes like JOIN nodes when rendering df.explain() trees, so both left and right branches are shown instead of rendering RACE as a leaf node. Keep join3 extra_nodes handling scoped to JOIN nodes, since RACE is a binary fan-out and should only render left_node and right_node. Add e2e coverage for both the race operator and df.race(...) dry-run explain output, plus a live race instance that verifies the losing branch is rendered with the skipped marker.
1 parent 27a8b8c commit e17b7e4

2 files changed

Lines changed: 77 additions & 8 deletions

File tree

src/explain.rs

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -618,7 +618,7 @@ fn render_children(
618618
);
619619
}
620620
}
621-
"JOIN" => {
621+
"JOIN" | "RACE" => {
622622
let mut branches = Vec::new();
623623
if let Some(ref left_id) = node.left_node {
624624
branches.push(left_id.clone());
@@ -627,13 +627,15 @@ fn render_children(
627627
branches.push(right_id.clone());
628628
}
629629

630-
// Check for extra nodes in join3
631-
if let Some(ref query) = node.query {
632-
if let Ok(cfg) = serde_json::from_str::<serde_json::Value>(query) {
633-
if let Some(extras) = cfg["extra_nodes"].as_array() {
634-
for extra in extras {
635-
if let Some(extra_id) = extra.as_str() {
636-
branches.push(extra_id.to_string());
630+
if node.node_type == "JOIN" {
631+
// Check for extra nodes in join3
632+
if let Some(ref query) = node.query {
633+
if let Ok(cfg) = serde_json::from_str::<serde_json::Value>(query) {
634+
if let Some(extras) = cfg["extra_nodes"].as_array() {
635+
for extra in extras {
636+
if let Some(extra_id) = extra.as_str() {
637+
branches.push(extra_id.to_string());
638+
}
637639
}
638640
}
639641
}

tests/e2e/sql/05_monitoring_and_explain.sql

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,34 @@ BEGIN
7777
RAISE NOTICE 'Dry-run explain passed';
7878
END $body$;
7979

80+
-- Test dry-run explain renders RACE branches for both operator and function forms
81+
DO $body$
82+
DECLARE
83+
explain_output TEXT;
84+
BEGIN
85+
SELECT df.explain($$ 'SELECT ''winner''' | df.sleep(30) $$) INTO explain_output;
86+
87+
IF explain_output NOT LIKE '%RACE%'
88+
OR explain_output NOT LIKE '%branch 1:%'
89+
OR explain_output NOT LIKE '%branch 2:%'
90+
OR explain_output NOT LIKE '%SELECT ''winner''%'
91+
OR explain_output NOT LIKE '%SLEEP 30s%' THEN
92+
RAISE EXCEPTION 'TEST FAILED: operator RACE explain should show both branches, got: %', explain_output;
93+
END IF;
94+
95+
SELECT df.explain($$ df.race('SELECT ''winner''', df.sleep(30)) $$) INTO explain_output;
96+
97+
IF explain_output NOT LIKE '%RACE%'
98+
OR explain_output NOT LIKE '%branch 1:%'
99+
OR explain_output NOT LIKE '%branch 2:%'
100+
OR explain_output NOT LIKE '%SELECT ''winner''%'
101+
OR explain_output NOT LIKE '%SLEEP 30s%' THEN
102+
RAISE EXCEPTION 'TEST FAILED: df.race() explain should show both branches, got: %', explain_output;
103+
END IF;
104+
105+
RAISE NOTICE 'Dry-run RACE explain passed';
106+
END $body$;
107+
80108
-- Test live instance explain
81109
CREATE TEMP TABLE _test_state (instance_id TEXT);
82110

@@ -108,6 +136,45 @@ END $$;
108136

109137
DROP TABLE _test_state;
110138

139+
-- Test live RACE explain shows the skipped losing branch
140+
CREATE TEMP TABLE _test_race_explain_state (instance_id TEXT);
141+
142+
INSERT INTO _test_race_explain_state
143+
SELECT df.start(df.race('SELECT ''winner''', df.sleep(10)), 'test-race-explain');
144+
145+
DO $$
146+
DECLARE
147+
inst_id TEXT;
148+
status TEXT;
149+
explain_output TEXT;
150+
BEGIN
151+
SELECT instance_id INTO inst_id FROM _test_race_explain_state;
152+
153+
SELECT df.await_instance(inst_id, 20) INTO status;
154+
155+
IF status != 'completed' THEN
156+
RAISE EXCEPTION 'TEST FAILED: expected completed RACE instance, got %', status;
157+
END IF;
158+
159+
SELECT df.explain(inst_id) INTO explain_output;
160+
161+
IF explain_output NOT LIKE '%RACE%'
162+
OR explain_output NOT LIKE '%branch 1:%'
163+
OR explain_output NOT LIKE '%branch 2:%'
164+
OR explain_output NOT LIKE '%SELECT ''winner''%'
165+
OR explain_output NOT LIKE '%SLEEP 10s%' THEN
166+
RAISE EXCEPTION 'TEST FAILED: live RACE explain should show both branches, got: %', explain_output;
167+
END IF;
168+
169+
IF explain_output NOT LIKE '%⊘%' THEN
170+
RAISE EXCEPTION 'TEST FAILED: live RACE explain should show skipped marker for losing branch, got: %', explain_output;
171+
END IF;
172+
173+
RAISE NOTICE 'TEST PASSED: live race explain';
174+
END $$;
175+
176+
DROP TABLE _test_race_explain_state;
177+
111178
-- === Test: 31_explain_plain_sql ===
112179

113180
DO $body$

0 commit comments

Comments
 (0)