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
7namespace duckdb {
8
9unique_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
16void 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
32void 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
86unique_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