1 | #include "duckdb/parser/transformer.hpp" |
---|---|
2 | #include "duckdb/common/enums/set_operation_type.hpp" |
3 | #include "duckdb/common/exception.hpp" |
4 | #include "duckdb/parser/statement/select_statement.hpp" |
5 | #include "duckdb/parser/query_node/recursive_cte_node.hpp" |
6 | |
7 | namespace duckdb { |
8 | |
9 | unique_ptr<CommonTableExpressionInfo> CommonTableExpressionInfo::Copy() { |
10 | auto result = make_uniq<CommonTableExpressionInfo>(); |
11 | result->aliases = aliases; |
12 | result->query = unique_ptr_cast<SQLStatement, SelectStatement>(src: query->Copy()); |
13 | return result; |
14 | } |
15 | |
16 | void Transformer::ExtractCTEsRecursive(CommonTableExpressionMap &cte_map) { |
17 | for (auto &cte_entry : stored_cte_map) { |
18 | for (auto &entry : cte_entry->map) { |
19 | auto found_entry = cte_map.map.find(x: entry.first); |
20 | if (found_entry != cte_map.map.end()) { |
21 | // entry already present - use top-most entry |
22 | continue; |
23 | } |
24 | cte_map.map[entry.first] = entry.second->Copy(); |
25 | } |
26 | } |
27 | if (parent) { |
28 | parent->ExtractCTEsRecursive(cte_map); |
29 | } |
30 | } |
31 | |
32 | void Transformer::TransformCTE(duckdb_libpgquery::PGWithClause &de_with_clause, CommonTableExpressionMap &cte_map) { |
33 | // TODO: might need to update in case of future lawsuit |
34 | stored_cte_map.push_back(x: &cte_map); |
35 | |
36 | D_ASSERT(de_with_clause.ctes); |
37 | for (auto cte_ele = de_with_clause.ctes->head; cte_ele != nullptr; cte_ele = cte_ele->next) { |
38 | auto info = make_uniq<CommonTableExpressionInfo>(); |
39 | |
40 | auto &cte = *PGPointerCast<duckdb_libpgquery::PGCommonTableExpr>(ptr: cte_ele->data.ptr_value); |
41 | if (cte.aliascolnames) { |
42 | for (auto node = cte.aliascolnames->head; node != nullptr; node = node->next) { |
43 | info->aliases.emplace_back( |
44 | args&: reinterpret_cast<duckdb_libpgquery::PGValue *>(node->data.ptr_value)->val.str); |
45 | } |
46 | } |
47 | // lets throw some errors on unsupported features early |
48 | if (cte.ctecolnames) { |
49 | throw NotImplementedException("Column name setting not supported in CTEs"); |
50 | } |
51 | if (cte.ctecoltypes) { |
52 | throw NotImplementedException("Column type setting not supported in CTEs"); |
53 | } |
54 | if (cte.ctecoltypmods) { |
55 | throw NotImplementedException("Column type modification not supported in CTEs"); |
56 | } |
57 | if (cte.ctecolcollations) { |
58 | throw NotImplementedException("CTE collations not supported"); |
59 | } |
60 | // we need a query |
61 | if (!cte.ctequery || cte.ctequery->type != duckdb_libpgquery::T_PGSelectStmt) { |
62 | throw NotImplementedException("A CTE needs a SELECT"); |
63 | } |
64 | |
65 | // CTE transformation can either result in inlining for non recursive CTEs, or in recursive CTE bindings |
66 | // otherwise. |
67 | if (cte.cterecursive || de_with_clause.recursive) { |
68 | info->query = TransformRecursiveCTE(cte, info&: *info); |
69 | } else { |
70 | Transformer cte_transformer(*this); |
71 | info->query = |
72 | cte_transformer.TransformSelect(select&: *PGPointerCast<duckdb_libpgquery::PGSelectStmt>(ptr: cte.ctequery)); |
73 | } |
74 | D_ASSERT(info->query); |
75 | auto cte_name = string(cte.ctename); |
76 | |
77 | auto it = cte_map.map.find(x: cte_name); |
78 | if (it != cte_map.map.end()) { |
79 | // can't have two CTEs with same name |
80 | throw ParserException("Duplicate CTE name \"%s\"", cte_name); |
81 | } |
82 | cte_map.map[cte_name] = std::move(info); |
83 | } |
84 | } |
85 | |
86 | unique_ptr<SelectStatement> Transformer::TransformRecursiveCTE(duckdb_libpgquery::PGCommonTableExpr &cte, |
87 | CommonTableExpressionInfo &info) { |
88 | auto &stmt = *PGPointerCast<duckdb_libpgquery::PGSelectStmt>(ptr: cte.ctequery); |
89 | |
90 | unique_ptr<SelectStatement> select; |
91 | switch (stmt.op) { |
92 | case duckdb_libpgquery::PG_SETOP_UNION: |
93 | case duckdb_libpgquery::PG_SETOP_EXCEPT: |
94 | case duckdb_libpgquery::PG_SETOP_INTERSECT: { |
95 | select = make_uniq<SelectStatement>(); |
96 | select->node = make_uniq_base<QueryNode, RecursiveCTENode>(); |
97 | auto &result = select->node->Cast<RecursiveCTENode>(); |
98 | result.ctename = string(cte.ctename); |
99 | result.union_all = stmt.all; |
100 | result.left = TransformSelectNode(select&: *PGPointerCast<duckdb_libpgquery::PGSelectStmt>(ptr: stmt.larg)); |
101 | result.right = TransformSelectNode(select&: *PGPointerCast<duckdb_libpgquery::PGSelectStmt>(ptr: stmt.rarg)); |
102 | result.aliases = info.aliases; |
103 | if (stmt.op != duckdb_libpgquery::PG_SETOP_UNION) { |
104 | throw ParserException("Unsupported setop type for recursive CTE: only UNION or UNION ALL are supported"); |
105 | } |
106 | break; |
107 | } |
108 | default: |
109 | // This CTE is not recursive. Fallback to regular query transformation. |
110 | return TransformSelect(select&: *PGPointerCast<duckdb_libpgquery::PGSelectStmt>(ptr: cte.ctequery)); |
111 | } |
112 | |
113 | if (stmt.limitCount || stmt.limitOffset) { |
114 | throw ParserException("LIMIT or OFFSET in a recursive query is not allowed"); |
115 | } |
116 | if (stmt.sortClause) { |
117 | throw ParserException("ORDER BY in a recursive query is not allowed"); |
118 | } |
119 | return select; |
120 | } |
121 | |
122 | } // namespace duckdb |
123 |