Skip to content

Commit 4b112e1

Browse files
committed
Keep scalar and table macro references apart in the export order
An expression call can only name a scalar macro and a FROM-clause call only a table macro, but the reference collector matched both entry kinds by name, so a scalar and a table macro sharing one name formed a false cycle whose fallback could emit a caller before its callee. References now carry their call category: the callee of a TableFunctionRef is a table reference, calls inside its arguments stay scalar, and candidates are filtered by entry type.
1 parent 94acffe commit 4b112e1

2 files changed

Lines changed: 112 additions & 23 deletions

File tree

‎src/execution/operator/persistent/physical_export.cpp‎

Lines changed: 68 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,13 @@
1515
#include "duckdb/function/scalar_macro_function.hpp"
1616
#include "duckdb/function/table_macro_function.hpp"
1717
#include "duckdb/common/set.hpp"
18+
#include "duckdb/common/unordered_set.hpp"
1819
#include "duckdb/parser/expression/function_expression.hpp"
1920
#include "duckdb/parser/expression/subquery_expression.hpp"
2021
#include "duckdb/parser/expression/window_expression.hpp"
2122
#include "duckdb/parser/parsed_expression_iterator.hpp"
2223
#include "duckdb/parser/statement/select_statement.hpp"
24+
#include "duckdb/parser/tableref/table_function_ref.hpp"
2325

2426
#include <algorithm>
2527
#include <sstream>
@@ -180,43 +182,81 @@ struct MacroReference {
180182
//! empty when the call is unqualified
181183
string schema;
182184
string name;
185+
//! a FROM-clause table function call names a table macro, every other call a scalar one
186+
bool is_table_function;
187+
//! the expression the reference came from, to reclassify table function callees
188+
const ParsedExpression *source;
183189
};
184190

