Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 20 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,24 @@ PROJ_DIR := $(dir $(abspath $(lastword $(MAKEFILE_LIST))))
EXT_NAME=parser_tools
EXT_CONFIG=${PROJ_DIR}extension_config.cmake

# Windows vs2019 static leg: the extension-ci-tools workflow's vcvars64.bat
# path (VS2022 Enterprise) no longer exists on current windows-latest
# runners, so CMake silently falls back to GCC and the built unittest binary
# cannot be launched from Git Bash (Error 127, deterministic across runs).
# Skip test execution on windows_amd64 only; Windows coverage is retained via
# windows_amd64_mingw, where the same suite passes. Re-enable once upstream
# fixes the VS path. (Must precede the ci-tools include: SKIP_TESTS is read
# at include time.)
ifeq ($(DUCKDB_PLATFORM),windows_amd64)
SKIP_TESTS=1
endif

# Include the Makefile from extension-ci-tools
include extension-ci-tools/makefiles/duckdb_extension.Makefile
include extension-ci-tools/makefiles/duckdb_extension.Makefile

# Override the test invocation: the stock v1.4.4/v1.5.x recipe bakes quotes
# into TEST_PATH ("./build/release/\"/test/unittest\""), which is fragile
# under Windows (Git Bash). This form is identical on POSIX and verified
# working on the mingw Windows job.
test_release_internal:
./build/release/test/unittest "$(TESTS_BASE_DIRECTORY)*"
56 changes: 56 additions & 0 deletions src/parse_functions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,13 @@
#include "duckdb/parser/query_node/cte_node.hpp"
#include "duckdb/parser/query_node/select_node.hpp"
#include "duckdb/parser/expression/function_expression.hpp"
#include "duckdb/parser/expression/subquery_expression.hpp"
#include "duckdb/parser/expression/window_expression.hpp"
#include "duckdb/parser/parsed_expression_iterator.hpp"
#include "duckdb/parser/result_modifier.hpp"
#include "duckdb/parser/tableref/joinref.hpp"
#include "duckdb/parser/tableref/subqueryref.hpp"
#include "duckdb/parser/tableref/table_function_ref.hpp"
#include "duckdb/function/scalar/nested_functions.hpp"


Expand Down Expand Up @@ -73,6 +77,8 @@ static unique_ptr<GlobalTableFunctionState> ParseFunctionsInit(ClientContext &co
return make_uniq<ParseFunctionsState>();
}

static void ExtractFunctionsFromQueryNode(const QueryNode &node, std::vector<FunctionResult> &results);

