Context
#245 (closed in v0.28.0) added auto-normalization script: str → array for SQL transformations via normalize_blocks_codes_script calling split_statements(). That closed the string-vs-array runtime crash.
There is a remaining gap on the same code path: when the input script is already a list but a single element contains multiple ;-separated statements, the normalizer short-circuits and never re-splits per-element.
Failure mode
Runtime crashes at job execution with:
Error "odbc_prepare(): SQL error: Actual statement count 2 did not match the desired statement count 1.,
SQL state 0A000 in SQLPrepare" while executing query "CREATE OR REPLACE TABLE ... ; alter session unset week_of_year_policy, week_start;"
ODBC SQLPrepare expects exactly 1 statement per script[i] element. Two statements glued by ; in one element pass the array-shape validator (it's still a list of strings), so the write lands as 200 OK and the version increments — crash happens only at job run time.
Root cause
src/keboola_agent_cli/sync/code_extraction.py line 173 in normalize_blocks_codes_script:
script = code.get("script")
if not isinstance(script, str):
continue # ← already a list → passed through unchanged, even if elements are malformed
The split_statements() state machine that #245 already wired up handles this case correctly — it just isn't invoked on per-element list contents.
Repro
Take any Snowflake transformation, put two ;-separated statements into one script[i] element via Storage API PUT (or have a third-party tool / older UI / hand-edited config produce that shape — both happen in practice). Storage API accepts it silently and the version increments. Run the job → the runtime error above.
A natural seed for this shape: legacy code where a CREATE OR REPLACE TABLE …; ALTER SESSION SET week_of_year_policy=1; pair was authored as one block, then ingested by something that wraps the whole block as a single array element.
Proposal
In the loop over codes, after the existing string-case branch, also pass each list element through split_statements() (SQL transformations only — the existing is_sql gate). If the element produces more than 1 statement, replace it in place with the multi-element split. Surface via the existing normalizations envelope with a new action label, e.g. sql_resplit, plus before_length=1 / after_length=N so the silent fix stays observable.
This keeps the helper's contract — "every list element is exactly one statement after kbagent writes it" — and reuses the same split_statements() logic that already respects '…' / \"…\" / \$\$…\$\$ / -- / # / // / /* … */.
Where this was hit
SK→CZ migration on 2026-05-11 of project 1507 transformation 745661351 (Data reports). Three elements contained CREATE OR REPLACE TABLE … ; alter session set/unset week_of_year_policy, week_start; glued in one element each:
- C0 "CZ Presidents" el[23] (
out.proxy_zombici)
- C1 "SK Presidents" el[26] (
tmp.sk_visits_cpc_ag_prep)
- C1 "SK Presidents" el[29] (
out.sk_proxy_start)
These survived v513 → v515 because the array was already a list and only specific elements got rewritten — the offending elements were not touched by the migration text replacements but were re-pushed verbatim. Manual fix: split each element in two and push via kbagent config update --configuration-file ….
Related
Context
#245 (closed in v0.28.0) added auto-normalization
script: str → arrayfor SQL transformations vianormalize_blocks_codes_scriptcallingsplit_statements(). That closed the string-vs-array runtime crash.There is a remaining gap on the same code path: when the input
scriptis already a list but a single element contains multiple;-separated statements, the normalizer short-circuits and never re-splits per-element.Failure mode
Runtime crashes at job execution with:
ODBC
SQLPrepareexpects exactly 1 statement perscript[i]element. Two statements glued by;in one element pass the array-shape validator (it's still a list of strings), so the write lands as 200 OK and the version increments — crash happens only at job run time.Root cause
src/keboola_agent_cli/sync/code_extraction.pyline 173 innormalize_blocks_codes_script:The
split_statements()state machine that #245 already wired up handles this case correctly — it just isn't invoked on per-element list contents.Repro
Take any Snowflake transformation, put two
;-separated statements into onescript[i]element via Storage API PUT (or have a third-party tool / older UI / hand-edited config produce that shape — both happen in practice). Storage API accepts it silently and the version increments. Run the job → the runtime error above.A natural seed for this shape: legacy code where a
CREATE OR REPLACE TABLE …; ALTER SESSION SET week_of_year_policy=1;pair was authored as one block, then ingested by something that wraps the whole block as a single array element.Proposal
In the loop over
codes, after the existing string-case branch, also pass each list element throughsplit_statements()(SQL transformations only — the existingis_sqlgate). If the element produces more than 1 statement, replace it in place with the multi-element split. Surface via the existingnormalizationsenvelope with a new action label, e.g.sql_resplit, plusbefore_length=1/after_length=Nso the silent fix stays observable.This keeps the helper's contract — "every list element is exactly one statement after kbagent writes it" — and reuses the same
split_statements()logic that already respects'…'/\"…\"/\$\$…\$\$/--/#/////* … */.Where this was hit
SK→CZ migration on 2026-05-11 of project 1507 transformation 745661351 (Data reports). Three elements contained
CREATE OR REPLACE TABLE … ; alter session set/unset week_of_year_policy, week_start;glued in one element each:out.proxy_zombici)tmp.sk_visits_cpc_ag_prep)out.sk_proxy_start)These survived v513 → v515 because the array was already a list and only specific elements got rewritten — the offending elements were not touched by the migration text replacements but were re-pushed verbatim. Manual fix: split each element in two and push via
kbagent config update --configuration-file ….Related