1+ from new_test_framework .utils import tdLog , tdSql , sc , clusterComCheck , tdCom
2+
3+
4+ class TestExplainCaseWhen :
5+
6+ def setup_class (cls ):
7+ tdLog .debug (f"start to execute { __file__ } " )
8+
9+ def test_explain_case_when_scan_range (self ):
10+ """explain verbose true with CASE WHEN subquery
11+
12+ 1. EXPLAIN VERBOSE TRUE on a query that uses CASE WHEN inside a subquery
13+ with the outer WHERE filtering on the computed CASE column should not crash.
14+ 2. The scan time range reported by EXPLAIN VERBOSE TRUE must match the
15+ explicit ts filters in the inner WHERE clause, not the full history range.
16+
17+ Catalog:
18+ - Query:Explain
19+
20+ Since: v3.3.6.0
21+
22+ Labels: common,ci
23+
24+ Jira: TD-34178
25+
26+ History:
27+ - 2026-04-13 Wei Pan Created to cover two related bugs:
28+ (a) EXPLAIN VERBOSE TRUE crashes with "unknown node = CaseWhen"
29+ (b) Server scans wrong time range when outer WHERE filters on a CASE WHEN alias
30+
31+ """
32+
33+ # ------------------------------------------------------------------
34+ # Setup
35+ # ------------------------------------------------------------------
36+ tdSql .execute ("DROP DATABASE IF EXISTS test_case_when_explain" )
37+ tdSql .execute ("CREATE DATABASE test_case_when_explain PRECISION 'ms'" )
38+ tdSql .execute ("USE test_case_when_explain" )
39+ tdSql .execute (
40+ "CREATE STABLE device_data "
41+ "(ts TIMESTAMP, val DOUBLE, zone_id NCHAR(32)) "
42+ "TAGS (device_sn NCHAR(64))"
43+ )
44+ tdSql .execute (
45+ "CREATE TABLE device_001 USING device_data TAGS ('DEV001')"
46+ )
47+ # Insert rows: one before, two inside, one after the test window
48+ tdSql .execute ("INSERT INTO device_001 VALUES ('2020-12-31 23:59:59.000', 0.0, 'UTC')" )
49+ tdSql .execute ("INSERT INTO device_001 VALUES ('2021-01-01 06:00:00.000', 1.0, 'UTC')" )
50+ tdSql .execute ("INSERT INTO device_001 VALUES ('2021-01-01 18:00:00.000', 2.0, 'UTC')" )
51+ tdSql .execute ("INSERT INTO device_001 VALUES ('2021-01-02 00:00:01.000', 3.0, 'UTC')" )
52+
53+ # ------------------------------------------------------------------
54+ # The query under test – DO NOT change this SQL; it is the exact
55+ # pattern reported in TD-34178.
56+ # ------------------------------------------------------------------
57+ # Window: [2021-01-01 00:00:00.000, 2021-01-02 00:00:00.000)
58+ # Expected time range is derived from the server's timezone to avoid
59+ # hardcoding UTC offsets that differ across environments.
60+ inner_ts_start = "2021-01-01 00:00:00.000"
61+ inner_ts_end = "2021-01-02 00:00:00.000"
62+
63+ # Query the server to get the actual epoch-ms for these timestamp strings
64+ tdSql .query (
65+ f"SELECT to_unixtimestamp('{ inner_ts_start } '), to_unixtimestamp('{ inner_ts_end } ') "
66+ f"FROM device_001 LIMIT 1"
67+ )
68+ expected_skey = tdSql .queryResult [0 ][0 ]
69+ expected_ekey = tdSql .queryResult [0 ][1 ] - 1 # exclusive → inclusive
70+
71+ test_sql = f"""
72+ SELECT
73+ time_period,
74+ first(val) AS first_val,
75+ last(val) AS last_val,
76+ first(zone_id) AS zone_id
77+ FROM (
78+ SELECT
79+ CASE
80+ WHEN ts >= '{ inner_ts_start } ' AND ts < '{ inner_ts_end } '
81+ THEN 'PERIOD_20210101'
82+ ELSE NULL
83+ END AS time_period,
84+ val,
85+ zone_id,
86+ ts
87+ FROM device_001
88+ WHERE ts >= '{ inner_ts_start } '
89+ AND ts < '{ inner_ts_end } '
90+ ) t
91+ WHERE time_period IS NOT NULL
92+ GROUP BY time_period
93+ ORDER BY time_period
94+ """
95+
96+ # ------------------------------------------------------------------
97+ # (a) Verify EXPLAIN VERBOSE TRUE does not raise an error
98+ # ------------------------------------------------------------------
99+ tdLog .info ("step1: EXPLAIN VERBOSE TRUE must not crash on CASE WHEN subquery" )
100+ tdSql .query (f"EXPLAIN VERBOSE TRUE { test_sql } " )
101+
102+ # ------------------------------------------------------------------
103+ # (b) Verify the scan time range matches the inner WHERE conditions
104+ # ------------------------------------------------------------------
105+ tdLog .info ("step2: check scan Time Range in EXPLAIN output" )
106+ time_range_line = f"Time Range: [{ expected_skey } , { expected_ekey } ]"
107+ found = False
108+ for row in tdSql .queryResult :
109+ # Each row is a one-column tuple containing the explain text
110+ if time_range_line in str (row [0 ]):
111+ found = True
112+ break
113+ if not found :
114+ tdLog .info (f"EXPLAIN output rows:" )
115+ for row in tdSql .queryResult :
116+ tdLog .info (f" { row [0 ]} " )
117+ assert found , (
118+ f"Expected '{ time_range_line } ' in EXPLAIN VERBOSE TRUE output, "
119+ f"but it was not found. The server may be scanning the wrong time range."
120+ )
121+
122+ # ------------------------------------------------------------------
123+ # (c) EXPLAIN ANALYZE VERBOSE TRUE must also not crash
124+ # ------------------------------------------------------------------
125+ tdLog .info ("step3: EXPLAIN ANALYZE VERBOSE TRUE must not crash" )
126+ tdSql .query (f"EXPLAIN ANALYZE VERBOSE TRUE { test_sql } " )
127+
128+ # ------------------------------------------------------------------
129+ # (d) Functional correctness: only the two rows inside the window
130+ # should be returned.
131+ # ------------------------------------------------------------------
132+ tdLog .info ("step4: query correctness – only rows inside window returned" )
133+ tdSql .query (test_sql )
134+ tdSql .checkRows (1 )
135+ tdSql .checkData (0 , 0 , "PERIOD_20210101" )
136+ tdSql .checkData (0 , 1 , 1.0 ) # first(val)
137+ tdSql .checkData (0 , 2 , 2.0 ) # last(val)
0 commit comments