Skip to content

Fix inline sequence rendering after IN (#629) - #1013

Open
cathleeny wants to merge 7 commits into
mainfrom
cathleeny/fix-inline-in-list-rendering
Open

cathleeny wants to merge 7 commits into
mainfrom
cathleeny/fix-inline-in-list-rendering

Conversation

@cathleeny

@cathleeny cathleeny commented Nov 6, 2026 •

Copy link
Copy Markdown
Collaborator

Description

BUG FIX: Since 4.0.4 (#559), inline parameters (use_inline_params=True) render every Python sequence as an ARRAY(...) literal. The WHERE x IN %(ids)s pattern documented in docs/parameters.md therefore renders as x IN ARRAY(1,2,3), which Spark rejects with PARSE_SYNTAX_ERROR. That blocks users moving from 3.x.

Change: A sequence at a marker that directly follows IN or NOT IN renders as a parenthesised list, (1,2,3), again. Everywhere else it stays an ARRAY(...) literal, including IN (%(p)s) where the caller wrote the parentheses, so the inline writes to ARRAY columns added in #559 keep working. Inside the list, a tuple renders as a row and a list as an ARRAY(...) literal, so a multi-column (a, b) IN %(pairs)s with [(1, 2), (3, 4)] renders as (a, b) IN ((1,2),(3,4)), as on 3.x.

Scope:

  • Rendering happens in Cursor._prepare_inline_parameters before backend dispatch, so the fix covers the Thrift, SEA and kernel backends.
  • When no parameter is a sequence, rendering skips the extra pass and runs the same code as main.
  • Spark doesn't widen types inside a row, so each value must have its column's type: the literal 1 is an INT and doesn't match a BIGINT column. docs/parameters.md says so.
  • Unchanged:
    • An empty sequence renders as IN (), which Spark rejects with INVALID_SQL_SYNTAX.EMPTY_IN_PREDICATE, the same as on 3.x.
    • A marker separated from IN by a comment keeps the ARRAY rendering.
  • An earlier community PR (Fix inline list parameter rendering for IN clauses #773) rendered every top-level sequence as (...). That would break the inline ARRAY inserts covered by test_primitive_single[INLINE-ARRAYS] and test_inline_recursive_complex_type, so this change only targets the IN position.

How is this tested?

  • Unit:

    • The full unit suite passes (-m "not realkernel"), and black --check src and mypy src are clean.
  • E2E:

    Case main This PR
    id IN %(ids)s with [1, 3] PARSE_SYNTAX_ERROR rows 1, 3
    id NOT IN %(ids)s with (1, 3) PARSE_SYNTAX_ERROR rows 0, 2, 4
    single element [2] PARSE_SYNTAX_ERROR row 2
    strings containing ' and \ PARSE_SYNTAX_ERROR both match
    same name after IN and in array_contains PARSE_SYNTAX_ERROR rows 1, 3
    IN %(ids)s ... LIMIT %(n)d PARSE_SYNTAX_ERROR rows 1, 3
    PIVOT (... FOR k IN %(ks)s) PARSE_SYNTAX_ERROR a=1, b=2
    position('b' IN %(s)s) with ["abc"] DATATYPE_MISMATCH 2
    nested arrays [[1, 2], [3]] PARSE_SYNTAX_ERROR rows [1,2], [3]
    a date and NULL PARSE_SYNTAX_ERROR row 2024-01-02
    Decimal("1.50") PARSE_SYNTAX_ERROR row 1.50
    empty list PARSE_SYNTAX_ERROR EMPTY_IN_PREDICATE, as on 3.x
    (id, tag) IN %(pairs)s with [(1, "a"), (2, "x"), (3, "c")] PARSE_SYNTAX_ERROR rows 1, 3
    (a, b) NOT IN %s with ((1, 2), (3, 4)) PARSE_SYNTAX_ERROR row (5, 6)
    Row objects from fetchall() PARSE_SYNTAX_ERROR row (3, 4)
    rows of INT literals against BIGINT columns PARSE_SYNTAX_ERROR DATATYPE_MISMATCH (Spark doesn't widen row types)

    The unchanged renderings, array_contains(%(p)s, 2) and arr IN (%(p)s), still return the expected results.

Related Tickets & Documents

Fixes #629

Since 4.0.4, inline parameters render every Python sequence as an ARRAY
literal, so the documented `x IN %(ids)s` pattern produces
`x IN ARRAY(1,2,3)`, which Spark rejects with PARSE_SYNTAX_ERROR.

A sequence whose marker directly follows IN (or NOT IN) now renders as a
parenthesised list again. Everywhere else, including `IN (%(p)s)` where
the caller wrote the parentheses, it stays an ARRAY literal, so inline
writes to ARRAY columns keep working. Because `x IN ARRAY(...)` never
parses, no query that runs today renders differently.

Co-authored-by: Isaac <[email protected]>
Signed-off-by: cathleen.yan <[email protected]>
Follow-ups from review of the inline IN fix:

- Decide per marker instead of per parameter name. A name used both
  directly after IN and elsewhere now keeps its ARRAY rendering at the
  other markers. Before, every occurrence became `(...)`, which could
  break a query that runs on main, e.g. one with a commented-out filter:
  `arr = %(tags)s -- AND tag IN %(tags)s`.
- Probe only sequence values and give every other marker its real
  escaped value, so a `%d` or `%(n)d` marker elsewhere in the query no
  longer turns the fix off. Probe tokens carry a random nonce so that a
  string value cannot pass for one.
- Leave escape_args exactly as it is on main and move the logic into
  ParamEscaper.render_inline, which Cursor._prepare_inline_parameters
  now calls.

Co-authored-by: Isaac <[email protected]>
Signed-off-by: Cathleen Yan <[email protected]>
@cathleeny
cathleeny marked this pull request as ready for review November 6, 2026 01:27

@peco-review-bot peco-review-bot Bot 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.

✅ No issues identified by the review bot.

Comment thread src/databricks/sql/utils.py Resolved
Comment thread src/databricks/sql/utils.py Resolved
Comment thread src/databricks/sql/utils.py Resolved
Follow-ups from review:

- Inside an IN list a tuple now renders as a row, so a multi-column
  `(a, b) IN %(pairs)s` with `[(1, 2), (3, 4)]` renders as
  `(a, b) IN ((1,2),(3,4))`, as it did in 3.x. Lists still render as
  ARRAY literals so ARRAY columns can be matched. Spark does not widen
  the field types of a row, so each value must have its column's type;
  docs/parameters.md says so.
- When no parameter is a sequence, render_inline skips the probe pass
  and renders exactly as escape_args and inject_parameters do on main.

Co-authored-by: Isaac <[email protected]>
Signed-off-by: Cathleen Yan <[email protected]>

@peco-review-bot peco-review-bot Bot 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.

Verdict: 1 Low

Looks good overall, with one low-severity edge case in IN detection. The probe-based per-marker approach is correct for named, positional, %% and %d markers, and it keeps the code path main uses when no parameter is a sequence. Nit (not posted inline): the new render_inline / _sequence_markers_following_in signatures leave parameters/escaped untyped, unlike the typed helpers next to them.

Comment thread src/databricks/sql/utils.py Resolved
Review bot follow-up: a `--` comment line that ends with the word IN
made the marker on the next line render as a list. For example

    WHERE arr =  -- tags we filter IN
      %(tags)s

rendered as `arr = ('a','b')` instead of `arr = ARRAY('a','b')`, which
breaks a query that runs on main. A match is now ignored when a `--`
comes before its IN on the same line and the marker is on a later line.

The probe pass also replaces escaped strings with '' (numbers stay, so
%d still formats), so a value containing `--` cannot hide a real IN.

Co-authored-by: Isaac <[email protected]>
Signed-off-by: Cathleen Yan <[email protected]>

@peco-review-bot peco-review-bot Bot 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.

Verdict: 1 Nit

Looks good. I found no correctness issues. The probe-based detection uses the same %-formatting as the real substitution, so marker numbering lines up for named, positional and %% cases. Errors still surface the same way as before, and queries without a sequence parameter render exactly as they do on main. The only note is a nit: a CHANGELOG entry is missing.

Other findings

  • ⚪ Nit — Consider adding an entry under # Unreleased in CHANGELOG.md. This is a user-visible fix for a 4.0.4 regression (#629) that blocks users migrating from 3.x.

Up to 4.0.3 the inline escaper rendered any iterable as `(...)`, so a
set after IN worked. 4.0.4 switched to Sequence for ARRAY rendering and
sets started raising ProgrammingError.

A set, frozenset or dict key view is accepted again when every marker
it fills directly follows IN. Its items are sorted so the SQL text is
stable, and a set of tuples renders as rows. Anywhere else a set raises
the same ProgrammingError as before, since a set has no order to keep
in an ARRAY.

Also adds a CHANGELOG entry for the inline IN fix.

Co-authored-by: Isaac <[email protected]>
Signed-off-by: Cathleen Yan <[email protected]>

@peco-review-bot peco-review-bot Bot 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.

Verdict: 1 Low

Looks good. The marker detection works by formatting the query once with probe tokens, and I traced it through named and positional params, %%, %d/%c, bare %s with dict params, missing keys and the set placeholder handling: each path produces the same output or raises the same error as escape_args. I have one low-severity note on how deterministic the sorted output of a set is.

Comment thread src/databricks/sql/utils.py Resolved
Review bot follow-up: the comments and docs described comment handling
as if it were settled. They now say that IN is found by matching the
SQL text rather than parsing it, which case is handled (an IN that ends
a `--` comment line) and which is not (an IN inside a string literal or
block comment directly before a marker).

Co-authored-by: Isaac <[email protected]>
Signed-off-by: Cathleen Yan <[email protected]>
Review bot follow-up: sorting a set's items does not give a stable
order when items compare inconsistently, such as float("nan") among
other numbers. The SQL still matches the same rows, but the docstring
and docs/parameters.md no longer say the query text is always the same.

Co-authored-by: Isaac <[email protected]>
Signed-off-by: Cathleen Yan <[email protected]>

@peco-review-bot peco-review-bot Bot 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.

✅ No issues identified by the review bot.

This branch was successfully deployed

1 active deployment
azure-prod — 7256b6b6 Deployed Nov 6, 2026 by cathleeny via followup #1429
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.

Compiled sql for an input list parameter results in incorrect syntax in 4.0.4

2 participants