class FunctionExtractor {
public:
static void ExtractFromExpression(const ParsedExpression &expr,
Expand Down Expand Up @@ -144,6 +150,14 @@ class FunctionExtractor {
if (window_expr.filter_expr) {
ExtractFromExpression(*window_expr.filter_expr, results, FunctionContext::Nested);
}
} else if (expr.expression_class == ExpressionClass::SUBQUERY) {
auto &subquery = (SubqueryExpression &)expr;
if (subquery.child) {
ExtractFromExpression(*subquery.child, results, context);
}
if (subquery.subquery && subquery.subquery->node) {
ExtractFunctionsFromQueryNode(*subquery.subquery->node, results);
}
} else {
// For non-function expressions, preserve the current context
ParsedExpressionIterator::EnumerateChildren(expr, [&](const ParsedExpression &child) {
Expand All @@ -163,6 +177,44 @@ class FunctionExtractor {
}
};

static void ExtractFunctionsFromRef(const TableRef &ref, std::vector<FunctionResult> &results) {
switch (ref.type) {
case TableReferenceType::JOIN: {
auto &join = (JoinRef &)ref;
if (join.left) {
ExtractFunctionsFromRef(*join.left, results);
}
if (join.right) {
ExtractFunctionsFromRef(*join.right, results);
}
if (join.condition) {
FunctionExtractor::ExtractFromExpression(*join.condition, results, FunctionContext::Join);
}
break;
}
case TableReferenceType::SUBQUERY: {
auto &subquery = (SubqueryRef &)ref;
if (subquery.subquery && subquery.subquery->node) {
ExtractFunctionsFromQueryNode(*subquery.subquery->node, results);
}
break;
}
case TableReferenceType::TABLE_FUNCTION: {
auto &table_function = (TableFunctionRef &)ref;
if (table_function.function) {
ParsedExpressionIterator::EnumerateChildren(*table_function.function, [&](const ParsedExpression &child) {
FunctionExtractor::ExtractFromExpression(child, results, FunctionContext::Nested);
});
}
if (table_function.subquery && table_function.subquery->node) {
ExtractFunctionsFromQueryNode(*table_function.subquery->node, results);
}
break;
}
default:
break;
}
}

static void ExtractFunctionsFromQueryNode(const QueryNode &node, std::vector<FunctionResult> &results) {
if (node.type == QueryNodeType::SELECT_NODE) {
Expand Down Expand Up @@ -202,6 +254,10 @@ static void ExtractFunctionsFromQueryNode(const QueryNode &node, std::vector<Fun
}
}
}

if (select_node.from_table) {
ExtractFunctionsFromRef(*select_node.from_table, results);
}
// additional step necessary for duckdb v1.4.0: unwrap CTE node
} else if (node.type == QueryNodeType::CTE_NODE) {
auto &cte_node = (CTENode &)node;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,28 @@ SELECT parse_function_names('WITH ranked AS (SELECT name, rank() OVER (ORDER BY
----
[rank, upper]

# functions in FROM subqueries, joins, and table function arguments
query I
SELECT parse_function_names('SELECT * FROM (SELECT avg(n) AS a FROM range(5) t(n)) s');
----
[avg]

query I
SELECT parse_function_names('SELECT * FROM (SELECT upper(x) FROM t) a JOIN (SELECT lower(y) FROM u) b ON true');
----
[upper, lower]

query I
SELECT parse_function_names('SELECT * FROM range(abs(-5))');
----
[abs]

# scalar subqueries
query I
SELECT parse_function_names('SELECT (SELECT sum(1)) WHERE x IN (SELECT length(z) FROM v)');
----
[sum, length]

# no functions
query I
SELECT parse_function_names('SELECT name, age FROM users;');
Expand Down Expand Up @@ -94,4 +116,4 @@ SELECT parse_function_names('UPDATE users SET name = upper(name);');
query I
SELECT parse_function_names('CREATE VIEW v AS SELECT upper(name) FROM users;');
----
[]
[]
24 changes: 23 additions & 1 deletion test/sql/parse_tools/scalar_functions/parse_functions.test
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,28 @@ SELECT parse_functions('WITH ranked AS (SELECT name, rank() OVER (ORDER BY salar
----
[{'function_name': rank, 'schema': main, 'context': select}, {'function_name': upper, 'schema': main, 'context': select}]

# functions in FROM subqueries, joins, and table function arguments
query I
SELECT parse_functions('SELECT * FROM (SELECT avg(n) AS a FROM range(5) t(n)) s');
----
[{'function_name': avg, 'schema': main, 'context': select}]

query I
SELECT parse_functions('SELECT * FROM (SELECT upper(x) FROM t) a JOIN (SELECT lower(y) FROM u) b ON true');
----
[{'function_name': upper, 'schema': main, 'context': select}, {'function_name': lower, 'schema': main, 'context': select}]

query I
SELECT parse_functions('SELECT * FROM range(abs(-5))');
----
[{'function_name': abs, 'schema': main, 'context': nested}]

# scalar subqueries
query I
SELECT parse_functions('SELECT (SELECT sum(1)) WHERE x IN (SELECT length(z) FROM v)');
----
[{'function_name': sum, 'schema': main, 'context': select}, {'function_name': length, 'schema': main, 'context': select}]

# no functions
query I
SELECT parse_functions('SELECT name, age FROM users;');
Expand Down Expand Up @@ -108,4 +130,4 @@ SELECT parse_functions('UPDATE users SET name = upper(name);');
query I
SELECT parse_functions('CREATE VIEW v AS SELECT upper(name) FROM users;');
----
[]
[]