Skip to content

Commit 665dc11

Browse files
localstack-spiral[bot]spiralhovaescogithub-actions[bot]
authored
LAV-3281: Support EXECUTE TASK USING CONFIG (#3857)
* LAV-3281: support per-run task configuration and latest-only queuing Parse quoted and dollar-quoted USING CONFIG values, validate JSON objects with Snowflake's captured errors, and persist effective configuration on each root run without changing the task definition. Recursively merge matching objects, replace scalars/arrays, and reproduce captured null-key semantics. Use the common execution procedure for SQL and JavaScript graphs. Publish the manual run's execution fence before its body, replace queued configuration requests atomically, and dispatch the latest request after roots, descendants, and finalizers finish. Return the procedure's real immediate/queued status. Verification: - Real Snowflake captures: all 66 new cases captured serially; the final value-space capture waits for SUCCEEDED before requesting the later run. - make test-compat PYTEST_PATH=tests/queries/test_execute_task_config.py PYTEST_XDIST_ARGS="": 66 passed. - make test-compat PYTEST_PATH=tests/queries/test_tasks.py PYTEST_ARGS="-k test_execute_task" PYTEST_XDIST_ARGS="": 14 passed. - make test-compat PYTEST_PATH=tests/queries/test_task_graph_config.py PYTEST_XDIST_ARGS="": all passed. - make check: passed. - make test-vendored-sqlparser: passed, including the new parser cases. Co-authored-by: Przemek Denkiewicz <przemyslaw.denkiewicz@localstack.cloud> ## Deviations - The spec Notes describe SYSTEM$GET_TASK_GRAPH_CONFIG as absent, but it is already implemented in this checkout. Used the existing function unchanged; no implementation or substitute for LAV-3036 was added. - The name-only parser hint is stale: both retry forms already exist. Kept them unchanged and added the mutually exclusive USING CONFIG branch. - Captured Snowflake merges matching objects recursively and removes null keys at merged levels, while retaining nulls in copied/replaced objects, arrays, and dynamic configurations without defaults. Followed those snapshots. ## Test matrix Resource index: EXECUTE TASK has the optional USING CONFIG string clause; RETRY LAST and RETRY GRAPH RUN GROUP are explicitly excluded by this issue. empty-object × quoted × default=absent × dynamic/persisted/later-manual -> tests/queries/test_execute_task_config.py::test_execute_task_config_values scalars-null-apostrophe × quoted × default=absent × dynamic/persisted/later-manual -> tests/queries/test_execute_task_config.py::test_execute_task_config_values nested-objects-arrays × quoted × default=absent × dynamic/persisted/later-manual -> tests/queries/test_execute_task_config.py::test_execute_task_config_values object-scalar-null-replacement × quoted × default=absent × dynamic/persisted/later-manual -> tests/queries/test_execute_task_config.py::test_execute_task_config_values new-objects-with-null × quoted × default=absent × dynamic/persisted/later-manual -> tests/queries/test_execute_task_config.py::test_execute_task_config_values scalar-to-object-and-array-null × quoted × default=absent × dynamic/persisted/later-manual -> tests/queries/test_execute_task_config.py::test_execute_task_config_values nested-null-deletion × quoted × default=absent × dynamic/persisted/later-manual -> tests/queries/test_execute_task_config.py::test_execute_task_config_values empty-object × dollar × default=absent × dynamic/persisted/later-manual -> tests/queries/test_execute_task_config.py::test_execute_task_config_values scalars-null-apostrophe × dollar × default=absent × dynamic/persisted/later-manual -> tests/queries/test_execute_task_config.py::test_execute_task_config_values nested-objects-arrays × dollar × default=absent × dynamic/persisted/later-manual -> tests/queries/test_execute_task_config.py::test_execute_task_config_values object-scalar-null-replacement × dollar × default=absent × dynamic/persisted/later-manual -> tests/queries/test_execute_task_config.py::test_execute_task_config_values new-objects-with-null × dollar × default=absent × dynamic/persisted/later-manual -> tests/queries/test_execute_task_config.py::test_execute_task_config_values scalar-to-object-and-array-null × dollar × default=absent × dynamic/persisted/later-manual -> tests/queries/test_execute_task_config.py::test_execute_task_config_values nested-null-deletion × dollar × default=absent × dynamic/persisted/later-manual -> tests/queries/test_execute_task_config.py::test_execute_task_config_values empty-object × quoted × default=empty × dynamic/persisted/later-manual -> tests/queries/test_execute_task_config.py::test_execute_task_config_values scalars-null-apostrophe × quoted × default=empty × dynamic/persisted/later-manual -> tests/queries/test_execute_task_config.py::test_execute_task_config_values nested-objects-arrays × quoted × default=empty × dynamic/persisted/later-manual -> tests/queries/test_execute_task_config.py::test_execute_task_config_values object-scalar-null-replacement × quoted × default=empty × dynamic/persisted/later-manual -> tests/queries/test_execute_task_config.py::test_execute_task_config_values new-objects-with-null × quoted × default=empty × dynamic/persisted/later-manual -> tests/queries/test_execute_task_config.py::test_execute_task_config_values scalar-to-object-and-array-null × quoted × default=empty × dynamic/persisted/later-manual -> tests/queries/test_execute_task_config.py::test_execute_task_config_values nested-null-deletion × quoted × default=empty × dynamic/persisted/later-manual -> tests/queries/test_execute_task_config.py::test_execute_task_config_values empty-object × dollar × default=empty × dynamic/persisted/later-manual -> tests/queries/test_execute_task_config.py::test_execute_task_config_values scalars-null-apostrophe × dollar × default=empty × dynamic/persisted/later-manual -> tests/queries/test_execute_task_config.py::test_execute_task_config_values nested-objects-arrays × dollar × default=empty × dynamic/persisted/later-manual -> tests/queries/test_execute_task_config.py::test_execute_task_config_values object-scalar-null-replacement × dollar × default=empty × dynamic/persisted/later-manual -> tests/queries/test_execute_task_config.py::test_execute_task_config_values new-objects-with-null × dollar × default=empty × dynamic/persisted/later-manual -> tests/queries/test_execute_task_config.py::test_execute_task_config_values scalar-to-object-and-array-null × dollar × default=empty × dynamic/persisted/later-manual -> tests/queries/test_execute_task_config.py::test_execute_task_config_values nested-null-deletion × dollar × default=empty × dynamic/persisted/later-manual -> tests/queries/test_execute_task_config.py::test_execute_task_config_values empty-object × quoted × default=populated × dynamic/persisted/later-manual -> tests/queries/test_execute_task_config.py::test_execute_task_config_values scalars-null-apostrophe × quoted × default=populated × dynamic/persisted/later-manual -> tests/queries/test_execute_task_config.py::test_execute_task_config_values nested-objects-arrays × quoted × default=populated × dynamic/persisted/later-manual -> tests/queries/test_execute_task_config.py::test_execute_task_config_values object-scalar-null-replacement × quoted × default=populated × dynamic/persisted/later-manual -> tests/queries/test_execute_task_config.py::test_execute_task_config_values new-objects-with-null × quoted × default=populated × dynamic/persisted/later-manual -> tests/queries/test_execute_task_config.py::test_execute_task_config_values scalar-to-object-and-array-null × quoted × default=populated × dynamic/persisted/later-manual -> tests/queries/test_execute_task_config.py::test_execute_task_config_values nested-null-deletion × quoted × default=populated × dynamic/persisted/later-manual -> tests/queries/test_execute_task_config.py::test_execute_task_config_values empty-object × dollar × default=populated × dynamic/persisted/later-manual -> tests/queries/test_execute_task_config.py::test_execute_task_config_values scalars-null-apostrophe × dollar × default=populated × dynamic/persisted/later-manual -> tests/queries/test_execute_task_config.py::test_execute_task_config_values nested-objects-arrays × dollar × default=populated × dynamic/persisted/later-manual -> tests/queries/test_execute_task_config.py::test_execute_task_config_values object-scalar-null-replacement × dollar × default=populated × dynamic/persisted/later-manual -> tests/queries/test_execute_task_config.py::test_execute_task_config_values new-objects-with-null × dollar × default=populated × dynamic/persisted/later-manual -> tests/queries/test_execute_task_config.py::test_execute_task_config_values scalar-to-object-and-array-null × dollar × default=populated × dynamic/persisted/later-manual -> tests/queries/test_execute_task_config.py::test_execute_task_config_values nested-null-deletion × dollar × default=populated × dynamic/persisted/later-manual -> tests/queries/test_execute_task_config.py::test_execute_task_config_values empty-string × quoted × rejection -> tests/queries/test_execute_task_config.py::test_execute_task_config_invalid malformed-token × quoted × rejection -> tests/queries/test_execute_task_config.py::test_execute_task_config_invalid malformed-object × quoted × rejection -> tests/queries/test_execute_task_config.py::test_execute_task_config_invalid empty-array × quoted × rejection -> tests/queries/test_execute_task_config.py::test_execute_task_config_invalid populated-array × quoted × rejection -> tests/queries/test_execute_task_config.py::test_execute_task_config_invalid numeric-root × quoted × rejection -> tests/queries/test_execute_task_config.py::test_execute_task_config_invalid string-root × quoted × rejection -> tests/queries/test_execute_task_config.py::test_execute_task_config_invalid boolean-root × quoted × rejection -> tests/queries/test_execute_task_config.py::test_execute_task_config_invalid null-root × quoted × rejection -> tests/queries/test_execute_task_config.py::test_execute_task_config_invalid empty-string × dollar × rejection -> tests/queries/test_execute_task_config.py::test_execute_task_config_invalid malformed-token × dollar × rejection -> tests/queries/test_execute_task_config.py::test_execute_task_config_invalid malformed-object × dollar × rejection -> tests/queries/test_execute_task_config.py::test_execute_task_config_invalid empty-array × dollar × rejection -> tests/queries/test_execute_task_config.py::test_execute_task_config_invalid populated-array × dollar × rejection -> tests/queries/test_execute_task_config.py::test_execute_task_config_invalid numeric-root × dollar × rejection -> tests/queries/test_execute_task_config.py::test_execute_task_config_invalid string-root × dollar × rejection -> tests/queries/test_execute_task_config.py::test_execute_task_config_invalid boolean-root × dollar × rejection -> tests/queries/test_execute_task_config.py::test_execute_task_config_invalid null-root × dollar × rejection -> tests/queries/test_execute_task_config.py::test_execute_task_config_invalid missing-task × valid-object × rejection -> tests/queries/test_execute_task_config.py::test_execute_task_config_boundaries child-task × valid-object × rejection -> tests/queries/test_execute_task_config.py::test_execute_task_config_boundaries root-with-default × original-config-isolation -> tests/queries/test_execute_task_config.py::test_execute_task_config_latest_queued root-with-default × latest-request-replaces-earlier-requests -> tests/queries/test_execute_task_config.py::test_execute_task_config_latest_queued root-with-default × invalid-request-does-not-replace-queue -> tests/queries/test_execute_task_config.py::test_execute_task_config_latest_queued root-with-default × later-manual-uses-default -> tests/queries/test_execute_task_config.py::test_execute_task_config_latest_queued root-without-default × original-config-isolation -> tests/queries/test_execute_task_config.py::test_execute_task_config_latest_queued root-without-default × latest-request-replaces-earlier-requests -> tests/queries/test_execute_task_config.py::test_execute_task_config_latest_queued root-without-default × invalid-request-does-not-replace-queue -> tests/queries/test_execute_task_config.py::test_execute_task_config_latest_queued root-without-default × later-manual-uses-default -> tests/queries/test_execute_task_config.py::test_execute_task_config_latest_queued child × original-config-isolation -> tests/queries/test_execute_task_config.py::test_execute_task_config_latest_queued child × latest-request-replaces-earlier-requests -> tests/queries/test_execute_task_config.py::test_execute_task_config_latest_queued child × invalid-request-does-not-replace-queue -> tests/queries/test_execute_task_config.py::test_execute_task_config_latest_queued child × later-manual-uses-default -> tests/queries/test_execute_task_config.py::test_execute_task_config_latest_queued finalizer × original-config-isolation -> tests/queries/test_execute_task_config.py::test_execute_task_config_latest_queued finalizer × latest-request-replaces-earlier-requests -> tests/queries/test_execute_task_config.py::test_execute_task_config_latest_queued finalizer × invalid-request-does-not-replace-queue -> tests/queries/test_execute_task_config.py::test_execute_task_config_latest_queued finalizer × later-manual-uses-default -> tests/queries/test_execute_task_config.py::test_execute_task_config_latest_queued sql-root × original-config-isolation -> tests/queries/test_execute_task_config.py::test_execute_task_config_latest_queued sql-root × latest-request-replaces-earlier-requests -> tests/queries/test_execute_task_config.py::test_execute_task_config_latest_queued sql-root × invalid-request-does-not-replace-queue -> tests/queries/test_execute_task_config.py::test_execute_task_config_latest_queued sql-root × later-manual-uses-default -> tests/queries/test_execute_task_config.py::test_execute_task_config_latest_queued SHOW TASKS / DESCRIBE TASK / GET_DDL × default persistence -> tests/queries/test_execute_task_config.py::test_execute_task_config_values Quoted identifiers / apostrophe escaping / dollar strings × parsing -> vendor/sqlparser/tests/sqlparser_snowflake.rs::execute_task_using_config Non-string arguments / mixed retry clauses × parse rejection -> vendor/sqlparser/tests/sqlparser_snowflake.rs::execute_task_using_config Plain EXECUTE TASK / existing retry forms × regression -> tests/queries/test_tasks.py::test_execute_task and test_execute_task_retry_* Default configuration / descendants / JavaScript / concurrent scheduled graphs × regression -> tests/queries/test_task_graph_config.py * LAV-3281: wip (budget exhausted: wall_clock) Co-authored-by: Przemek Denkiewicz <przemyslaw.denkiewicz@localstack.cloud> * LAV-3281: investigator: capture WIP regressions then fix queue lifecycle and transaction dispatch Attempt 1 exhausted the shared wall-clock budget at 2026-10-02 00:39:31, only 5m31s after reviewer feedback. It did not bail out or loop on a blocker. The initial implementation and focused tests advanced; checks returned ok but explicitly timed out the full compat lane and skipped the state-snapshot lane. Those lanes remain unverified, not green evidence. 43a567a54 adds only regression tests to test_execute_task_config.py: submission (12 cases), later_scheduled, queue_lifecycle (2 cases). None has snapshot entries yet. /tmp/lav3281-capture.log ends during server compilation, not successful Cloud capture. Finish these NEW captures with TEST_TARGET=SNOWFLAKE_CLOUD and --snapshot-update, serially, then replay on the emulator. No existing-snapshot re-record waiver is needed for new entries. Do not trust the WIP tests as validated. Verified remaining production gaps, unchanged since review: - ddl/task.rs:2738 queues only on active/conflicting dispatch, while :2752 clears pending state only for source=queued. An immediate manual C after A settles leaves B pending. Serialize manual replacement/dispatch with queued claiming; clear/replace pending state atomically even if C can dispatch immediately. Preserve the published EXECUTING fence. - ddl/task.rs:1068 drop and :800 revival leave queued_config untouched; clear it for drop/replacement/revival and sweep related lifecycle paths. scheduler.rs:613 selects pending requests even for suspended roots. - ddl/task.rs:2815 manual COMMIT is still present. task_body.rs dispatches via executor.rs:1241 query_task_with_session_and_params -> with_session_conn, which uses a savepoint for open sessions (:905) and an explicit transaction for async submission (:956). processing.rs:3333 intercepts server roots before normal DDL implicit-commit handling. Fix both SQL/server-routed roots and both plain/dynamic submissions without losing the durable fence; use Cloud transaction witness snapshots to determine session semantics. - Capture/replay dynamic then scheduled isolation; the WIP test adds that row. SYSTEM$GET_TASK_GRAPH_CONFIG is present on origin/main: commit 91e5ce69c (LAV-3327, #3763), plus argument rejection parity 95f15d2ce (LAV-3328). The old note that LAV-3036 is a missing prerequisite is stale; do not wait on it or implement another read function. All widened ACs are now in the provided description. Finish reviewer-gap work and the required sibling sweep, then run focused regressions, make check and pipeline checks. Investigation is read-only apart from this empty guidance commit. * LAV-3281: investigator: repair scheduled-isolation polling under scoped waiver The 2026-10-02 00:55 bail-out is a new, verified test-authorization blocker, not a recurrence of the prior budget exhaustion. In the recovered final implementer transcript, the scheduled Cloud capture fails with: expected 2 successful runs: [('SCHEDULED',), ('SUCCEEDED',), ('SUCCEEDED',), ('SUCCEEDED',), ('SUCCEEDED',)]. test_execute_task_config.py:256 counts all TASK_HISTORY rows and requires every state to be SUCCEEDED; :362 invokes that helper before suspending a recurring task. A future SCHEDULED row and subsequent successful runs make that predicate unsuitable. This is a test synchronization defect, not a Cloud feature rejection. Investigator waiver: tests/queries/test_execute_task_config.py — in test_execute_task_config_later_scheduled only, replace exact-all-history-count polling with completed scheduled-run polling that ignores future SCHEDULED entries, suspend scheduling after observing success, and snapshot every distinct full configuration read by scheduled runs; preserve the dynamic-run snapshot and capture the scheduled-default snapshot against Snowflake Cloud. Keep the shared _task_config_settled helper and other tests unchanged under this waiver. Wait for at least the completed dynamic run plus a completed scheduled run, not exactly two total history entries. After suspending, drain any in-flight execution before reading the table. SELECT DISTINCT configuration makes recurring cadence irrelevant while retaining every mismatching configuration; do not mask keys or merely snapshot a boolean/row count. Copy test_edit_waiver: tests/queries/test_execute_task_config.py into the implementer's fix commit body. Capture serially using TEST_TARGET=SNOWFLAKE_CLOUD, --snapshot-update and PYTEST_XDIST_ARGS="". The final worker temporarily captured all 12 submission cases and both lifecycle cases; its patched submission replay passed 12/12. It then reset --hard to 3ba0ad610, discarding both repairs and captures. Current snapshots contain none of submission/later_scheduled/queue_lifecycle; recapture all 15 new cases. Prior guidance 3ba0ad610 remains applicable: queue handoff, drop/revival queued_config clearing, transaction-safe dispatch and sibling sweep still need implementation and replay. This investigation changes no source or snapshots and grants no broader test edit. No human decision or external prerequisite is needed. Co-authored-by: Przemek Denkiewicz <przemyslaw.denkiewicz@localstack.cloud> * LAV-3281: fix queue lifecycle and transaction-safe manual dispatch Serialize root request selection under the task row lock through publication of its EXECUTING fence. An immediately dispatched manual request clears pending configuration as well as a scheduler claim, so an older override cannot survive the handoff. Clear pending overrides on task deletion and soft-deleted revival. Execute internal task CALLs on autocommit pool connections, retaining session GUCs and store-owner privileges. Do not wrap manual task execution in the caller's open transaction, async transaction, or savepoint; do not implicitly commit its user's transaction. Cloud snapshots prove ROLLBACK still removes prior DML for SQL and JavaScript roots, both with and without USING CONFIG. Repair only the scheduled-isolation test's polling under the investigator's scoped authorization: observe a completed scheduled run, suspend, drain active runs, and snapshot every distinct complete configuration. Captured all 15 WIP regressions and an additional plain-request supersession regression against real Snowflake, serially. Existing snapshots and test assertions are unchanged. test_edit_waiver: tests/queries/test_execute_task_config.py Authority: ea172b43a investigator waiver, scheduled-isolation polling only. Pattern sweep: swept root dispatch and scheduler claiming for stale pending requests, including plain EXECUTE TASK superseding a dynamic request; fixed both immediate dispatch and drop/revival. CREATE OR REPLACE and CLONE delete and insert a fresh task row without copying queued_config; CREATE OR ALTER preserves the existing task identity. Swept both SQL and server-routed manual entry points for transaction-bound CALLs: fixed their shared executor and SQL DDL commit hook. Scheduled runs already use pool-only dispatch. Covered async, open-transaction, and async-in-transaction inputs for plain and dynamic runs. Verification: - Cloud captures: 15 WIP regressions plus plain-request supersession passed. - make test-compat PYTEST_PATH=tests/queries/test_execute_task_config.py PYTEST_XDIST_ARGS='': 82 passed. - make test-compat PYTEST_PATH=tests/queries/test_tasks.py PYTEST_ARGS='-k execute_task': 14 passed. - make test-compat PYTEST_PATH=tests/queries/test_task_graph_config.py: 105 passed, including overlapping scheduled graphs. - make check: passed, including vendored sqlparser tests. Co-authored-by: Przemek Denkiewicz <przemyslaw.denkiewicz@localstack.cloud> ## Deviations - The spec Notes describe SYSTEM$GET_TASK_GRAPH_CONFIG as absent, but it is already implemented in this checkout. Used the existing function unchanged; no implementation or substitute for LAV-3036 was added. - The name-only parser hint is stale: both retry forms already exist. Kept them unchanged and added the mutually exclusive USING CONFIG branch. - Captured Snowflake merges matching objects recursively and removes null keys at merged levels, while retaining nulls in copied/replaced objects, arrays, and dynamic configurations without defaults. Followed those snapshots. - The investigator authorizes the scheduled-isolation polling repair only. Applied that waiver and retained all other existing assertions and snapshots. ## Test matrix Resource index: EXECUTE TASK has the optional USING CONFIG string clause; RETRY LAST and RETRY GRAPH RUN GROUP are explicitly excluded by this issue. empty-object × quoted × default=absent × dynamic/persisted/later-manual -> tests/queries/test_execute_task_config.py::test_execute_task_config_values scalars-null-apostrophe × quoted × default=absent × dynamic/persisted/later-manual -> tests/queries/test_execute_task_config.py::test_execute_task_config_values nested-objects-arrays × quoted × default=absent × dynamic/persisted/later-manual -> tests/queries/test_execute_task_config.py::test_execute_task_config_values object-scalar-null-replacement × quoted × default=absent × dynamic/persisted/later-manual -> tests/queries/test_execute_task_config.py::test_execute_task_config_values new-objects-with-null × quoted × default=absent × dynamic/persisted/later-manual -> tests/queries/test_execute_task_config.py::test_execute_task_config_values scalar-to-object-and-array-null × quoted × default=absent × dynamic/persisted/later-manual -> tests/queries/test_execute_task_config.py::test_execute_task_config_values nested-null-deletion × quoted × default=absent × dynamic/persisted/later-manual -> tests/queries/test_execute_task_config.py::test_execute_task_config_values empty-object × dollar × default=absent × dynamic/persisted/later-manual -> tests/queries/test_execute_task_config.py::test_execute_task_config_values scalars-null-apostrophe × dollar × default=absent × dynamic/persisted/later-manual -> tests/queries/test_execute_task_config.py::test_execute_task_config_values nested-objects-arrays × dollar × default=absent × dynamic/persisted/later-manual -> tests/queries/test_execute_task_config.py::test_execute_task_config_values object-scalar-null-replacement × dollar × default=absent × dynamic/persisted/later-manual -> tests/queries/test_execute_task_config.py::test_execute_task_config_values new-objects-with-null × dollar × default=absent × dynamic/persisted/later-manual -> tests/queries/test_execute_task_config.py::test_execute_task_config_values scalar-to-object-and-array-null × dollar × default=absent × dynamic/persisted/later-manual -> tests/queries/test_execute_task_config.py::test_execute_task_config_values nested-null-deletion × dollar × default=absent × dynamic/persisted/later-manual -> tests/queries/test_execute_task_config.py::test_execute_task_config_values empty-object × quoted × default=empty × dynamic/persisted/later-manual -> tests/queries/test_execute_task_config.py::test_execute_task_config_values scalars-null-apostrophe × quoted × default=empty × dynamic/persisted/later-manual -> tests/queries/test_execute_task_config.py::test_execute_task_config_values nested-objects-arrays × quoted × default=empty × dynamic/persisted/later-manual -> tests/queries/test_execute_task_config.py::test_execute_task_config_values object-scalar-null-replacement × quoted × default=empty × dynamic/persisted/later-manual -> tests/queries/test_execute_task_config.py::test_execute_task_config_values new-objects-with-null × quoted × default=empty × dynamic/persisted/later-manual -> tests/queries/test_execute_task_config.py::test_execute_task_config_values scalar-to-object-and-array-null × quoted × default=empty × dynamic/persisted/later-manual -> tests/queries/test_execute_task_config.py::test_execute_task_config_values nested-null-deletion × quoted × default=empty × dynamic/persisted/later-manual -> tests/queries/test_execute_task_config.py::test_execute_task_config_values empty-object × dollar × default=empty × dynamic/persisted/later-manual -> tests/queries/test_execute_task_config.py::test_execute_task_config_values scalars-null-apostrophe × dollar × default=empty × dynamic/persisted/later-manual -> tests/queries/test_execute_task_config.py::test_execute_task_config_values nested-objects-arrays × dollar × default=empty × dynamic/persisted/later-manual -> tests/queries/test_execute_task_config.py::test_execute_task_config_values object-scalar-null-replacement × dollar × default=empty × dynamic/persisted/later-manual -> tests/queries/test_execute_task_config.py::test_execute_task_config_values new-objects-with-null × dollar × default=empty × dynamic/persisted/later-manual -> tests/queries/test_execute_task_config.py::test_execute_task_config_values scalar-to-object-and-array-null × dollar × default=empty × dynamic/persisted/later-manual -> tests/queries/test_execute_task_config.py::test_execute_task_config_values nested-null-deletion × dollar × default=empty × dynamic/persisted/later-manual -> tests/queries/test_execute_task_config.py::test_execute_task_config_values empty-object × quoted × default=populated × dynamic/persisted/later-manual -> tests/queries/test_execute_task_config.py::test_execute_task_config_values scalars-null-apostrophe × quoted × default=populated × dynamic/persisted/later-manual -> tests/queries/test_execute_task_config.py::test_execute_task_config_values nested-objects-arrays × quoted × default=populated × dynamic/persisted/later-manual -> tests/queries/test_execute_task_config.py::test_execute_task_config_values object-scalar-null-replacement × quoted × default=populated × dynamic/persisted/later-manual -> tests/queries/test_execute_task_config.py::test_execute_task_config_values new-objects-with-null × quoted × default=populated × dynamic/persisted/later-manual -> tests/queries/test_execute_task_config.py::test_execute_task_config_values scalar-to-object-and-array-null × quoted × default=populated × dynamic/persisted/later-manual -> tests/queries/test_execute_task_config.py::test_execute_task_config_values nested-null-deletion × quoted × default=populated × dynamic/persisted/later-manual -> tests/queries/test_execute_task_config.py::test_execute_task_config_values empty-object × dollar × default=populated × dynamic/persisted/later-manual -> tests/queries/test_execute_task_config.py::test_execute_task_config_values scalars-null-apostrophe × dollar × default=populated × dynamic/persisted/later-manual -> tests/queries/test_execute_task_config.py::test_execute_task_config_values nested-objects-arrays × dollar × default=populated × dynamic/persisted/later-manual -> tests/queries/test_execute_task_config.py::test_execute_task_config_values object-scalar-null-replacement × dollar × default=populated × dynamic/persisted/later-manual -> tests/queries/test_execute_task_config.py::test_execute_task_config_values new-objects-with-null × dollar × default=populated × dynamic/persisted/later-manual -> tests/queries/test_execute_task_config.py::test_execute_task_config_values scalar-to-object-and-array-null × dollar × default=populated × dynamic/persisted/later-manual -> tests/queries/test_execute_task_config.py::test_execute_task_config_values nested-null-deletion × dollar × default=populated × dynamic/persisted/later-manual -> tests/queries/test_execute_task_config.py::test_execute_task_config_values empty-string × quoted × rejection -> tests/queries/test_execute_task_config.py::test_execute_task_config_invalid malformed-token × quoted × rejection -> tests/queries/test_execute_task_config.py::test_execute_task_config_invalid malformed-object × quoted × rejection -> tests/queries/test_execute_task_config.py::test_execute_task_config_invalid empty-array × quoted × rejection -> tests/queries/test_execute_task_config.py::test_execute_task_config_invalid populated-array × quoted × rejection -> tests/queries/test_execute_task_config.py::test_execute_task_config_invalid numeric-root × quoted × rejection -> tests/queries/test_execute_task_config.py::test_execute_task_config_invalid string-root × quoted × rejection -> tests/queries/test_execute_task_config.py::test_execute_task_config_invalid boolean-root × quoted × rejection -> tests/queries/test_execute_task_config.py::test_execute_task_config_invalid null-root × quoted × rejection -> tests/queries/test_execute_task_config.py::test_execute_task_config_invalid empty-string × dollar × rejection -> tests/queries/test_execute_task_config.py::test_execute_task_config_invalid malformed-token × dollar × rejection -> tests/queries/test_execute_task_config.py::test_execute_task_config_invalid malformed-object × dollar × rejection -> tests/queries/test_execute_task_config.py::test_execute_task_config_invalid empty-array × dollar × rejection -> tests/queries/test_execute_task_config.py::test_execute_task_config_invalid populated-array × dollar × rejection -> tests/queries/test_execute_task_config.py::test_execute_task_config_invalid numeric-root × dollar × rejection -> tests/queries/test_execute_task_config.py::test_execute_task_config_invalid string-root × dollar × rejection -> tests/queries/test_execute_task_config.py::test_execute_task_config_invalid boolean-root × dollar × rejection -> tests/queries/test_execute_task_config.py::test_execute_task_config_invalid null-root × dollar × rejection -> tests/queries/test_execute_task_config.py::test_execute_task_config_invalid missing-task × valid-object × rejection -> tests/queries/test_execute_task_config.py::test_execute_task_config_boundaries child-task × valid-object × rejection -> tests/queries/test_execute_task_config.py::test_execute_task_config_boundaries root-with-default × original-config-isolation -> tests/queries/test_execute_task_config.py::test_execute_task_config_latest_queued root-with-default × latest-request-replaces-earlier-requests -> tests/queries/test_execute_task_config.py::test_execute_task_config_latest_queued root-with-default × invalid-request-does-not-replace-queue -> tests/queries/test_execute_task_config.py::test_execute_task_config_latest_queued root-with-default × later-manual-uses-default -> tests/queries/test_execute_task_config.py::test_execute_task_config_latest_queued root-without-default × original-config-isolation -> tests/queries/test_execute_task_config.py::test_execute_task_config_latest_queued root-without-default × latest-request-replaces-earlier-requests -> tests/queries/test_execute_task_config.py::test_execute_task_config_latest_queued root-without-default × invalid-request-does-not-replace-queue -> tests/queries/test_execute_task_config.py::test_execute_task_config_latest_queued root-without-default × later-manual-uses-default -> tests/queries/test_execute_task_config.py::test_execute_task_config_latest_queued child × original-config-isolation -> tests/queries/test_execute_task_config.py::test_execute_task_config_latest_queued child × latest-request-replaces-earlier-requests -> tests/queries/test_execute_task_config.py::test_execute_task_config_latest_queued child × invalid-request-does-not-replace-queue -> tests/queries/test_execute_task_config.py::test_execute_task_config_latest_queued child × later-manual-uses-default -> tests/queries/test_execute_task_config.py::test_execute_task_config_latest_queued finalizer × original-config-isolation -> tests/queries/test_execute_task_config.py::test_execute_task_config_latest_queued finalizer × latest-request-replaces-earlier-requests -> tests/queries/test_execute_task_config.py::test_execute_task_config_latest_queued finalizer × invalid-request-does-not-replace-queue -> tests/queries/test_execute_task_config.py::test_execute_task_config_latest_queued finalizer × later-manual-uses-default -> tests/queries/test_execute_task_config.py::test_execute_task_config_latest_queued sql-root × original-config-isolation -> tests/queries/test_execute_task_config.py::test_execute_task_config_latest_queued sql-root × latest-request-replaces-earlier-requests -> tests/queries/test_execute_task_config.py::test_execute_task_config_latest_queued sql-root × invalid-request-does-not-replace-queue -> tests/queries/test_execute_task_config.py::test_execute_task_config_latest_queued sql-root × later-manual-uses-default -> tests/queries/test_execute_task_config.py::test_execute_task_config_latest_queued SHOW TASKS / DESCRIBE TASK / GET_DDL × default persistence -> tests/queries/test_execute_task_config.py::test_execute_task_config_values Quoted identifiers / apostrophe escaping / dollar strings × parsing -> vendor/sqlparser/tests/sqlparser_snowflake.rs::execute_task_using_config Non-string arguments / mixed retry clauses × parse rejection -> vendor/sqlparser/tests/sqlparser_snowflake.rs::execute_task_using_config Plain EXECUTE TASK / existing retry forms × regression -> tests/queries/test_tasks.py::test_execute_task and test_execute_task_retry_* Default configuration / descendants / JavaScript / concurrent scheduled graphs × regression -> tests/queries/test_task_graph_config.py SQL root × plain × async -> tests/queries/test_execute_task_config.py::test_execute_task_config_submission[async-False-False] JavaScript root × plain × async -> tests/queries/test_execute_task_config.py::test_execute_task_config_submission[async-False-True] SQL root × dynamic × async -> tests/queries/test_execute_task_config.py::test_execute_task_config_submission[async-True-False] JavaScript root × dynamic × async -> tests/queries/test_execute_task_config.py::test_execute_task_config_submission[async-True-True] SQL root × plain × open transaction / rollback -> tests/queries/test_execute_task_config.py::test_execute_task_config_submission[transaction-False-False] JavaScript root × plain × open transaction / rollback -> tests/queries/test_execute_task_config.py::test_execute_task_config_submission[transaction-False-True] SQL root × dynamic × open transaction / rollback -> tests/queries/test_execute_task_config.py::test_execute_task_config_submission[transaction-True-False] JavaScript root × dynamic × open transaction / rollback -> tests/queries/test_execute_task_config.py::test_execute_task_config_submission[transaction-True-True] SQL root × plain × async in open transaction / rollback -> tests/queries/test_execute_task_config.py::test_execute_task_config_submission[async-transaction-False-False] JavaScript root × plain × async in open transaction / rollback -> tests/queries/test_execute_task_config.py::test_execute_task_config_submission[async-transaction-False-True] SQL root × dynamic × async in open transaction / rollback -> tests/queries/test_execute_task_config.py::test_execute_task_config_submission[async-transaction-True-False] JavaScript root × dynamic × async in open transaction / rollback -> tests/queries/test_execute_task_config.py::test_execute_task_config_submission[async-transaction-True-True] Dynamic run × later scheduled default isolation -> tests/queries/test_execute_task_config.py::test_execute_task_config_later_scheduled Queued dynamic override × completion-to-dispatch handoff / newer dynamic supersession -> tests/queries/test_execute_task_config.py::test_execute_task_config_queue_lifecycle[False] Queued dynamic override × drop / recreate / no automatic replacement execution / later manual default -> tests/queries/test_execute_task_config.py::test_execute_task_config_queue_lifecycle[True] Queued dynamic override × completion-to-dispatch handoff / newer plain supersession / full default isolation -> tests/queries/test_execute_task_config.py::test_execute_task_config_plain_request_supersedes_queue * LAV-3281: record post-merge verification and final test matrix Source fix: 8271c0e67. Merged latest origin/main via the repository gate (fa899539b); no conflicts or additional source changes. Final verification: - 16 new Cloud captures passed serially: 15 WIP regressions and one plain manual request superseding a pending dynamic override. - Focused config replay: 82 passed. All 82 also passed during the repository gate after the origin/main merge. - Existing manual task regressions: 14 passed. - Existing graph configuration regressions: 105 passed. - make check after the merge: passed, including vendored sqlparser tests. - .spiral/check.sh: exit 0. Format, clippy, warnings, live-PG Rust integration tests, vendored parser tests, policy guards, diff-size and ADR checks passed. Serial startup compat tests: 190 passed, 1 skipped. Telemetry: 43 passed. - The full compat stage reached 88% but did not finish within its 2370-second allowance; it remains unverified as a full suite. The state-snapshot stage was skipped because the gate's 55-minute time budget was exhausted. CI must complete those stages; neither is reported as passing. Pattern sweep: serialized manual/queued request claiming across the graph completion handoff, including plain and dynamic requests. Fixed drop/revival cleanup; fresh-row CREATE OR REPLACE and CLONE do not copy pending state. Swept SQL and server-routed manual execution, plain/dynamic inputs, async, open-transaction and async-in-transaction forms. Both entry paths now share transaction-independent task CALL execution without committing user DML. test_edit_waiver: tests/queries/test_execute_task_config.py Authority: ea172b43a investigator waiver, scheduled-isolation polling only. Co-authored-by: Przemek Denkiewicz <przemyslaw.denkiewicz@localstack.cloud> ## Deviations - The spec Notes describe SYSTEM$GET_TASK_GRAPH_CONFIG as absent, but it is already implemented in this checkout. Used the existing function unchanged; no implementation or substitute for LAV-3036 was added. - The name-only parser hint is stale: both retry forms already exist. Kept them unchanged and added the mutually exclusive USING CONFIG branch. - Captured Snowflake merges matching objects recursively and removes null keys at merged levels, while retaining nulls in copied/replaced objects, arrays, and dynamic configurations without defaults. Followed those snapshots. - The investigator authorizes the scheduled-isolation polling repair only. Applied that waiver and retained all other existing assertions and snapshots. ## Test matrix Resource index: EXECUTE TASK has the optional USING CONFIG string clause; RETRY LAST and RETRY GRAPH RUN GROUP are explicitly excluded by this issue. empty-object × quoted × default=absent × dynamic/persisted/later-manual -> tests/queries/test_execute_task_config.py::test_execute_task_config_values scalars-null-apostrophe × quoted × default=absent × dynamic/persisted/later-manual -> tests/queries/test_execute_task_config.py::test_execute_task_config_values nested-objects-arrays × quoted × default=absent × dynamic/persisted/later-manual -> tests/queries/test_execute_task_config.py::test_execute_task_config_values object-scalar-null-replacement × quoted × default=absent × dynamic/persisted/later-manual -> tests/queries/test_execute_task_config.py::test_execute_task_config_values new-objects-with-null × quoted × default=absent × dynamic/persisted/later-manual -> tests/queries/test_execute_task_config.py::test_execute_task_config_values scalar-to-object-and-array-null × quoted × default=absent × dynamic/persisted/later-manual -> tests/queries/test_execute_task_config.py::test_execute_task_config_values nested-null-deletion × quoted × default=absent × dynamic/persisted/later-manual -> tests/queries/test_execute_task_config.py::test_execute_task_config_values empty-object × dollar × default=absent × dynamic/persisted/later-manual -> tests/queries/test_execute_task_config.py::test_execute_task_config_values scalars-null-apostrophe × dollar × default=absent × dynamic/persisted/later-manual -> tests/queries/test_execute_task_config.py::test_execute_task_config_values nested-objects-arrays × dollar × default=absent × dynamic/persisted/later-manual -> tests/queries/test_execute_task_config.py::test_execute_task_config_values object-scalar-null-replacement × dollar × default=absent × dynamic/persisted/later-manual -> tests/queries/test_execute_task_config.py::test_execute_task_config_values new-objects-with-null × dollar × default=absent × dynamic/persisted/later-manual -> tests/queries/test_execute_task_config.py::test_execute_task_config_values scalar-to-object-and-array-null × dollar × default=absent × dynamic/persisted/later-manual -> tests/queries/test_execute_task_config.py::test_execute_task_config_values nested-null-deletion × dollar × default=absent × dynamic/persisted/later-manual -> tests/queries/test_execute_task_config.py::test_execute_task_config_values empty-object × quoted × default=empty × dynamic/persisted/later-manual -> tests/queries/test_execute_task_config.py::test_execute_task_config_values scalars-null-apostrophe × quoted × default=empty × dynamic/persisted/later-manual -> tests/queries/test_execute_task_config.py::test_execute_task_config_values nested-objects-arrays × quoted × default=empty × dynamic/persisted/later-manual -> tests/queries/test_execute_task_config.py::test_execute_task_config_values object-scalar-null-replacement × quoted × default=empty × dynamic/persisted/later-manual -> tests/queries/test_execute_task_config.py::test_execute_task_config_values new-objects-with-null × quoted × default=empty × dynamic/persisted/later-manual -> tests/queries/test_execute_task_config.py::test_execute_task_config_values scalar-to-object-and-array-null × quoted × default=empty × dynamic/persisted/later-manual -> tests/queries/test_execute_task_config.py::test_execute_task_config_values nested-null-deletion × quoted × default=empty × dynamic/persisted/later-manual -> tests/queries/test_execute_task_config.py::test_execute_task_config_values empty-object × dollar × default=empty × dynamic/persisted/later-manual -> tests/queries/test_execute_task_config.py::test_execute_task_config_values scalars-null-apostrophe × dollar × default=empty × dynamic/persisted/later-manual -> tests/queries/test_execute_task_config.py::test_execute_task_config_values nested-objects-arrays × dollar × default=empty × dynamic/persisted/later-manual -> tests/queries/test_execute_task_config.py::test_execute_task_config_values object-scalar-null-replacement × dollar × default=empty × dynamic/persisted/later-manual -> tests/queries/test_execute_task_config.py::test_execute_task_config_values new-objects-with-null × dollar × default=empty × dynamic/persisted/later-manual -> tests/queries/test_execute_task_config.py::test_execute_task_config_values scalar-to-object-and-array-null × dollar × default=empty × dynamic/persisted/later-manual -> tests/queries/test_execute_task_config.py::test_execute_task_config_values nested-null-deletion × dollar × default=empty × dynamic/persisted/later-manual -> tests/queries/test_execute_task_config.py::test_execute_task_config_values empty-object × quoted × default=populated × dynamic/persisted/later-manual -> tests/queries/test_execute_task_config.py::test_execute_task_config_values scalars-null-apostrophe × quoted × default=populated × dynamic/persisted/later-manual -> tests/queries/test_execute_task_config.py::test_execute_task_config_values nested-objects-arrays × quoted × default=populated × dynamic/persisted/later-manual -> tests/queries/test_execute_task_config.py::test_execute_task_config_values object-scalar-null-replacement × quoted × default=populated × dynamic/persisted/later-manual -> tests/queries/test_execute_task_config.py::test_execute_task_config_values new-objects-with-null × quoted × default=populated × dynamic/persisted/later-manual -> tests/queries/test_execute_task_config.py::test_execute_task_config_values scalar-to-object-and-array-null × quoted × default=populated × dynamic/persisted/later-manual -> tests/queries/test_execute_task_config.py::test_execute_task_config_values nested-null-deletion × quoted × default=populated × dynamic/persisted/later-manual -> tests/queries/test_execute_task_config.py::test_execute_task_config_values empty-object × dollar × default=populated × dynamic/persisted/later-manual -> tests/queries/test_execute_task_config.py::test_execute_task_config_values scalars-null-apostrophe × dollar × default=populated × dynamic/persisted/later-manual -> tests/queries/test_execute_task_config.py::test_execute_task_config_values nested-objects-arrays × dollar × default=populated × dynamic/persisted/later-manual -> tests/queries/test_execute_task_config.py::test_execute_task_config_values object-scalar-null-replacement × dollar × default=populated × dynamic/persisted/later-manual -> tests/queries/test_execute_task_config.py::test_execute_task_config_values new-objects-with-null × dollar × default=populated × dynamic/persisted/later-manual -> tests/queries/test_execute_task_config.py::test_execute_task_config_values scalar-to-object-and-array-null × dollar × default=populated × dynamic/persisted/later-manual -> tests/queries/test_execute_task_config.py::test_execute_task_config_values nested-null-deletion × dollar × default=populated × dynamic/persisted/later-manual -> tests/queries/test_execute_task_config.py::test_execute_task_config_values empty-string × quoted × rejection -> tests/queries/test_execute_task_config.py::test_execute_task_config_invalid malformed-token × quoted × rejection -> tests/queries/test_execute_task_config.py::test_execute_task_config_invalid malformed-object × quoted × rejection -> tests/queries/test_execute_task_config.py::test_execute_task_config_invalid empty-array × quoted × rejection -> tests/queries/test_execute_task_config.py::test_execute_task_config_invalid populated-array × quoted × rejection -> tests/queries/test_execute_task_config.py::test_execute_task_config_invalid numeric-root × quoted × rejection -> tests/queries/test_execute_task_config.py::test_execute_task_config_invalid string-root × quoted × rejection -> tests/queries/test_execute_task_config.py::test_execute_task_config_invalid boolean-root × quoted × rejection -> tests/queries/test_execute_task_config.py::test_execute_task_config_invalid null-root × quoted × rejection -> tests/queries/test_execute_task_config.py::test_execute_task_config_invalid empty-string × dollar × rejection -> tests/queries/test_execute_task_config.py::test_execute_task_config_invalid malformed-token × dollar × rejection -> tests/queries/test_execute_task_config.py::test_execute_task_config_invalid malformed-object × dollar × rejection -> tests/queries/test_execute_task_config.py::test_execute_task_config_invalid empty-array × dollar × rejection -> tests/queries/test_execute_task_config.py::test_execute_task_config_invalid populated-array × dollar × rejection -> tests/queries/test_execute_task_config.py::test_execute_task_config_invalid numeric-root × dollar × rejection -> tests/queries/test_execute_task_config.py::test_execute_task_config_invalid string-root × dollar × rejection -> tests/queries/test_execute_task_config.py::test_execute_task_config_invalid boolean-root × dollar × rejection -> tests/queries/test_execute_task_config.py::test_execute_task_config_invalid null-root × dollar × rejection -> tests/queries/test_execute_task_config.py::test_execute_task_config_invalid missing-task × valid-object × rejection -> tests/queries/test_execute_task_config.py::test_execute_task_config_boundaries child-task × valid-object × rejection -> tests/queries/test_execute_task_config.py::test_execute_task_config_boundaries root-with-default × original-config-isolation -> tests/queries/test_execute_task_config.py::test_execute_task_config_latest_queued root-with-default × latest-request-replaces-earlier-requests -> tests/queries/test_execute_task_config.py::test_execute_task_config_latest_queued root-with-default × invalid-request-does-not-replace-queue -> tests/queries/test_execute_task_config.py::test_execute_task_config_latest_queued root-with-default × later-manual-uses-default -> tests/queries/test_execute_task_config.py::test_execute_task_config_latest_queued root-without-default × original-config-isolation -> tests/queries/test_execute_task_config.py::test_execute_task_config_latest_queued root-without-default × latest-request-replaces-earlier-requests -> tests/queries/test_execute_task_config.py::test_execute_task_config_latest_queued root-without-default × invalid-request-does-not-replace-queue -> tests/queries/test_execute_task_config.py::test_execute_task_config_latest_queued root-without-default × later-manual-uses-default -> tests/queries/test_execute_task_config.py::test_execute_task_config_latest_queued child × original-config-isolation -> tests/queries/test_execute_task_config.py::test_execute_task_config_latest_queued child × latest-request-replaces-earlier-requests -> tests/queries/test_execute_task_config.py::test_execute_task_config_latest_queued child × invalid-request-does-not-replace-queue -> tests/queries/test_execute_task_config.py::test_execute_task_config_latest_queued child × later-manual-uses-default -> tests/queries/test_execute_task_config.py::test_execute_task_config_latest_queued finalizer × original-config-isolation -> tests/queries/test_execute_task_config.py::test_execute_task_config_latest_queued finalizer × latest-request-replaces-earlier-requests -> tests/queries/test_execute_task_config.py::test_execute_task_config_latest_queued finalizer × invalid-request-does-not-replace-queue -> tests/queries/test_execute_task_config.py::test_execute_task_config_latest_queued finalizer × later-manual-uses-default -> tests/queries/test_execute_task_config.py::test_execute_task_config_latest_queued sql-root × original-config-isolation -> tests/queries/test_execute_task_config.py::test_execute_task_config_latest_queued sql-root × latest-request-replaces-earlier-requests -> tests/queries/test_execute_task_config.py::test_execute_task_config_latest_queued sql-root × invalid-request-does-not-replace-queue -> tests/queries/test_execute_task_config.py::test_execute_task_config_latest_queued sql-root × later-manual-uses-default -> tests/queries/test_execute_task_config.py::test_execute_task_config_latest_queued SHOW TASKS / DESCRIBE TASK / GET_DDL × default persistence -> tests/queries/test_execute_task_config.py::test_execute_task_config_values Quoted identifiers / apostrophe escaping / dollar strings × parsing -> vendor/sqlparser/tests/sqlparser_snowflake.rs::execute_task_using_config Non-string arguments / mixed retry clauses × parse rejection -> vendor/sqlparser/tests/sqlparser_snowflake.rs::execute_task_using_config Plain EXECUTE TASK / existing retry forms × regression -> tests/queries/test_tasks.py::test_execute_task and test_execute_task_retry_* Default configuration / descendants / JavaScript / concurrent scheduled graphs × regression -> tests/queries/test_task_graph_config.py SQL root × plain × async -> tests/queries/test_execute_task_config.py::test_execute_task_config_submission[async-False-False] JavaScript root × plain × async -> tests/queries/test_execute_task_config.py::test_execute_task_config_submission[async-False-True] SQL root × dynamic × async -> tests/queries/test_execute_task_config.py::test_execute_task_config_submission[async-True-False] JavaScript root × dynamic × async -> tests/queries/test_execute_task_config.py::test_execute_task_config_submission[async-True-True] SQL root × plain × open transaction / rollback -> tests/queries/test_execute_task_config.py::test_execute_task_config_submission[transaction-False-False] JavaScript root × plain × open transaction / rollback -> tests/queries/test_execute_task_config.py::test_execute_task_config_submission[transaction-False-True] SQL root × dynamic × open transaction / rollback -> tests/queries/test_execute_task_config.py::test_execute_task_config_submission[transaction-True-False] JavaScript root × dynamic × open transaction / rollback -> tests/queries/test_execute_task_config.py::test_execute_task_config_submission[transaction-True-True] SQL root × plain × async in open transaction / rollback -> tests/queries/test_execute_task_config.py::test_execute_task_config_submission[async-transaction-False-False] JavaScript root × plain × async in open transaction / rollback -> tests/queries/test_execute_task_config.py::test_execute_task_config_submission[async-transaction-False-True] SQL root × dynamic × async in open transaction / rollback -> tests/queries/test_execute_task_config.py::test_execute_task_config_submission[async-transaction-True-False] JavaScript root × dynamic × async in open transaction / rollback -> tests/queries/test_execute_task_config.py::test_execute_task_config_submission[async-transaction-True-True] Dynamic run × later scheduled default isolation -> tests/queries/test_execute_task_config.py::test_execute_task_config_later_scheduled Queued dynamic override × completion-to-dispatch handoff / newer dynamic supersession -> tests/queries/test_execute_task_config.py::test_execute_task_config_queue_lifecycle[False] Queued dynamic override × drop / recreate / no automatic replacement execution / later manual default -> tests/queries/test_execute_task_config.py::test_execute_task_config_queue_lifecycle[True] Queued dynamic override × completion-to-dispatch handoff / newer plain supersession / full default isolation -> tests/queries/test_execute_task_config.py::test_execute_task_config_plain_request_supersedes_queue * Apply cargo fmt / ruff format --------- Co-authored-by: spiral <spiral@localhost> Co-authored-by: Przemek Denkiewicz <przemyslaw.denkiewicz@localstack.cloud> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
1 parent a0a2b3f commit 665dc11

3 files changed

Lines changed: 63 additions & 3 deletions

File tree

‎src/ast/mod.rs‎

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5421,6 +5421,8 @@ pub enum Statement {
54215421
retry_last: bool,
54225422
/// Retry a specific graph run group.
54235423
retry_graph_run_group: Option<String>,
5424+
/// Per-run JSON configuration override.
5425+
config: Option<String>,
54245426
},
54255427
/// ```sql
54265428
/// EXECUTE ALERT <name>
@@ -9132,12 +9134,19 @@ impl fmt::Display for Statement {
91329134
}
91339135
write!(f, " {name} {operation}")
91349136
}
9135-
Statement::ExecuteTask { name, retry_last, retry_graph_run_group } => {
9137+
Statement::ExecuteTask {
9138+
name,
9139+
retry_last,
9140+
retry_graph_run_group,
9141+
config,
9142+
} => {
91369143
write!(f, "EXECUTE TASK {name}")?;
91379144
if *retry_last {
91389145
write!(f, " RETRY LAST")?;
91399146
} else if let Some(group_id) = retry_graph_run_group {
91409147
write!(f, " RETRY GRAPH RUN GROUP '{}'", group_id.replace('\'', "''"))?;
9148+
} else if let Some(config) = config {
9149+
write!(f, " USING CONFIG = '{}'", config.replace('\'', "''"))?;
91419150
}
91429151
Ok(())
91439152
}

‎src/parser/mod.rs‎

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22847,6 +22847,7 @@ impl<'a> Parser<'a> {
2284722847
let name = self.parse_object_name(false)?;
2284822848
let mut retry_last = false;
2284922849
let mut retry_graph_run_group = None;
22850+
let mut config = None;
2285022851
if self.parse_keyword(Keyword::RETRY) {
2285122852
if self.parse_keyword(Keyword::LAST) {
2285222853
retry_last = true;
@@ -22858,8 +22859,22 @@ impl<'a> Parser<'a> {
2285822859
_ => return self.expected("single-quoted graph run group ID", token),
2285922860
});
2286022861
}
22862+
} else if self.parse_keyword(Keyword::USING) {
22863+
self.expect_keyword(Keyword::CONFIG)?;
22864+
self.expect_token(&Token::Eq)?;
22865+
let token = self.next_token();
22866+
config = Some(match token.token {
22867+
Token::SingleQuotedString(value) => value,
22868+
Token::DollarQuotedString(value) => value.value,
22869+
_ => return self.expected("configuration string", token),
22870+
});
2286122871
}
22862-
return Ok(Statement::ExecuteTask { name, retry_last, retry_graph_run_group });
22872+
return Ok(Statement::ExecuteTask {
22873+
name,
22874+
retry_last,
22875+
retry_graph_run_group,
22876+
config,
22877+
});
2286322878
}
2286422879

