Repository navigation
[2.1.0] Accept only a type name in a type annotation; reject an annotation before an initializer - #20
Open
OffgridwithJD wants to merge 1 commit into
Conversation
…e an initializer A local's type is spliced into DECLARE, where plpgsql reads up to the ';'. An annotation such as "int := f()" or "int DEFAULT f()" therefore became an initializer that ran on entry while the source showed only a type (in plxjs, only a comment), and "int; BEGIN ... END; DECLARE y int" became statements. Before a type reaches DECLARE it must now be name%TYPE / name%ROWTYPE or a type name the SQL type-name grammar accepts (typeStringToTypeName, which is what plpgsql's own parse_datatype uses), with no comment in it. The check is at the one place DECLARE is assembled, so it covers annotations, plxts types, plxgo and plxtsql declarations and the plxcobol TYPE clause. Also: in plxjs and plxphp, "let n /*:: bigint */ = 3;" was treated as a bare annotation and the "= 3" was dropped, leaving n NULL. It is now rejected with a hint to put the annotation after the value. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
OffgridwithJD
pushed a commit
to OffgridwithJD/plx
that referenced
this pull request
Nov 9, 2026
"let x /*:: int */ = 0;" drops the "= 0" (commandprompt#20 makes that form an error). The test's result does not depend on it, but it should use the documented form so it keeps passing once commandprompt#20 is merged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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.
Summary
Found while reviewing the new plxlua front end. The problem is in the shared code, so it affects every dialect that hoists
DECLAREs.Type annotations could carry code
A local's type is spliced into the plpgsql
DECLAREsection, and plpgsql reads a declaration up to its;. So anything after the type in an annotation became part of the declaration:x #:: int := (SELECT ...)x int := (SELECT ...);let x /*:: int DEFAULT 42 */;x int DEFAULT 42;let x;returns 42x #:: int; BEGIN ... END; DECLARE y intThis is the same class as the string-literal breakout in #12: the source shows only a type (in plxjs, only a comment), but other code runs. I confirmed that
set_config('search_path', ...)in an annotation initializer runs and changes the caller's setting.Fix. Before a type reaches
DECLAREit must be one of these, with no comment in it:name%TYPEorname%ROWTYPE, optionally qualified, optionally followed by[]typeStringToTypeName, the same parser plpgsql's ownparse_datatypeuses.The check runs where
DECLAREis assembled, so it covers:TYPEclauseWhy this is sound. plpgsql's
read_datatypestops at:=,=,DEFAULT,NOTandCOLLATE, and at,or)outside parentheses. None of these can appear at the top level of a SQL type name. When one appears inside a type modifier, plpgsql hands an unbalanced prefix toparse_datatype, which fails. So any text that passes the check is either consumed whole as the type or rejected by plpgsql. Comments (--,/*) are rejected up front, because the SQL parser skips them but a--would hide the declaration's;.Only syntax errors from the parser are turned into the plx error. Any other error is re-thrown unchanged.
Annotation before an initializer dropped the initializer
In plxjs and plxphp,
let n /*:: bigint */ = 3;was treated as a bare annotation, and the= 3was silently dropped, leavingnNULL. It is now rejected with a hint to writelet n = 3 /*:: bigint */;, the documented form.Compatibility
Code-only, so no
ALTER EXTENSIONis needed. A function whose annotation held anything but a type is now rejected atCREATE FUNCTION. Every documented form still works, and a new test covers them:double precisiontimestamp(3) with time zonenumeric(10, 2)character varying(20)[]%ROWTYPEand qualified%TYPEint[][]interval day to secondCost
One raw parse per distinct type, about 2 µs each. Consecutive locals with the same type are checked once. With #12's locals hash, a body of 40,000 locals that each have a different type transpiles in 0.12 s.
Testing
make installcheck: all 13 suites pass on PG 18.4. CI covers 13 to 18. The version split (typeStringToTypeNamegained anescontextargument in 16) is handled with#if.make differentialcheck: 0 unexplained.plx_errors: six rejected annotations across plxruby, plxjs and plxpython3; the two misplaced-annotation cases; and the accepted forms above.🤖 Generated with Claude Code