Skip to content

[bugfix] External variable: supplied value overrides declared default#6475

Open
joewiz wants to merge 2 commits into
eXist-db:developfrom
joewiz:feat/external-var-default-override
Open

[bugfix] External variable: supplied value overrides declared default#6475
joewiz wants to merge 2 commits into
eXist-db:developfrom
joewiz:feat/external-var-default-override

Conversation

@joewiz

@joewiz joewiz commented Jun 14, 2026

Copy link
Copy Markdown
Member

[This PR was co-authored with Claude Code. -Joe]

What & why

A value supplied for an external variable by the external environment should take precedence over the variable's declared default value (XQuery 3.1 §4.15 External Variables: "If a value is supplied by the external environment for an external variable, that value is used; otherwise, if a default value is specified, the default is used.").

eXist evaluated the default unconditionally for a declare variable $x external := … declaration, so a value bound before execution — via XQueryContext.declareVariable (the XMLDB XQueryService.declareVariable, util:eval's external-variables argument, REST/API callers, etc.) — was silently ignored whenever the declaration carried a default. Only externals without a default honored a supplied value.

(: with $greeting bound to "supplied" by the caller :)
declare variable $greeting external := "default";
$greeting
(: before: "default"   after: "supplied" :)

How

VariableDeclaration was constructed identically for a plain internal global (declare variable $x := …) and an external-with-default (declare variable $x external := …), so eval() couldn't tell them apart and always evaluated the initializer.

  • The tree parser (XQueryTree.g) now marks the declaration as external via a new VariableDeclaration.setExternal(true).
  • VariableDeclaration.eval(): for an external declaration with a default, it first checks whether a value was already supplied (a non-null global value bound before execution) and, if so, uses it (applying the declared sequence type) instead of evaluating the default.

Behavior is unchanged for: non-external globals (always evaluate their initializer), external variables without a default, and defaulted externals with no supplied value.

Test plan

  • New ExternalVariableDefaultOverrideTest (exist-core), 5 cases, all green locally:
    • supplied value overrides default; typed supplied value (xs:integer 21 → 42) overrides default; default used when not supplied; missing required external still raises XPDY0002; a plain internal global with an initializer is unaffected.
  • Requesting the full CI suite for regression coverage across the XQuery prolog/variable paths.

Motivation

Enables parameterized server-side queries (binding $param values at call time) over HTTP — e.g. an editor running a stored .xq against the database the way basex -bname=value does — without forcing query authors to drop default values from their external-variable declarations.

A value supplied for an external variable by the external environment must take
precedence over the variable's declared default (XQuery 3.1 §4.15 External
Variables). eXist evaluated the default unconditionally for a
`declare variable $x external := ...` declaration, so a value bound via
XQueryContext.declareVariable (XMLDB declareVariable, util:eval external vars,
REST/API callers, etc.) was ignored whenever the declaration carried a default.

VariableDeclaration now carries an `external` flag (set by the tree parser for
`declare variable ... external`). On eval, an external declaration with a
default first checks whether a value was supplied (a non-null global value
already bound before execution) and, if so, uses it instead of evaluating the
default. Behavior is unchanged for non-external globals, for external variables
without a default, and for defaulted externals with no supplied value.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@joewiz joewiz requested a review from a team as a code owner June 14, 2026 16:40
joewiz added a commit to joewiz/existdb-openapi that referenced this pull request Jun 14, 2026
Adds a `variables` describe block to query.cy.js covering: scalar string
binding; JSON number → xs:double; JSON array → array(*) (members via ?*);
JSON object → map(*); a supplied name with no matching external declaration
is ignored; and a missing required external returns an error. All use
no-default externals, so the spec is independent of the eXist-core
default-override fix (eXist-db/exist#6475). 37/37 green locally.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
final XQueryService service = server.getRoot().getService(XQueryService.class);
try {
service.query("declare variable $required external; $required");
org.junit.Assert.fail("expected XPDY0002 for an unbound external variable with no default");

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

use short name ;)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

[This response was co-authored with Claude Code. -Joe]

Done — switched to import static org.junit.Assert.fail; and the short fail(...). Thanks for the review!

@line-o line-o left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks. LGTM

@line-o line-o requested a review from a team June 14, 2026 16:54
Addresses @line-o's review on PR eXist-db#6475.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
joewiz added a commit to joewiz/existdb-openapi that referenced this pull request Jun 14, 2026
Adds a `variables` describe block to query.cy.js covering: scalar string
binding; JSON number → xs:double; JSON array → array(*) (members via ?*);
JSON object → map(*); a supplied name with no matching external declaration
is ignored; and a missing required external returns an error. All use
no-default externals, so the spec is independent of the eXist-core
default-override fix (eXist-db/exist#6475). 37/37 green locally.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.

2 participants