1#include "duckdb/common/enums/set_operation_type.hpp"
2#include "duckdb/common/exception.hpp"
3#include "duckdb/parser/statement/select_statement.hpp"
4#include "duckdb/parser/transformer.hpp"
5#include "duckdb/parser/expression/star_expression.hpp"
6#include "duckdb/parser/query_node/recursive_cte_node.hpp"
7
8using namespace duckdb;
9using namespace std;
10
11void Transformer::TransformCTE(PGWithClause *de_with_clause, SelectStatement &select) {
12 // TODO: might need to update in case of future lawsuit
13 assert(de_with_clause);
14
15 assert(de_with_clause->ctes);
16 for (auto cte_ele = de_with_clause->ctes->head; cte_ele != NULL; cte_ele = cte_ele->next) {
17 auto cte = reinterpret_cast<PGCommonTableExpr *>(cte_ele->data.ptr_value);
18 // lets throw some errors on unsupported features early
19
20 if (cte->aliascolnames) {
21 throw NotImplementedException("Column name aliases not supported in CTEs");
22 }
23 if (cte->ctecolnames) {
24 throw NotImplementedException("Column name setting not supported in CTEs");
25 }
26 if (cte->ctecoltypes) {
27 throw NotImplementedException("Column type setting not supported in CTEs");
28 }
29 if (cte->ctecoltypmods) {
30 throw NotImplementedException("Column type modification not supported in CTEs");
31 }
32 if (cte->ctecolcollations) {
33 throw NotImplementedException("CTE collations not supported");
34 }
35 // we need a query
36 if (!cte->ctequery || cte->ctequery->type != T_PGSelectStmt) {
37 throw Exception("A CTE needs a SELECT");
38 }
39
40 unique_ptr<QueryNode> cte_select;
41
42 // CTE transformation can either result in inlining for non recursive CTEs, or in recursive CTE bindings
43 // otherwise.
44 if (cte->cterecursive || de_with_clause->recursive) {
45 cte_select = TransformRecursiveCTE(cte);
46 } else {
47 cte_select = TransformSelectNode((PGSelectStmt *)cte->ctequery);
48 }
49
50 if (!cte_select) {
51 throw Exception("A CTE needs a SELECT");
52 }
53 auto cte_name = string(cte->ctename);
54
55 auto it = select.cte_map.find(cte_name);
56 if (it != select.cte_map.end()) {
57 // can't have two CTEs with same name
58 throw Exception("A CTE needs an unique name");
59 }
60 select.cte_map[cte_name] = move(cte_select);
61 }
62}
63
64unique_ptr<QueryNode> Transformer::TransformRecursiveCTE(PGCommonTableExpr *cte) {
65 auto stmt = (PGSelectStmt *)cte->ctequery;
66
67 unique_ptr<QueryNode> node;
68 switch (stmt->op) {
69 case PG_SETOP_UNION:
70 case PG_SETOP_EXCEPT:
71 case PG_SETOP_INTERSECT: {
72 node = make_unique<RecursiveCTENode>();
73 auto result = (RecursiveCTENode *)node.get();
74 result->ctename = string(cte->ctename);
75 result->union_all = stmt->all;
76 result->left = TransformSelectNode(stmt->larg);
77 result->right = TransformSelectNode(stmt->rarg);
78
79 if (!result->left || !result->right) {
80 throw Exception("Failed to transform recursive CTE children.");
81 }
82
83 bool select_distinct = true;
84 switch (stmt->op) {
85 case PG_SETOP_UNION:
86 // We don't need a DISTINCT operation on top of a recursive UNION CTE.
87 select_distinct = false;
88 break;
89 default:
90 throw Exception("Unexpected setop type for recursive CTE");
91 }
92 // if we compute the distinct result here, we do not have to do this in
93 // the children. This saves a bunch of unnecessary DISTINCTs.
94 if (select_distinct) {
95 result->modifiers.push_back(make_unique<DistinctModifier>());
96 }
97 break;
98 }
99 default:
100 // This CTE is not recursive. Fallback to regular query transformation.
101 return TransformSelectNode((PGSelectStmt *)cte->ctequery);
102 }
103
104 if (stmt->limitCount) {
105 throw Exception("LIMIT in a recursive query is not implemented");
106 }
107
108 if (stmt->limitOffset) {
109 throw Exception("OFFSET in a recursive query is not implemented");
110 }
111 return node;
112}
113