Skip to content

feat(workspace): analyse goose ENVSUB expansions as parameters - #949

Open
psteinroe wants to merge 1 commit into
mainfrom
feat/goose-envsub
Open

psteinroe wants to merge 1 commit into
mainfrom
feat/goose-envsub

Conversation

@psteinroe

Copy link
Copy Markdown
Collaborator

goose's -- +goose ENVSUB ON replaces ${NAME} with environment variables before it runs the SQL (parser.go, using interpolate). We parsed the expansions as written, so every statement with one was a syntax error and got no linting:

-- +goose Up
-- +goose ENVSUB ON
create table ${SCHEMA}.events (id bigint, at timestamp);   -- before: syntax error at or near "$"

Behaviour

Real substitution doesn't fit a linter: the environment of the editor or CI isn't the one goose runs in, and a missing variable becomes an empty string. Instead, between ENVSUB ON and ENVSUB OFF, each braced expansion (${NAME}, ${NAME:-default}, ${NAME:?}, … with nested braces) is read as the named parameter $NAME, padded with spaces to the same length:

create table ${SCHEMA}.events (...)   ->   create table $SCHEMA  .events (...)

Named parameters already get the psql-variable treatment (:name, @name, $name): they are identifiers where an object name is expected and value parameters elsewhere, and statements with identifier parameters aren't typechecked. The unbraced $NAME that interpolate also expands is a named parameter already. The escapes \$, $$ and \\ are left as written, as is $(. The text keeps its length, so diagnostics, fixes and LSP positions need no offset mapping.

  • Document keeps the content as written (get_document_content), and splits and parses the substituted text.
  • Fixes with an edit inside an expansion are dropped in code actions and in fix_file. Without that, constraintMissingNotValid put NOT VALID inside ${PARENT}, once per pass, because the end of the constraint was the padded placeholder. There's a test for that. Fixes elsewhere in the statement still apply, and the expansion stays as written.
  • The formatter leaves statements with expansions as they are, so it can't write the placeholder back.
  • It applies to any file with the annotation, also without sectionMarkers: "goose". The annotation is specific enough.

To avoid new syntax errors, I had to extend the identifier heuristic of the named-parameter conversion in pgls_lexer: parameters after INTO, JOIN, ON, UPDATE, TRUNCATE, REFERENCES, EXISTS, VIEW, SEQUENCE, EXTENSION, FUNCTION and TRIGGER are now identifiers. references ${PARENT} was converted to a value parameter, which doesn't parse. This also helps psql variables, e.g. insert into :items.

Tests

Unit tests for the substitution (ON/OFF regions, nested defaults, escapes, offsets, no annotation). Workspace tests: no syntax error between ON and OFF but one after OFF, with the rules still running; fix_file keeping ${SCHEMA} while fixing around it; no fix inside an expansion; the formatter leaving the statement unchanged. A lexer test covers the new identifier contexts. The migrations guide describes the behaviour.

Not covered

$$ inside an ENVSUB region becomes $ for goose, so dollar-quoted bodies break there in goose itself. We still read them as dollar quotes.

Between -- +goose ENVSUB ON and OFF, goose replaces ${NAME} with environment variables. The analysis now reads each braced expansion as the named parameter $NAME, padded to the same length, so it no longer causes syntax errors. Fixes that would edit inside an expansion are dropped, and the formatter leaves statements with expansions alone. Named parameters after more keywords that name objects, like REFERENCES and INTO, are now identifiers.

This branch has not been deployed

No deployments
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.

1 participant