185-
static void CollectReferencedFunctionNames(ParsedExpression &expr, vector<MacroReference> &refs);
191+
struct MacroReferenceCollector {
192+
public:
193+
vector<MacroReference> refs;
186194

187-
static void CollectReferencedFunctionNames(QueryNode &node, vector<MacroReference> &refs) {
188-
ParsedExpressionIterator::EnumerateQueryNodeChildren(
189-
node, [&refs](unique_ptr<ParsedExpression> &child) { CollectReferencedFunctionNames(*child, refs); });
190-
}
195+
public:
196+
void Collect(ParsedExpression &expr) {
197+
if (expr.GetExpressionClass() == ExpressionClass::FUNCTION) {
198+
auto &function = expr.Cast<FunctionExpression>();
199+
refs.push_back({function.GetQualifiedName().Schema().GetIdentifierName(),
200+
function.FunctionName().GetIdentifierName(), false, &expr});
201+
} else if (expr.GetExpressionClass() == ExpressionClass::WINDOW) {
202+
// a macro invoked with OVER is a window expression carrying the callee name itself
203+
auto &window = expr.Cast<WindowExpression>();
204+
refs.push_back({window.GetQualifiedName().Schema().GetIdentifierName(),
205+
window.FunctionName().GetIdentifierName(), false, &expr});
206+
} else if (expr.GetExpressionClass() == ExpressionClass::SUBQUERY) {
207+
Collect(*expr.Cast<SubqueryExpression>().Subquery()->node);
208+
}
209+
ParsedExpressionIterator::EnumerateChildren(expr, [this](ParsedExpression &child) { Collect(child); });
210+
}
191211

192-
static void CollectReferencedFunctionNames(ParsedExpression &expr, vector<MacroReference> &refs) {
193-
if (expr.GetExpressionClass() == ExpressionClass::FUNCTION) {
194-
auto &function = expr.Cast<FunctionExpression>();
195-
refs.push_back(
196-
{function.GetQualifiedName().Schema().GetIdentifierName(), function.FunctionName().GetIdentifierName()});
197-
} else if (expr.GetExpressionClass() == ExpressionClass::WINDOW) {
198-
// a macro invoked with OVER is a window expression carrying the callee name itself
199-
auto &window = expr.Cast<WindowExpression>();
200-
refs.push_back(
201-
{window.GetQualifiedName().Schema().GetIdentifierName(), window.FunctionName().GetIdentifierName()});
202-
} else if (expr.GetExpressionClass() == ExpressionClass::SUBQUERY) {
203-
CollectReferencedFunctionNames(*expr.Cast<SubqueryExpression>().Subquery()->node, refs);
212+
void Collect(QueryNode &node) {
213+
ParsedExpressionIterator::EnumerateQueryNodeChildren(
214+
node, [this](unique_ptr<ParsedExpression> &child) { Collect(*child); },
215+
[this](TableRef &ref) {
216+
if (ref.type == TableReferenceType::TABLE_FUNCTION) {
217+
auto &table_function = ref.Cast<TableFunctionRef>();
218+
if (table_function.function) {
219+
table_function_roots.insert(table_function.function.get());
220+
}
221+
}
222+
});
204223
}
205-
ParsedExpressionIterator::EnumerateChildren(
206-
expr, [&refs](ParsedExpression &child) { CollectReferencedFunctionNames(child, refs); });
207-
}
224+
225+
//! reclassify the callees of table function refs: FROM f(...) names a table macro, while the
226+
//! calls inside its arguments stay scalar. Must run while the walked tree is alive, the source
227+
//! pointers dangle afterwards and are cleared here.
228+
void Finalize() {
229+
for (auto &ref : refs) {
230+
if (table_function_roots.count(ref.source) != 0) {
231+
ref.is_table_function = true;
232+
}
233+
ref.source = nullptr;
234+
}
235+
}
236+
237+
private:
238+
unordered_set<const ParsedExpression *> table_function_roots;
239+
};
208240

209241
static vector<MacroReference> CollectMacroReferences(CatalogEntry &entry) {
210242
vector<MacroReference> refs;
211243
auto &macro_entry = entry.Cast<MacroCatalogEntry>();
212244
for (auto &member : macro_entry.macros) {
213-
// walk a copy: enumeration hands out mutable references
245+
// one collector per member: Finalize compares pointers into the member's copy, so it must
246+
// run before that copy is destroyed
247+
MacroReferenceCollector collector;
214248
if (member->type == MacroType::SCALAR_MACRO) {
249+
// walk a copy: enumeration hands out mutable references
215250
auto expression = member->Cast<ScalarMacroFunction>().expression->Copy();
216-
CollectReferencedFunctionNames(*expression, refs);
251+
collector.Collect(*expression);
252+
collector.Finalize();
217253
} else {
218254
auto query_node = member->Cast<TableMacroFunction>().query_node->Copy();
219-
CollectReferencedFunctionNames(*query_node, refs);
255+
collector.Collect(*query_node);
256+
collector.Finalize();
257+
}
258+
for (auto &ref : collector.refs) {
259+
refs.push_back(std::move(ref));
220260
}
221261
}
222262
return refs;
@@ -247,6 +287,11 @@ static void OrderMacrosByReference(catalog_entry_vector_t &macros) {
247287
if (callee == i) {
248288
continue;
249289
}
290+
// a FROM-clause call names a table macro and any other call a scalar one
291+
const bool callee_is_table = macros[callee].get().type == CatalogType::TABLE_MACRO_ENTRY;
292+
if (callee_is_table != ref.is_table_function) {
293+
continue;
294+
}
250295
// a qualified call narrows the candidates to that schema
251296
if (!ref.schema.empty() &&
252297
!StringUtil::CIEquals(ref.schema, macros[callee].get().ParentSchema().name.GetIdentifierName())) {

‎test/sql/export/export_macro_order.test‎

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,50 @@ DROP MACRO TABLE tm_outer;
216216
statement ok
217217
DROP MACRO TABLE tm_inner;
218218

219+
# a scalar and a table macro sharing one name stay apart: an expression call only references the
220+
# scalar entry, a FROM-clause call only the table entry
221+
222+
statement ok
223+
CREATE MACRO f(x INTEGER) AS x;
224+
225+
statement ok
226+
CREATE MACRO g(x INTEGER) AS main.f(x);
227+
228+
statement ok
229+
CREATE MACRO f(x INTEGER) AS TABLE SELECT main.g(x) AS v;
230+
231+
statement ok
232+
CREATE OR REPLACE MACRO g(x INTEGER) AS main.f(x) + 1;
233+
234+
statement ok
235+
EXPORT DATABASE '{TEST_DIR}/export_macro_order_category' (FORMAT CSV);
236+
237+
statement ok
238+
DROP MACRO TABLE f;
239+
240+
statement ok
241+
DROP MACRO g;
242+
243+
statement ok
244+
DROP MACRO f;
245+
246+
statement ok
247+
IMPORT DATABASE '{TEST_DIR}/export_macro_order_category';
248+
249+
query T
250+
SELECT v FROM f(1);
251+
----
252+
2
253+
254+
statement ok
255+
DROP MACRO TABLE f;
256+
257+
statement ok
258+
DROP MACRO g;
259+
260+
statement ok
261+
DROP MACRO f;
262+
219263
# a table macro used as MATCH_RECOGNIZE input is referenced through the recursed input ref
220264

221265
statement ok

0 commit comments

Comments
 (0)