Line data Source code
1 : #include "backend/engine/duckdb/transpiler/transpiler_test_fixture.h"
2 :
3 : namespace bigquery_emulator {
4 : namespace backend {
5 : namespace engine {
6 : namespace duckdb {
7 : namespace transpiler {
8 :
9 1 : TEST_F(TranspilerTest, EmitSetOperationScanUnionAll) {
10 : // BigQuery `<lhs> UNION ALL <rhs>` analyzes to a
11 : // `ResolvedSetOperationScan` whose `op_type=UNION_ALL`,
12 : // `column_match_mode=BY_POSITION`, and two
13 : // `ResolvedSetOperationItem`s. GoogleSQL takes the parent
14 : // column's name from the leftmost input's column (`id` here), so
15 : // the LHS item's projection collapses (`"id"` -> `"id"`) and the
16 : // RHS item renames `order_id` to `id` to land on the parent's
17 : // column name.
18 : //
19 : // The analyzer wraps each `SELECT <col> FROM <table>` arm in a
20 : // ResolvedProjectScan over the ResolvedTableScan (because the
21 : // selected columns are a subset of the table's full column list),
22 : // which is why the per-arm SQL has the extra `(SELECT ... FROM
23 : // (SELECT ... FROM ...))` nesting -- the outer SELECT is the
24 : // set-op item's projection, the middle SELECT is the analyzer's
25 : // ProjectScan, and the inner SELECT is the TableScan.
26 1 : const ::googlesql::ResolvedStatement* stmt =
27 1 : Analyze("SELECT id FROM people UNION ALL SELECT order_id FROM orders");
28 1 : ASSERT_NE(stmt, nullptr);
29 1 : const ::googlesql::ResolvedScan* scan = QueryInputScan(stmt);
30 1 : ASSERT_NE(scan, nullptr);
31 1 : ASSERT_EQ(scan->node_kind(), ::googlesql::RESOLVED_SET_OPERATION_SCAN);
32 1 : TestTranspiler t;
33 1 : EXPECT_EQ(t.EmitSetOperationScan(
34 1 : scan->GetAs<::googlesql::ResolvedSetOperationScan>()),
35 1 : "SELECT \"id\" FROM (SELECT *, 1 AS \"__bq_union_ord\" FROM "
36 1 : "(SELECT \"id\" FROM (SELECT \"id\" FROM (SELECT \"id\", "
37 1 : "\"name\" FROM \"people\"))) UNION ALL SELECT *, 2 AS "
38 1 : "\"__bq_union_ord\" FROM (SELECT \"order_id\" AS \"id\" FROM "
39 1 : "(SELECT \"order_id\" FROM (SELECT \"order_id\", \"amount\" FROM "
40 1 : "\"orders\")))) ORDER BY \"__bq_union_ord\"");
41 1 : }
42 :
43 1 : TEST_F(TranspilerTest, EmitSetOperationScanUnionDistinct) {
44 : // `UNION DISTINCT` lowers to DuckDB's bare `UNION` (DuckDB's
45 : // default duplicate-handling on `UNION` is DISTINCT, matching the
46 : // BigQuery semantics). The per-item projection shape is the same
47 : // as UNION ALL because the duplicate-handling is the only
48 : // difference between the two ops.
49 1 : const ::googlesql::ResolvedStatement* stmt = Analyze(
50 1 : "SELECT id FROM people UNION DISTINCT SELECT order_id FROM orders");
51 1 : ASSERT_NE(stmt, nullptr);
52 1 : const ::googlesql::ResolvedScan* scan = QueryInputScan(stmt);
53 1 : ASSERT_NE(scan, nullptr);
54 1 : ASSERT_EQ(scan->node_kind(), ::googlesql::RESOLVED_SET_OPERATION_SCAN);
55 1 : TestTranspiler t;
56 1 : EXPECT_EQ(t.EmitSetOperationScan(
57 1 : scan->GetAs<::googlesql::ResolvedSetOperationScan>()),
58 1 : "SELECT \"id\" FROM (SELECT \"id\" FROM (SELECT \"id\", \"name\" "
59 1 : "FROM \"people\"))"
60 1 : " UNION "
61 1 : "SELECT \"order_id\" AS \"id\" FROM (SELECT \"order_id\" FROM "
62 1 : "(SELECT \"order_id\", \"amount\" FROM \"orders\"))");
63 1 : }
64 :
65 1 : TEST_F(TranspilerTest, EmitSetOperationScanIntersectDistinct) {
66 : // `INTERSECT DISTINCT` lowers to DuckDB's bare `INTERSECT` (also
67 : // DISTINCT by default). Same item shape as UNION; only the
68 : // keyword between items changes. The output column name comes
69 : // from the leftmost input's column.
70 1 : const ::googlesql::ResolvedStatement* stmt = Analyze(
71 1 : "SELECT id FROM people INTERSECT DISTINCT SELECT order_id FROM orders");
72 1 : ASSERT_NE(stmt, nullptr);
73 1 : const ::googlesql::ResolvedScan* scan = QueryInputScan(stmt);
74 1 : ASSERT_NE(scan, nullptr);
75 1 : ASSERT_EQ(scan->node_kind(), ::googlesql::RESOLVED_SET_OPERATION_SCAN);
76 1 : TestTranspiler t;
77 1 : EXPECT_EQ(t.EmitSetOperationScan(
78 1 : scan->GetAs<::googlesql::ResolvedSetOperationScan>()),
79 1 : "SELECT \"id\" FROM (SELECT \"id\" FROM (SELECT \"id\", \"name\" "
80 1 : "FROM \"people\"))"
81 1 : " INTERSECT "
82 1 : "SELECT \"order_id\" AS \"id\" FROM (SELECT \"order_id\" FROM "
83 1 : "(SELECT \"order_id\", \"amount\" FROM \"orders\"))");
84 1 : }
85 :
86 1 : TEST_F(TranspilerTest, EmitSetOperationScanExceptDistinct) {
87 : // `EXCEPT DISTINCT` lowers to DuckDB's bare `EXCEPT` (DISTINCT by
88 : // default). BigQuery's EXCEPT DISTINCT semantics (row R in LHS at
89 : // least once and absent from RHS) match DuckDB's bag-difference
90 : // followed by DISTINCT.
91 1 : const ::googlesql::ResolvedStatement* stmt = Analyze(
92 1 : "SELECT id FROM people EXCEPT DISTINCT SELECT order_id FROM orders");
93 1 : ASSERT_NE(stmt, nullptr);
94 1 : const ::googlesql::ResolvedScan* scan = QueryInputScan(stmt);
95 1 : ASSERT_NE(scan, nullptr);
96 1 : ASSERT_EQ(scan->node_kind(), ::googlesql::RESOLVED_SET_OPERATION_SCAN);
97 1 : TestTranspiler t;
98 1 : EXPECT_EQ(t.EmitSetOperationScan(
99 1 : scan->GetAs<::googlesql::ResolvedSetOperationScan>()),
100 1 : "SELECT * FROM (SELECT \"id\" FROM (SELECT \"id\" FROM "
101 1 : "(SELECT \"id\", \"name\" FROM \"people\")) EXCEPT SELECT "
102 1 : "\"order_id\" AS \"id\" FROM (SELECT \"order_id\" FROM "
103 1 : "(SELECT \"order_id\", \"amount\" FROM \"orders\"))) ORDER BY 1");
104 1 : }
105 :
106 1 : TEST_F(TranspilerTest, EmitSetOperationScanIdenticalArmsBothCollapse) {
107 : // When both arms expose the same column name as the parent's
108 : // output column, the per-item AS aliases collapse on both sides.
109 : // Identical-arm `SELECT id FROM people UNION ALL SELECT id FROM
110 : // people` is the smallest input that exercises the both-side
111 : // collapse path -- both items project `"id"` onto the parent's
112 : // `id` column, so neither projection needs an AS keyword. This
113 : // pins the symmetric-collapse path that the per-arm-rename tests
114 : // above leave only half-covered.
115 1 : const ::googlesql::ResolvedStatement* stmt =
116 1 : Analyze("SELECT id FROM people UNION ALL SELECT id FROM people");
117 1 : ASSERT_NE(stmt, nullptr);
118 1 : const ::googlesql::ResolvedScan* scan = QueryInputScan(stmt);
119 1 : ASSERT_NE(scan, nullptr);
120 1 : ASSERT_EQ(scan->node_kind(), ::googlesql::RESOLVED_SET_OPERATION_SCAN);
121 1 : TestTranspiler t;
122 1 : EXPECT_EQ(t.EmitSetOperationScan(
123 1 : scan->GetAs<::googlesql::ResolvedSetOperationScan>()),
124 1 : "SELECT \"id\" FROM (SELECT *, 1 AS \"__bq_union_ord\" FROM "
125 1 : "(SELECT \"id\" FROM (SELECT \"id\" FROM (SELECT \"id\", "
126 1 : "\"name\" FROM \"people\"))) UNION ALL SELECT *, 2 AS "
127 1 : "\"__bq_union_ord\" FROM (SELECT \"id\" FROM (SELECT \"id\" FROM "
128 1 : "(SELECT \"id\", \"name\" FROM \"people\")))) ORDER BY "
129 1 : "\"__bq_union_ord\"");
130 1 : }
131 :
132 1 : TEST_F(TranspilerTest, EmitSetOperationScanMultiColumnPreservesOrder) {
133 : // Two-column UNION ALL. The LHS exposes `id, name`; the RHS
134 : // (`SELECT order_id, CAST(amount AS STRING) FROM orders`)
135 : // renames both columns to land on the LHS-named output columns
136 : // (`id`, `name`). The test pins that the per-item projections
137 : // honor positional column matching even when each column needs a
138 : // different rename direction.
139 : //
140 : // The analyzer assigns column IDs across the whole query and
141 : // hands the synthesized computed-column name through them; the
142 : // CAST in the RHS lands as `$col2` (slot 2 in the overall
143 : // computed-column ordering), not `$col1`. The set-op item
144 : // renames both onto the parent's `id` / `name`.
145 1 : const ::googlesql::ResolvedStatement* stmt = Analyze(
146 1 : "SELECT id, name FROM people UNION ALL "
147 1 : "SELECT order_id, CAST(amount AS STRING) FROM orders");
148 1 : ASSERT_NE(stmt, nullptr);
149 1 : const ::googlesql::ResolvedScan* scan = QueryInputScan(stmt);
150 1 : ASSERT_NE(scan, nullptr);
151 1 : ASSERT_EQ(scan->node_kind(), ::googlesql::RESOLVED_SET_OPERATION_SCAN);
152 1 : TestTranspiler t;
153 1 : EXPECT_EQ(
154 1 : t.EmitSetOperationScan(
155 1 : scan->GetAs<::googlesql::ResolvedSetOperationScan>()),
156 1 : "SELECT \"id\", \"name\" FROM (SELECT *, 1 AS \"__bq_union_ord\" FROM "
157 1 : "(SELECT \"id\", \"name\" FROM (SELECT \"id\", \"name\" FROM "
158 1 : "\"people\")) UNION ALL SELECT *, 2 AS \"__bq_union_ord\" FROM "
159 1 : "(SELECT \"order_id\" AS \"id\", \"$col2\" AS \"name\" FROM (SELECT "
160 1 : "\"order_id\", CAST(\"amount\" AS VARCHAR) AS \"$col2\" FROM (SELECT "
161 1 : "\"order_id\", \"amount\" FROM \"orders\")))) ORDER BY "
162 1 : "\"__bq_union_ord\"");
163 1 : }
164 :
165 1 : TEST_F(TranspilerTest, EmitSetOperationScanThreeArmFlattening) {
166 : // BigQuery / GoogleSQL flattens same-op chains so a three-way
167 : // `UNION ALL` lands as a single `ResolvedSetOperationScan` with
168 : // three items, not a tree. The emit joins all three items with
169 : // the keyword.
170 1 : const ::googlesql::ResolvedStatement* stmt = Analyze(
171 1 : "SELECT id FROM people UNION ALL SELECT order_id FROM orders "
172 1 : "UNION ALL SELECT amount FROM orders");
173 1 : ASSERT_NE(stmt, nullptr);
174 1 : const ::googlesql::ResolvedScan* scan = QueryInputScan(stmt);
175 1 : ASSERT_NE(scan, nullptr);
176 1 : ASSERT_EQ(scan->node_kind(), ::googlesql::RESOLVED_SET_OPERATION_SCAN);
177 1 : const auto* set_op = scan->GetAs<::googlesql::ResolvedSetOperationScan>();
178 1 : ASSERT_EQ(set_op->input_item_list_size(), 3);
179 1 : TestTranspiler t;
180 1 : EXPECT_EQ(t.EmitSetOperationScan(set_op),
181 1 : "SELECT \"id\" FROM (SELECT *, 1 AS \"__bq_union_ord\" FROM "
182 1 : "(SELECT \"id\" FROM (SELECT \"id\" FROM (SELECT \"id\", "
183 1 : "\"name\" FROM \"people\"))) UNION ALL SELECT *, 2 AS "
184 1 : "\"__bq_union_ord\" FROM (SELECT \"order_id\" AS \"id\" FROM "
185 1 : "(SELECT \"order_id\" FROM (SELECT \"order_id\", \"amount\" FROM "
186 1 : "\"orders\"))) UNION ALL SELECT *, 3 AS \"__bq_union_ord\" FROM "
187 1 : "(SELECT \"amount\" AS \"id\" FROM (SELECT \"amount\" FROM "
188 1 : "(SELECT \"order_id\", \"amount\" FROM \"orders\")))) ORDER BY "
189 1 : "\"__bq_union_ord\"");
190 1 : }
191 :
192 1 : TEST_F(TranspilerTest, EmitSetOperationScanNestedDifferentOps) {
193 : // Mixing operators (UNION ALL outside, INTERSECT DISTINCT inside)
194 : // forces the analyzer to nest: the outer UNION ALL has one
195 : // TableScan-y item plus one SetOperationScan item. The emit
196 : // composes recursively -- each item's child scan goes through
197 : // `EmitScan`, which dispatches back to `EmitSetOperationScan`
198 : // for the inner set-op.
199 1 : const ::googlesql::ResolvedStatement* stmt = Analyze(
200 1 : "SELECT id FROM people UNION ALL "
201 1 : "(SELECT order_id FROM orders INTERSECT DISTINCT "
202 1 : "SELECT amount FROM orders)");
203 1 : ASSERT_NE(stmt, nullptr);
204 1 : const ::googlesql::ResolvedScan* scan = QueryInputScan(stmt);
205 1 : ASSERT_NE(scan, nullptr);
206 1 : ASSERT_EQ(scan->node_kind(), ::googlesql::RESOLVED_SET_OPERATION_SCAN);
207 1 : TestTranspiler t;
208 : // The inner INTERSECT's parent column name is `order_id` (the
209 : // leftmost input's column), and the outer UNION ALL renames it
210 : // onto its own parent column `id` for the second arm.
211 1 : EXPECT_EQ(t.EmitSetOperationScan(
212 1 : scan->GetAs<::googlesql::ResolvedSetOperationScan>()),
213 1 : "SELECT \"id\" FROM (SELECT *, 1 AS \"__bq_union_ord\" FROM "
214 1 : "(SELECT \"id\" FROM (SELECT \"id\" FROM (SELECT \"id\", "
215 1 : "\"name\" FROM \"people\"))) UNION ALL SELECT *, 2 AS "
216 1 : "\"__bq_union_ord\" FROM (SELECT \"order_id\" AS \"id\" FROM "
217 1 : "(SELECT \"order_id\" FROM (SELECT \"order_id\" FROM (SELECT "
218 1 : "\"order_id\", \"amount\" FROM \"orders\")) INTERSECT SELECT "
219 1 : "\"amount\" AS \"order_id\" FROM (SELECT \"amount\" FROM (SELECT "
220 1 : "\"order_id\", \"amount\" FROM \"orders\"))))) ORDER BY "
221 1 : "\"__bq_union_ord\"");
222 1 : }
223 :
224 1 : TEST_F(TranspilerTest, EmitSetOperationScanFallsBackOnUnloweredChild) {
225 : // If any child scan returns "" the whole set-op emit must
226 : // propagate the empty string. `BIT_COUNT` is on the
227 : // `semantic_executor` route in the YAML disposition table so the
228 : // right-hand ProjectScan's computed column emit returns "" ->
229 : // ProjectScan returns "" -> set-op item returns "" -> set-op
230 : // scan returns "".
231 1 : const ::googlesql::ResolvedStatement* stmt = Analyze(
232 1 : "SELECT id FROM people UNION ALL SELECT BIT_COUNT(amount) FROM orders");
233 1 : ASSERT_NE(stmt, nullptr);
234 1 : const ::googlesql::ResolvedScan* scan = QueryInputScan(stmt);
235 1 : ASSERT_NE(scan, nullptr);
236 1 : ASSERT_EQ(scan->node_kind(), ::googlesql::RESOLVED_SET_OPERATION_SCAN);
237 1 : TestTranspiler t;
238 1 : EXPECT_EQ(t.EmitSetOperationScan(
239 1 : scan->GetAs<::googlesql::ResolvedSetOperationScan>()),
240 1 : "");
241 1 : }
242 :
243 1 : TEST_F(TranspilerTest, EmitSetOperationScanUnionAllDuplicateBehaviorContrast) {
244 : // Execution-style contrast between UNION ALL and UNION DISTINCT
245 : // on the *same* input shape. We assert on the SQL strings (this
246 : // fixture does not have a running DuckDB connection) so a
247 : // regression in either keyword choice surfaces here. The
248 : // expected duplicate behavior is documented in
249 : // `ResolvedSetOperationScan`'s comment block in
250 : // `resolved_ast.h` (UNION ALL keeps all rows, UNION DISTINCT
251 : // dedupes); DuckDB's `UNION ALL` and `UNION` (DISTINCT by
252 : // default) match that contract.
253 : //
254 : // We compute each side's SQL fully and discard the analyzer's
255 : // output before re-`Analyze`-ing for the next side. The fixture
256 : // `last_output_` slot is single-shot (the second `Analyze` call
257 : // would otherwise free the first AST out from under us), so the
258 : // strings are the durable artifact we compare across the two
259 : // emits.
260 1 : std::string sql_all;
261 1 : {
262 1 : const ::googlesql::ResolvedStatement* stmt =
263 1 : Analyze("SELECT id FROM people UNION ALL SELECT id FROM people");
264 1 : ASSERT_NE(stmt, nullptr);
265 1 : const ::googlesql::ResolvedScan* scan = QueryInputScan(stmt);
266 1 : ASSERT_NE(scan, nullptr);
267 1 : ASSERT_EQ(scan->node_kind(), ::googlesql::RESOLVED_SET_OPERATION_SCAN);
268 1 : TestTranspiler t;
269 1 : sql_all = t.EmitSetOperationScan(
270 1 : scan->GetAs<::googlesql::ResolvedSetOperationScan>());
271 1 : }
272 0 : std::string sql_distinct;
273 1 : {
274 1 : const ::googlesql::ResolvedStatement* stmt =
275 1 : Analyze("SELECT id FROM people UNION DISTINCT SELECT id FROM people");
276 1 : ASSERT_NE(stmt, nullptr);
277 1 : const ::googlesql::ResolvedScan* scan = QueryInputScan(stmt);
278 1 : ASSERT_NE(scan, nullptr);
279 1 : ASSERT_EQ(scan->node_kind(), ::googlesql::RESOLVED_SET_OPERATION_SCAN);
280 1 : TestTranspiler t;
281 1 : sql_distinct = t.EmitSetOperationScan(
282 1 : scan->GetAs<::googlesql::ResolvedSetOperationScan>());
283 1 : }
284 0 : EXPECT_NE(sql_all, sql_distinct);
285 1 : EXPECT_NE(sql_all.find(" UNION ALL "), std::string::npos);
286 1 : EXPECT_EQ(sql_all.find(" UNION DISTINCT "), std::string::npos);
287 1 : EXPECT_EQ(sql_distinct.find(" UNION ALL "), std::string::npos);
288 : // The bare ` UNION ` keyword (with spaces on both sides) needs
289 : // to land in the distinct emit; we deliberately do NOT match a
290 : // substring `UNION` because that would also match `UNION ALL`.
291 1 : EXPECT_NE(sql_distinct.find(" UNION "), std::string::npos);
292 1 : }
293 :
294 : // Helper: synthesize a `ResolvedSetOperationScan` directly so the
295 : // fallback paths can be exercised without depending on the
296 : // analyzer producing the matching surface SQL. Each "arm" of the
297 : // set operation is a fresh `ResolvedSingleRowScan` so the inner
298 : // emit composes onto `SELECT 1`. The parent's `column_list` is
299 : // left empty so the per-item projection lands on `SELECT *`,
300 : // which keeps the fallback assertions focused on the
301 : // kind/mode-bail behavior of `EmitSetOperationScan` rather than
302 : // the per-item projection logic.
303 : std::unique_ptr<::googlesql::ResolvedSetOperationScan> MakeTestSetOperationScan(
304 : ::googlesql::ResolvedSetOperationScan::SetOperationType op_type,
305 : ::googlesql::ResolvedSetOperationScan::SetOperationColumnMatchMode
306 2 : match_mode) {
307 2 : std::vector<std::unique_ptr<const ::googlesql::ResolvedSetOperationItem>>
308 2 : items;
309 2 : items.push_back(::googlesql::MakeResolvedSetOperationItem(
310 2 : ::googlesql::MakeResolvedSingleRowScan(),
311 2 : /*output_column_list=*/{}));
312 2 : items.push_back(::googlesql::MakeResolvedSetOperationItem(
313 2 : ::googlesql::MakeResolvedSingleRowScan(),
314 2 : /*output_column_list=*/{}));
315 2 : auto scan = ::googlesql::MakeResolvedSetOperationScan(
316 2 : /*column_list=*/{}, op_type, std::move(items));
317 2 : scan->set_column_match_mode(match_mode);
318 2 : scan->set_column_propagation_mode(
319 2 : ::googlesql::ResolvedSetOperationScan::STRICT);
320 2 : return scan;
321 2 : }
322 :
323 1 : TEST_F(TranspilerTest, EmitSetOperationScanCorrespondingEmitsKeyword) {
324 : // `CORRESPONDING` uses the analyzer's per-item `output_column_list`
325 : // mapping; `EmitSetOperationItem` projects each arm before the keyword.
326 1 : const ::googlesql::ResolvedStatement* stmt = Analyze(
327 1 : "SELECT x, y FROM (SELECT 1 AS x, 'a' AS y UNION ALL CORRESPONDING "
328 1 : "SELECT 'b' AS y, 2 AS x)");
329 1 : ASSERT_NE(stmt, nullptr);
330 1 : const ::googlesql::ResolvedScan* scan = QueryInputScan(stmt);
331 1 : ASSERT_NE(scan, nullptr);
332 1 : ASSERT_EQ(scan->node_kind(), ::googlesql::RESOLVED_SET_OPERATION_SCAN);
333 1 : const auto* set_op = scan->GetAs<::googlesql::ResolvedSetOperationScan>();
334 1 : ASSERT_EQ(set_op->column_match_mode(),
335 1 : ::googlesql::ResolvedSetOperationScan::CORRESPONDING);
336 1 : TestTranspiler t;
337 1 : const std::string sql = t.EmitSetOperationScan(set_op);
338 1 : EXPECT_FALSE(sql.empty());
339 1 : EXPECT_NE(sql.find(" UNION ALL "), std::string::npos);
340 1 : EXPECT_NE(sql.find("\"x\""), std::string::npos);
341 1 : EXPECT_NE(sql.find("\"y\""), std::string::npos);
342 1 : }
343 :
344 1 : TEST_F(TranspilerTest, EmitSetOperationScanIntersectAllEmitsKeyword) {
345 : // `INTERSECT_ALL` (DuckDB-native extension; not in BQ surface
346 : // SQL) emits the matching `INTERSECT ALL` keyword. The standard
347 : // SQL bag semantics (`min(m, n)`) match the GoogleSQL
348 : // `INTERSECT_ALL` contract per `resolved_ast.h`, so the lowered
349 : // SQL preserves the analyzer's intent.
350 1 : auto scan = MakeTestSetOperationScan(
351 1 : ::googlesql::ResolvedSetOperationScan::INTERSECT_ALL,
352 1 : ::googlesql::ResolvedSetOperationScan::BY_POSITION);
353 1 : TestTranspiler t;
354 1 : EXPECT_EQ(t.EmitSetOperationScan(scan.get()),
355 1 : "SELECT * FROM (SELECT 1) INTERSECT ALL SELECT * FROM (SELECT 1)");
356 1 : }
357 :
358 1 : TEST_F(TranspilerTest, EmitSetOperationScanExceptAllEmitsKeyword) {
359 : // `EXCEPT_ALL` similarly emits `EXCEPT ALL`. DuckDB has shipped
360 : // `EXCEPT ALL` with standard SQL bag-difference semantics
361 : // (`max(m - n, 0)`) since v0.10; the GoogleSQL contract is the
362 : // same.
363 1 : auto scan = MakeTestSetOperationScan(
364 1 : ::googlesql::ResolvedSetOperationScan::EXCEPT_ALL,
365 1 : ::googlesql::ResolvedSetOperationScan::BY_POSITION);
366 1 : TestTranspiler t;
367 1 : EXPECT_EQ(t.EmitSetOperationScan(scan.get()),
368 1 : "SELECT * FROM (SELECT * FROM (SELECT 1) EXCEPT ALL SELECT * "
369 1 : "FROM (SELECT 1)) ORDER BY 1");
370 1 : }
371 :
372 : // --- Sample scan -------------------------------------------------------
373 :
374 : } // namespace transpiler
375 : } // namespace duckdb
376 : } // namespace engine
377 : } // namespace backend
378 : } // namespace bigquery_emulator
|