Skip to content

Support the rest of DuckDB's CREATE MACRO syntax - #2783

Merged
manticore-projects merged 2 commits into
JSQLParser:masterfrom
guizmaii:duckdb-create-macro
Oct 13, 2026
Merged

manticore-projects merged 2 commits into
JSQLParser:masterfrom
guizmaii:duckdb-create-macro

Conversation

@guizmaii

@guizmaii guizmaii commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

Purpose

Support the rest of DuckDB's CREATE MACRO syntax, which is currently rejected:

  • CREATE MACRO IF NOT EXISTS name (...) AS ...
  • typed parameters: CREATE MACRO add (a INTEGER, b DOUBLE) AS a + b
  • overloads: CREATE MACRO is_maximal (a TINYINT) AS a = 127, (a INT) AS a = 2147483647
  • FUNCTION as an alias of MACRO: CREATE FUNCTION add (a, b) AS a + b, which today parses as an opaque CreateFunction

Change

  • CreateMacro keeps a list of Overloads, each with its parameters and body (expression or TABLE query). getParameters(), getExpression(), getSelect(), isTable() and their setters address the first overload, so existing callers keep working. It also records isIfNotExists() and isFunctionKeyword(); Parameter gets an optional getType().
  • CREATE FUNCTION is routed to CreateMacro only when AS directly follows the parameter list, the body is not a string ('...', $$ ... $$, $tag$ ... $tag$), and no LANGUAGE or OPTIONS clause follows outside parentheses. Other dialects keep their current parse: ... RETURNS ..., PostgreSQL's (IN a integer, OUT b integer) AS $function$ ... $function$ LANGUAGE ..., BigQuery's AS (expr) OPTIONS (...).
  • One behaviour change: a BigQuery SQL UDF without RETURNS/OPTIONS, CREATE [TEMP] FUNCTION f(x INT64) AS (x + 1), now parses as a CreateMacro (same shape as a DuckDB macro); CREATE OR REPLACE FUNCTION ds.f(x INT64) AS (x + 1) was an opaque CreateFunction before.
  • As in DuckDB, a , after a TABLE body ending with a list (FROM a, ..., GROUP BY a, ...) continues that list rather than starting an overload (documented in the Javadoc).
  • TablesNamesFinder visits the queries of every overload.

Validation

  • Java 17 Gradle check: 10,049 tests, zero failures/errors; Spotless, Checkstyle, PMD and SpotBugs pass.
  • DuckDBTest: IF NOT EXISTS, typed and default parameters, scalar overloads, table overloads (AST and TablesNamesFinder), the FUNCTION alias in scalar/table/OR REPLACE TEMPORARY forms, and PostgreSQL (RETURNS, $$, $function$) and BigQuery (OPTIONS) CREATE FUNCTIONs that must stay CreateFunction.
  • Together with Allow ERRORS as an unquoted identifier #2781 (ERRORS as an identifier) and Support DuckDB's lambda x, y : expr syntax #2782 (lambda x, y : expr), all 505 of our production DuckDB queries parse without any rewriting, against 319 on 5.4.

Copilot AI balanced review requested due to automatic review settings October 13, 2026 00:08

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@manticore-projects

Copy link
Copy Markdown
Contributor

Please resolve conflicts.

Jules Ivanic added 2 commits October 13, 2026 14:53
DuckDB's CREATE MACRO also takes `IF NOT EXISTS`, typed parameters (`a INTEGER`),
several overloads (`name (a TINYINT) AS ..., (a INT) AS ...`) and `FUNCTION` as an
alias of `MACRO`. All of these were rejected, and `CREATE FUNCTION f(a) AS a + 1`
was parsed as an opaque `CreateFunction`.

- `CreateMacro` keeps a list of `Overload`s, each with its parameters and body;
  `getParameters()`, `getExpression()`, `getSelect()` and their setters address the
  first one, as before. It also records `IF NOT EXISTS` and the `FUNCTION` keyword,
  and `Parameter` an optional type.
- `CREATE FUNCTION` is routed to `CreateMacro` only when `AS` directly follows the
  parameters and the body is not a string, so PostgreSQL functions
  (`... RETURNS ...`, `... AS $$ ... $$ LANGUAGE ...`) keep their current parse.
- `TablesNamesFinder` visits the queries of every overload.
`AS` directly after the parameters with a non-string body was not enough to tell a
DuckDB macro apart: PostgreSQL's `AS $function$ ... $function$ LANGUAGE ...` (a tagged
dollar quote lexes as an identifier), `AS U&'...' LANGUAGE ...` and BigQuery's
`AS (expr) OPTIONS (...)` became macros and failed. `isCreateFunctionMacroAhead()`
also rejects a `$tag$` body and a LANGUAGE or OPTIONS clause outside parentheses
before the end of the statement.

- `setOverloads()` copies its argument, so a null or immutable list no longer breaks
  the first-overload setters.
- The Javadoc notes that, as in DuckDB, a `,` after a TABLE body ending with a list
  continues that list.
- `testCreateOverloadedMacro` no longer mixes scalar and table overloads, which
  DuckDB rejects; `testCreateOverloadedTableMacro` covers table overloads.
@guizmaii
guizmaii force-pushed the duckdb-create-macro branch from 08ab771 to c63f275 Compare October 13, 2026 03:56
@guizmaii

Copy link
Copy Markdown
Contributor Author

Done: rebased on master after #2781 and #2782 were merged (the only conflict was in DuckDBTest, where both added tests). ./gradlew check passes on the rebased branch: 10,103 tests, zero failures/errors. Thanks for merging the other two!

@manticore-projects
manticore-projects merged commit 4ecc8bc into JSQLParser:master Oct 13, 2026
10 checks passed
@manticore-projects

Copy link
Copy Markdown
Contributor

Big DuckDB fan here, thank you much for this!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants