Repository navigation
fix: support PostgreSQL composite row expansion (function()).* - #2463
Merged
manticore-projects merged 1 commit intoAug 13, 2026
Conversation
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Re-enable PostgreSQL composite row expansion
(function_call).*, e.g.SELECT (json_populate_record(NULL::users, data)).* FROM staging_users. JSQLParser currently rejects the trailing.*.This builds on #2207, which added the feature and then had to disable it.
Why
FunctionAllColumns(the AST node, deparser, validator and all four visitors) was fully implemented in #2207 but the grammar call site was commented out afterwards. The reason was a severe performance regression: the originalLOOKAHEAD(FunctionAllColumns())at thePrimaryExpressionentry was a speculative syntactic lookahead that triggered catastrophic backtracking on every primary expression (393 ms/opvs~86 ms/op). Issue #2412 is the user-visible symptom of that disabled feature.The maintainer explicitly invited a re-enablement "once performance can be proved" and suggested treating
(((( FUNCTION )))).*as a single-FunctionParenthesedExpressionListfollowed by.*.How
The feature is wired into the existing
ParenthesedExpressionListbranch ofPrimaryExpression, but via a bounded semantic follower check instead of a speculative production:isFunctionAllColumnsAhead(Expression)first peeks the next two tokens; only when they are.*does it unwrap nestedParenthesedExpressionListwrappers and confirm the inner expression is aFunction. The common case (a parenthesised expression not followed by.*) therefore bails out in two comparisons, with no speculative production and no backtracking.FunctionAllColumns.TablesNamesFindernow descends into the wrapped function, so column/table references inside the expansion are not lost.Root cause
Not a missing feature but a feature disabled for the wrong reason: the perf toll came from the speculative syntactic lookahead, not from the feature itself. Removing the speculation (post-parse follower check) keeps the feature without the toll.
Scope / limitation
Only
(function_call).*is supported (the shape the existingFunctionAllColumnsAST and #2207 cover). Arbitrary(non-function expression).*(e.g.(a + b).*,(tbl.col).*) remains unsupported and still fails cleanly, as before. Redundant surrounding parentheses are unwrapped to the inner function (so(((( f() )))).*round-trips as(f()).*, semantically equivalent).Testing
CompositeRowExpansionTestcovers the issue case, simple/no-arg functions, theINSERT ... SELECTuse case, multiple surrounding parentheses, and negative cases (no trailing.*, aRowGetExpression, a non-function expression). The positive tests fail onmasterand pass with this change.Local:
spotlessApply,checkstyleMain/Test,spotbugsMain/Test, and the full test suite (4731 tests) all green, no regression.Performance
gradle jmh,JSQLParserBenchmark.parseSQLStatementsonperformance.sql,version=latest, 10 forks × 10 iterations (100 samples) on a dedicated 32-core host:3.632 ± 0.0203.639 ± 0.023The
+0.19%delta lies within the confidence intervals (the CIs overlap almost entirely) and is far from the393 ms/optoll that motivated disabling the feature.Verification of the original issue
fails on
master(Encountered: ".") and parses + round-trips with this change, yielding aFunctionAllColumnsselect item.Fixes #2412