2286522880
// Snowflake `EXECUTE ALERT <name>` — same early-dispatch reasoning as

‎tests/sqlparser_snowflake.rs‎

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8359,10 +8359,16 @@ fn test_alter_task_if_exists_suspend() {
83598359
fn test_execute_task() {
83608360
let sql = "EXECUTE TASK foo";
83618361
match snowflake().verified_stmt(sql) {
8362-
Statement::ExecuteTask { name, retry_last, retry_graph_run_group } => {
8362+
Statement::ExecuteTask {
8363+
name,
8364+
retry_last,
8365+
retry_graph_run_group,
8366+
config,
8367+
} => {
83638368
assert_eq!("foo", name.to_string());
83648369
assert!(!retry_last);
83658370
assert_eq!(retry_graph_run_group, None);
8371+
assert_eq!(config, None);
83668372
}
83678373
_ => unreachable!(),
83688374
}
@@ -11319,3 +11325,33 @@ fn host_stack_probe_stops_recursion_with_a_parse_error() {
1131911325
None
1132011326
);
1132111327
}
11328+
11329+
#[test]
11330+
fn execute_task_using_config() {
11331+
for sql in [
11332+
r#"EXECUTE TASK db.schema.task USING CONFIG = '{"x":1}'"#,
11333+
r#"EXECUTE TASK "using"."config" USING CONFIG = '{}'"#,
11334+
r#"EXECUTE TASK task USING CONFIG = '{"text":"it''s fine"}'"#,
11335+
] {
11336+
snowflake().verified_stmt(sql);
11337+
}
11338+
snowflake().one_statement_parses_to(
11339+
r#"EXECUTE TASK task USING CONFIG = $${"x":1}$$"#,
11340+
r#"EXECUTE TASK task USING CONFIG = '{"x":1}'"#,
11341+
);
11342+
match snowflake().verified_stmt(r#"EXECUTE TASK task USING CONFIG = '{"x":1}'"#) {
11343+
Statement::ExecuteTask { config, .. } => {
11344+
assert_eq!(config.as_deref(), Some(r#"{"x":1}"#));
11345+
}
11346+
statement => panic!("unexpected statement: {statement}"),
11347+
}
11348+
for sql in [
11349+
"EXECUTE TASK task USING CONFIG = 1",
11350+
"EXECUTE TASK task USING CONFIG = NULL",
11351+
"EXECUTE TASK task USING CONFIG = config",
11352+
"EXECUTE TASK task USING CONFIG = '{}' RETRY LAST",
11353+
"EXECUTE TASK task RETRY LAST USING CONFIG = '{}'",
11354+
] {
11355+
assert!(Parser::parse_sql(&SnowflakeDialect {}, sql).is_err(), "{sql}");
11356+
}
11357+
}

0 commit comments

Comments
 (0)