Skip to content

Commit 9714238

Browse files
localstack-spiral[bot]spiralhovaesco
authored
LAV-4375: Support warehouse, schedule and comment overrides on CREATE ALERT CLONE (#4649)
* LAV-4375: investigator: serialize Cloud capture, PG setup, and emulator replay Dispatch 1 stopped with an explicit bootstrap bail-out, not exhaustion. The recovered implementer-20261014T025235.116301477Z.jsonl confirms all implementation and new test/snapshot files were reverted in item_59 to 0c990afbe. Investigation started clean and identical to fetched origin/main b6237a7cd, with no task commits or file diff. The driver advanced the base since the report. LAV-4355, LAV-4370 and LAV-4371 are merged as d8449af57, 9932105f5 and b13423b43. The task remains: vendor/sqlparser/src/parser/mod.rs:6500 returns CreateAlertClone immediately after the source name; no override fields are parsed. Mechanical cause: Cloud tagged capture item_45 ran 2026-10-14 03:12:11.914406652Z through 03:13:35.991442731Z. Emulator replay item_47 started at 03:12:49.05547603Z, before capture exited. Item_57 prints replay server bootstrap at 03:13:19.182768Z skipping SNOWFLAKE because account_info row is missing, then at 03:13:24.323404Z duplicate key violates pg_namespace_nspname_index, Key (nspname)=(SNOWFLAKE.INFORMATION_SCHEMA) already exists. The tagged Cloud capture passed 6 cases, 64 deselected. The final report says all 70 Cloud cases passed, but their full aggregate result is not visible in the recovered transcript; do not assume that captures survive the reset. Makefile:323 invokes start and :545 launches an emulator even for Cloud capture. scripts/start.py:649 invokes --bootstrap-pg-only --reinstall-extension; crates/server/src/pg_supervisor.rs:875 drops/reinstalls the extension and :917 refreshes all account databases. Different HTTP ports do not isolate PG provisioning. Existing remote notes b001a5259 (LAV-4348), 36b911ad3 (LAV-4195), and 0ec3e1122 (LAV-3123) independently document this mechanism and successful serial recovery. Independent verification on this worker, serial and without code/test/snapshot changes: make test-compat PYTEST_XDIST_ARGS="" PYTEST_PATH=tests/queries/test_alert_clone.py passed 13 in 6.81s, with account bootstrap and all assertions reached. Then make _run-compat PORT=24375 PYTEST_XDIST_ARGS="" PYTEST_PATH="tests/queries/test_alert_clone.py tests/queries/test_alert_schedules.py tests/queries/test_alert_schedule_errors.py" PYTEST_ARGS="-v --tb=short" passed 145 in 157.42s, including warm bootstrap and scheduler regressions. Logs: /tmp/lav4375-investigator-clone.log and /tmp/lav4375-investigator-regressions.log. No service, credential, provisioning permission or missing prerequisite blocker remains. These results establish recovery, not implementation of overrides. Next implementer: await every Cloud capture, make test-compat, make start/restart, extension install and server startup command to completion before starting another; do not overlap any of these against shared PostgreSQL. Also await make check and do not edit sources while validation builds are running. Use PYTEST_XDIST_ARGS="" for Cloud captures and initial diagnostic replay. Recreate the reverted scoped implementation and new override matrix; capture NEW snapshots with TEST_TARGET=SNOWFLAKE_CLOUD and --snapshot-update, finish capture before replay, then verify override and scheduler cases plus the three prerequisite files. Reuse the shipped base-clone policy and shared schedule validation. No database deletion, bootstrap bypass, existing snapshot rewrite or broader clone property ownership is authorized or needed. Keep warehouse-catalog rejection cells with the reserved owner and leave .spiral/check.sh to the driver. Co-authored-by: Przemek Denkiewicz <przemyslaw.denkiewicz@localstack.cloud> * LAV-4375: support core property overrides on alert clones Parse WAREHOUSE, SCHEDULE and COMMENT after the clone source. Preserve omission versus explicit NULL and delegate clone creation, schedule validation, body storage, reset policy and scheduling to the existing alert machinery. Reject invalid scalar property values with Cloud-pinned parameter errors. Capture 96 new Cloud snapshots for CREATE and OR REPLACE, including independent readbacks, omission/NULL, literal and identifier forms, interval/calendar boundaries, error atomicity and actual scheduled execution. Local scheduler replay uses the existing virtual clock and the same Cloud result snapshots. No existing tests or recorded values changed; warehouse catalog validation remains owned by LAV-4377 (reserved_remainder). Verification: - make check: passed (clippy, warning checks, formatting). - Focused alert replay: 300 passed across clone overrides, base cloning, schedules, schedule errors and literal regressions. - Shared task schedule/config error regressions: 2 passed (each pins multiple accepted/rejected values). - Cloud captures, PG setup and emulator replay ran serially. - Before implementation, replay failed on unexpected WAREHOUSE after CLONE. Co-authored-by: Przemek Denkiewicz <przemyslaw.denkiewicz@localstack.cloud> ## Deviations Cloud verbatim schedule failures exposed compilation headers that the previous schedule tests normalized away. Extended the existing shared per-code header exceptions for 091404, 091440 and 091851; no error strategy changed. Both alert and task schedule regressions pass. Cloud accepts explicit SCHEDULE=NULL on scheduled clones and a schedule on unscheduled clones, on both CREATE and OR REPLACE. Implemented the captured copy/reset behavior rather than imposing conversion restrictions from ALTER. ## Test matrix CREATE CLONE x omitted core overrides x success + property readback + source independence -> tests/queries/test_alert_clone_overrides.py::test_alert_clone_core_overrides CREATE CLONE x WAREHOUSE=COMPUTE_WH x success + property readback + source independence -> tests/queries/test_alert_clone_overrides.py::test_alert_clone_core_overrides CREATE CLONE x WAREHOUSE=compute_wh x success + property readback + source independence -> tests/queries/test_alert_clone_overrides.py::test_alert_clone_core_overrides CREATE CLONE x WAREHOUSE="COMPUTE_WH" x success + property readback + source independence -> tests/queries/test_alert_clone_overrides.py::test_alert_clone_core_overrides CREATE CLONE x WAREHOUSE='COMPUTE_WH' x success + property readback + source independence -> tests/queries/test_alert_clone_overrides.py::test_alert_clone_core_overrides CREATE CLONE x WAREHOUSE=$$COMPUTE_WH$$ x success + property readback + source independence -> tests/queries/test_alert_clone_overrides.py::test_alert_clone_core_overrides CREATE CLONE x WAREHOUSE=NULL x error 001008 + target atomicity + source independence -> tests/queries/test_alert_clone_overrides.py::test_alert_clone_core_overrides CREATE CLONE x WAREHOUSE=42 x error 001008 + target atomicity + source independence -> tests/queries/test_alert_clone_overrides.py::test_alert_clone_core_overrides CREATE CLONE x WAREHOUSE=-42 x error 001008 + target atomicity + source independence -> tests/queries/test_alert_clone_overrides.py::test_alert_clone_core_overrides CREATE CLONE x WAREHOUSE=TRUE x error 001008 + target atomicity + source independence -> tests/queries/test_alert_clone_overrides.py::test_alert_clone_core_overrides CREATE CLONE x SCHEDULE='2 MINUTE' x success + property readback + source independence -> tests/queries/test_alert_clone_overrides.py::test_alert_clone_core_overrides CREATE CLONE x SCHEDULE=$$60 S$$ x success + property readback + source independence -> tests/queries/test_alert_clone_overrides.py::test_alert_clone_core_overrides CREATE CLONE x SCHEDULE='10 SECOND' x success + property readback + source independence -> tests/queries/test_alert_clone_overrides.py::test_alert_clone_core_overrides CREATE CLONE x SCHEDULE='11520 MINUTE' x success + property readback + source independence -> tests/queries/test_alert_clone_overrides.py::test_alert_clone_core_overrides CREATE CLONE x SCHEDULE='USING CRON 0 0 L * * UTC' x success + property readback + source independence -> tests/queries/test_alert_clone_overrides.py::test_alert_clone_core_overrides CREATE CLONE x SCHEDULE='USING CRON 0 0 * JAN MON America/New_York' x success + property readback + source independence -> tests/queries/test_alert_clone_overrides.py::test_alert_clone_core_overrides CREATE CLONE x SCHEDULE=NULL x success + property readback + source independence -> tests/queries/test_alert_clone_overrides.py::test_alert_clone_core_overrides CREATE CLONE x SCHEDULE='' x error 091086 + target atomicity + source independence -> tests/queries/test_alert_clone_overrides.py::test_alert_clone_core_overrides CREATE CLONE x SCHEDULE='9 SECOND' x error 091851 + target atomicity + source independence -> tests/queries/test_alert_clone_overrides.py::test_alert_clone_core_overrides CREATE CLONE x SCHEDULE='11521 MINUTE' x error 091440 + target atomicity + source independence -> tests/queries/test_alert_clone_overrides.py::test_alert_clone_core_overrides CREATE CLONE x SCHEDULE='0 MINUTE' x error 091086 + target atomicity + source independence -> tests/queries/test_alert_clone_overrides.py::test_alert_clone_core_overrides CREATE CLONE x SCHEDULE='1.5 MINUTE' x error 091086 + target atomicity + source independence -> tests/queries/test_alert_clone_overrides.py::test_alert_clone_core_overrides CREATE CLONE x SCHEDULE='USING CRON 60 0 * * * UTC' x error 091086 + target atomicity + source independence -> tests/queries/test_alert_clone_overrides.py::test_alert_clone_core_overrides CREATE CLONE x SCHEDULE='USING CRON 0 0 * * * NOT_A_TIMEZONE' x error 091404 + target atomicity + source independence -> tests/queries/test_alert_clone_overrides.py::test_alert_clone_core_overrides CREATE CLONE x SCHEDULE=42 x error 001008 + target atomicity + source independence -> tests/queries/test_alert_clone_overrides.py::test_alert_clone_core_overrides CREATE CLONE x SCHEDULE=TRUE x error 001008 + target atomicity + source independence -> tests/queries/test_alert_clone_overrides.py::test_alert_clone_core_overrides CREATE CLONE x SCHEDULE=invalid_schedule x error 091086 + target atomicity + source independence -> tests/queries/test_alert_clone_overrides.py::test_alert_clone_core_overrides CREATE CLONE x SCHEDULE="2 MINUTE" x success + property readback + source independence -> tests/queries/test_alert_clone_overrides.py::test_alert_clone_core_overrides CREATE CLONE x COMMENT='clone comment' x success + property readback + source independence -> tests/queries/test_alert_clone_overrides.py::test_alert_clone_core_overrides CREATE CLONE x COMMENT=bare_comment x success + property readback + source independence -> tests/queries/test_alert_clone_overrides.py::test_alert_clone_core_overrides CREATE CLONE x COMMENT='' x success + property readback + source independence -> tests/queries/test_alert_clone_overrides.py::test_alert_clone_core_overrides CREATE CLONE x COMMENT='it''s a clone' x success + property readback + source independence -> tests/queries/test_alert_clone_overrides.py::test_alert_clone_core_overrides CREATE CLONE x COMMENT=$$line one\nWAREHOUSE='fake' SCHEDULE='invalid' IF (EXISTS (SELECT 0)) THEN SELECT 0$$ x success + property readback + source independence -> tests/queries/test_alert_clone_overrides.py::test_alert_clone_core_overrides CREATE CLONE x COMMENT='雪 ❄' x success + property readback + source independence -> tests/queries/test_alert_clone_overrides.py::test_alert_clone_core_overrides CREATE CLONE x COMMENT=NULL x success + property readback + source independence -> tests/queries/test_alert_clone_overrides.py::test_alert_clone_core_overrides CREATE CLONE x COMMENT=42 x error 001008 + target atomicity + source independence -> tests/queries/test_alert_clone_overrides.py::test_alert_clone_core_overrides CREATE CLONE x COMMENT=TRUE x error 001008 + target atomicity + source independence -> tests/queries/test_alert_clone_overrides.py::test_alert_clone_core_overrides CREATE CLONE x COMMENT="clone comment" x success + property readback + source independence -> tests/queries/test_alert_clone_overrides.py::test_alert_clone_core_overrides CREATE CLONE x WAREHOUSE=COMPUTE_WH SCHEDULE='2 MINUTE' COMMENT='combined' x success + property readback + source independence -> tests/queries/test_alert_clone_overrides.py::test_alert_clone_core_overrides CREATE CLONE x COMMENT='combined reversed' SCHEDULE=$$60 S$$ WAREHOUSE='COMPUTE_WH' x success + property readback + source independence -> tests/queries/test_alert_clone_overrides.py::test_alert_clone_core_overrides CREATE CLONE x COMMENT='changed' SCHEDULE='0 MINUTE' x error 091086 + target atomicity + source independence -> tests/queries/test_alert_clone_overrides.py::test_alert_clone_core_overrides CREATE CLONE x source schedule absent x omitted -> tests/queries/test_alert_clone_overrides.py::test_alert_clone_schedule_presence CREATE CLONE x source schedule absent x SCHEDULE=NULL -> tests/queries/test_alert_clone_overrides.py::test_alert_clone_schedule_presence CREATE CLONE x source schedule absent x SCHEDULE='2 MINUTE' -> tests/queries/test_alert_clone_overrides.py::test_alert_clone_schedule_presence CREATE CLONE x source schedule present x omitted -> tests/queries/test_alert_clone_overrides.py::test_alert_clone_schedule_presence CREATE CLONE x source schedule present x SCHEDULE=NULL -> tests/queries/test_alert_clone_overrides.py::test_alert_clone_schedule_presence CREATE CLONE x source schedule present x SCHEDULE='2 MINUTE' -> tests/queries/test_alert_clone_overrides.py::test_alert_clone_schedule_presence CREATE CLONE x overridden 60 S x before overridden interval -> tests/queries/test_alert_clone_overrides.py::test_alert_clone_override_scheduler CREATE CLONE x overridden 60 S x scheduled insertion -> tests/queries/test_alert_clone_overrides.py::test_alert_clone_override_scheduler CREATE CLONE x overridden 60 S x unchanged suspended source -> tests/queries/test_alert_clone_overrides.py::test_alert_clone_override_scheduler OR REPLACE CLONE x omitted core overrides x success + property readback + source independence -> tests/queries/test_alert_clone_overrides.py::test_alert_clone_core_overrides OR REPLACE CLONE x WAREHOUSE=COMPUTE_WH x success + property readback + source independence -> tests/queries/test_alert_clone_overrides.py::test_alert_clone_core_overrides OR REPLACE CLONE x WAREHOUSE=compute_wh x success + property readback + source independence -> tests/queries/test_alert_clone_overrides.py::test_alert_clone_core_overrides OR REPLACE CLONE x WAREHOUSE="COMPUTE_WH" x success + property readback + source independence -> tests/queries/test_alert_clone_overrides.py::test_alert_clone_core_overrides OR REPLACE CLONE x WAREHOUSE='COMPUTE_WH' x success + property readback + source independence -> tests/queries/test_alert_clone_overrides.py::test_alert_clone_core_overrides OR REPLACE CLONE x WAREHOUSE=$$COMPUTE_WH$$ x success + property readback + source independence -> tests/queries/test_alert_clone_overrides.py::test_alert_clone_core_overrides OR REPLACE CLONE x WAREHOUSE=NULL x error 001008 + target atomicity + source independence -> tests/queries/test_alert_clone_overrides.py::test_alert_clone_core_overrides OR REPLACE CLONE x WAREHOUSE=42 x error 001008 + target atomicity + source independence -> tests/queries/test_alert_clone_overrides.py::test_alert_clone_core_overrides OR REPLACE CLONE x WAREHOUSE=-42 x error 001008 + target atomicity + source independence -> tests/queries/test_alert_clone_overrides.py::test_alert_clone_core_overrides OR REPLACE CLONE x WAREHOUSE=TRUE x error 001008 + target atomicity + source independence -> tests/queries/test_alert_clone_overrides.py::test_alert_clone_core_overrides OR REPLACE CLONE x SCHEDULE='2 MINUTE' x success + property readback + source independence -> tests/queries/test_alert_clone_overrides.py::test_alert_clone_core_overrides OR REPLACE CLONE x SCHEDULE=$$60 S$$ x success + property readback + source independence -> tests/queries/test_alert_clone_overrides.py::test_alert_clone_core_overrides OR REPLACE CLONE x SCHEDULE='10 SECOND' x success + property readback + source independence -> tests/queries/test_alert_clone_overrides.py::test_alert_clone_core_overrides OR REPLACE CLONE x SCHEDULE='11520 MINUTE' x success + property readback + source independence -> tests/queries/test_alert_clone_overrides.py::test_alert_clone_core_overrides OR REPLACE CLONE x SCHEDULE='USING CRON 0 0 L * * UTC' x success + property readback + source independence -> tests/queries/test_alert_clone_overrides.py::test_alert_clone_core_overrides OR REPLACE CLONE x SCHEDULE='USING CRON 0 0 * JAN MON America/New_York' x success + property readback + source independence -> tests/queries/test_alert_clone_overrides.py::test_alert_clone_core_overrides OR REPLACE CLONE x SCHEDULE=NULL x success + property readback + source independence -> tests/queries/test_alert_clone_overrides.py::test_alert_clone_core_overrides OR REPLACE CLONE x SCHEDULE='' x error 091086 + target atomicity + source independence -> tests/queries/test_alert_clone_overrides.py::test_alert_clone_core_overrides OR REPLACE CLONE x SCHEDULE='9 SECOND' x error 091851 + target atomicity + source independence -> tests/queries/test_alert_clone_overrides.py::test_alert_clone_core_overrides OR REPLACE CLONE x SCHEDULE='11521 MINUTE' x error 091440 + target atomicity + source independence -> tests/queries/test_alert_clone_overrides.py::test_alert_clone_core_overrides OR REPLACE CLONE x SCHEDULE='0 MINUTE' x error 091086 + target atomicity + source independence -> tests/queries/test_alert_clone_overrides.py::test_alert_clone_core_overrides OR REPLACE CLONE x SCHEDULE='1.5 MINUTE' x error 091086 + target atomicity + source independence -> tests/queries/test_alert_clone_overrides.py::test_alert_clone_core_overrides OR REPLACE CLONE x SCHEDULE='USING CRON 60 0 * * * UTC' x error 091086 + target atomicity + source independence -> tests/queries/test_alert_clone_overrides.py::test_alert_clone_core_overrides OR REPLACE CLONE x SCHEDULE='USING CRON 0 0 * * * NOT_A_TIMEZONE' x error 091404 + target atomicity + source independence -> tests/queries/test_alert_clone_overrides.py::test_alert_clone_core_overrides OR REPLACE CLONE x SCHEDULE=42 x error 001008 + target atomicity + source independence -> tests/queries/test_alert_clone_overrides.py::test_alert_clone_core_overrides OR REPLACE CLONE x SCHEDULE=TRUE x error 001008 + target atomicity + source independence -> tests/queries/test_alert_clone_overrides.py::test_alert_clone_core_overrides OR REPLACE CLONE x SCHEDULE=invalid_schedule x error 091086 + target atomicity + source independence -> tests/queries/test_alert_clone_overrides.py::test_alert_clone_core_overrides OR REPLACE CLONE x SCHEDULE="2 MINUTE" x success + property readback + source independence -> tests/queries/test_alert_clone_overrides.py::test_alert_clone_core_overrides OR REPLACE CLONE x COMMENT='clone comment' x success + property readback + source independence -> tests/queries/test_alert_clone_overrides.py::test_alert_clone_core_overrides OR REPLACE CLONE x COMMENT=bare_comment x success + property readback + source independence -> tests/queries/test_alert_clone_overrides.py::test_alert_clone_core_overrides OR REPLACE CLONE x COMMENT='' x success + property readback + source independence -> tests/queries/test_alert_clone_overrides.py::test_alert_clone_core_overrides OR REPLACE CLONE x COMMENT='it''s a clone' x success + property readback + source independence -> tests/queries/test_alert_clone_overrides.py::test_alert_clone_core_overrides OR REPLACE CLONE x COMMENT=$$line one\nWAREHOUSE='fake' SCHEDULE='invalid' IF (EXISTS (SELECT 0)) THEN SELECT 0$$ x success + property readback + source independence -> tests/queries/test_alert_clone_overrides.py::test_alert_clone_core_overrides OR REPLACE CLONE x COMMENT='雪 ❄' x success + property readback + source independence -> tests/queries/test_alert_clone_overrides.py::test_alert_clone_core_overrides OR REPLACE CLONE x COMMENT=NULL x success + property readback + source independence -> tests/queries/test_alert_clone_overrides.py::test_alert_clone_core_overrides OR REPLACE CLONE x COMMENT=42 x error 001008 + target atomicity + source independence -> tests/queries/test_alert_clone_overrides.py::test_alert_clone_core_overrides OR REPLACE CLONE x COMMENT=TRUE x error 001008 + target atomicity + source independence -> tests/queries/test_alert_clone_overrides.py::test_alert_clone_core_overrides OR REPLACE CLONE x COMMENT="clone comment" x success + property readback + source independence -> tests/queries/test_alert_clone_overrides.py::test_alert_clone_core_overrides OR REPLACE CLONE x WAREHOUSE=COMPUTE_WH SCHEDULE='2 MINUTE' COMMENT='combined' x success + property readback + source independence -> tests/queries/test_alert_clone_overrides.py::test_alert_clone_core_overrides OR REPLACE CLONE x COMMENT='combined reversed' SCHEDULE=$$60 S$$ WAREHOUSE='COMPUTE_WH' x success + property readback + source independence -> tests/queries/test_alert_clone_overrides.py::test_alert_clone_core_overrides OR REPLACE CLONE x COMMENT='changed' SCHEDULE='0 MINUTE' x error 091086 + target atomicity + source independence -> tests/queries/test_alert_clone_overrides.py::test_alert_clone_core_overrides OR REPLACE CLONE x source schedule absent x omitted -> tests/queries/test_alert_clone_overrides.py::test_alert_clone_schedule_presence OR REPLACE CLONE x source schedule absent x SCHEDULE=NULL -> tests/queries/test_alert_clone_overrides.py::test_alert_clone_schedule_presence OR REPLACE CLONE x source schedule absent x SCHEDULE='2 MINUTE' -> tests/queries/test_alert_clone_overrides.py::test_alert_clone_schedule_presence OR REPLACE CLONE x source schedule present x omitted -> tests/queries/test_alert_clone_overrides.py::test_alert_clone_schedule_presence OR REPLACE CLONE x source schedule present x SCHEDULE=NULL -> tests/queries/test_alert_clone_overrides.py::test_alert_clone_schedule_presence OR REPLACE CLONE x source schedule present x SCHEDULE='2 MINUTE' -> tests/queries/test_alert_clone_overrides.py::test_alert_clone_schedule_presence OR REPLACE CLONE x overridden 60 S x before overridden interval -> tests/queries/test_alert_clone_overrides.py::test_alert_clone_override_scheduler OR REPLACE CLONE x overridden 60 S x scheduled insertion -> tests/queries/test_alert_clone_overrides.py::test_alert_clone_override_scheduler OR REPLACE CLONE x overridden 60 S x unchanged suspended source -> tests/queries/test_alert_clone_overrides.py::test_alert_clone_override_scheduler Base-clone lifecycle/body/tag/reset/independence regression -> tests/queries/test_alert_clone.py Shared accepted schedule literals/calendar forms and scheduler regression -> tests/queries/test_alert_schedules.py Shared schedule invalid forms and failure atomicity regression -> tests/queries/test_alert_schedule_errors.py Shared quoted literal and keyword-boundary regression -> tests/queries/test_alert_literals.py Shared task schedule interval/timezone/config errors -> tests/queries/test_tasks.py::test_alter_task_schedule_config_value_spaces Shared task schedule extended invalid forms -> tests/queries/test_tasks.py::test_alter_task_schedule_config_extended_value_spaces Warehouse catalog rejections -> uncovered: reserved to LAV-4377 (reserved_remainder), excluded by this issue CONFIG/RUNBOOK/failure-threshold clone overrides -> uncovered: owned by LAV-4376, excluded by this issue Templates -> uncovered: deferred to LAV-4377, excluded by this issue * LAV-4375: split core override snapshots below the per-file diff limit The main merge combined this issue's new core-override captures with LAV-4376's existing property-override captures. Serialization churn pushed that snapshot's diff to 10037 lines, exceeding the driver's 10000-line gate. Move only this issue's added tests into test_alert_clone_core_overrides.py. Restore main's test_alert_clone_overrides.py and its snapshot exactly, keeping all sibling tests and original recorded values intact. Capture the relocated 96 tests serially against Snowflake Cloud; the new snapshot is 7975 lines. All 96 recorded-content payloads equal the previously approved captures; only snapshot scope paths and recorded-date metadata changed. No production behavior, assertions, or test cases changed. Verification (serial provisioning/capture/replay): - Cloud --snapshot-update: 96 passed, including both scheduler cases. - make check: passed (formatting, clippy, warnings, vendored parser tests). - Focused make test-compat: 519 passed across core overrides, sibling overrides, base clones, accepted schedules, schedule errors, literals and alerts; every selected test reached its assertions. - Existing main test/snapshot pair matches origin/main byte-for-byte. - git diff --cached --check passed; every staged per-file diff relative to origin/main is below 10000 lines (largest: 7975). Co-authored-by: Przemek Denkiewicz <przemyslaw.denkiewicz@localstack.cloud> ## Deviations Cloud verbatim schedule failures exposed compilation headers that the previous schedule tests normalized away. The implementation extends the established shared header exceptions for 091404, 091440 and 091851 without changing error strategy; prior verification covered task schedule regressions as well. Cloud accepts explicit SCHEDULE=NULL on scheduled clones and a schedule on unscheduled clones, on both CREATE and OR REPLACE. The implementation follows those captured values rather than imposing ALTER conversion restrictions. The prior core-override tests shared the sibling override module. Relocated only this issue's new tests to test_alert_clone_core_overrides.py and captured them anew on Cloud to satisfy the per-file gate without editing recorded values or altering main's existing tests. ## Test matrix CREATE CLONE x omitted core overrides x success + property readback + source independence -> tests/queries/test_alert_clone_core_overrides.py::test_alert_clone_core_overrides CREATE CLONE x WAREHOUSE=COMPUTE_WH x success + property readback + source independence -> tests/queries/test_alert_clone_core_overrides.py::test_alert_clone_core_overrides CREATE CLONE x WAREHOUSE=compute_wh x success + property readback + source independence -> tests/queries/test_alert_clone_core_overrides.py::test_alert_clone_core_overrides CREATE CLONE x WAREHOUSE="COMPUTE_WH" x success + property readback + source independence -> tests/queries/test_alert_clone_core_overrides.py::test_alert_clone_core_overrides CREATE CLONE x WAREHOUSE='COMPUTE_WH' x success + property readback + source independence -> tests/queries/test_alert_clone_core_overrides.py::test_alert_clone_core_overrides CREATE CLONE x WAREHOUSE=$$COMPUTE_WH$$ x success + property readback + source independence -> tests/queries/test_alert_clone_core_overrides.py::test_alert_clone_core_overrides CREATE CLONE x WAREHOUSE=NULL x error 001008 + target atomicity + source independence -> tests/queries/test_alert_clone_core_overrides.py::test_alert_clone_core_overrides CREATE CLONE x WAREHOUSE=42 x error 001008 + target atomicity + source independence -> tests/queries/test_alert_clone_core_overrides.py::test_alert_clone_core_overrides CREATE CLONE x WAREHOUSE=-42 x error 001008 + target atomicity + source independence -> tests/queries/test_alert_clone_core_overrides.py::test_alert_clone_core_overrides CREATE CLONE x WAREHOUSE=TRUE x error 001008 + target atomicity + source independence -> tests/queries/test_alert_clone_core_overrides.py::test_alert_clone_core_overrides CREATE CLONE x SCHEDULE='2 MINUTE' x success + property readback + source independence -> tests/queries/test_alert_clone_core_overrides.py::test_alert_clone_core_overrides CREATE CLONE x SCHEDULE=$$60 S$$ x success + property readback + source independence -> tests/queries/test_alert_clone_core_overrides.py::test_alert_clone_core_overrides CREATE CLONE x SCHEDULE='10 SECOND' x success + property readback + source independence -> tests/queries/test_alert_clone_core_overrides.py::test_alert_clone_core_overrides CREATE CLONE x SCHEDULE='11520 MINUTE' x success + property readback + source independence -> tests/queries/test_alert_clone_core_overrides.py::test_alert_clone_core_overrides CREATE CLONE x SCHEDULE='USING CRON 0 0 L * * UTC' x success + property readback + source independence -> tests/queries/test_alert_clone_core_overrides.py::test_alert_clone_core_overrides CREATE CLONE x SCHEDULE='USING CRON 0 0 * JAN MON America/New_York' x success + property readback + source independence -> tests/queries/test_alert_clone_core_overrides.py::test_alert_clone_core_overrides CREATE CLONE x SCHEDULE=NULL x success + property readback + source independence -> tests/queries/test_alert_clone_core_overrides.py::test_alert_clone_core_overrides CREATE CLONE x SCHEDULE='' x error 091086 + target atomicity + source independence -> tests/queries/test_alert_clone_core_overrides.py::test_alert_clone_core_overrides CREATE CLONE x SCHEDULE='9 SECOND' x error 091851 + target atomicity + source independence -> tests/queries/test_alert_clone_core_overrides.py::test_alert_clone_core_overrides CREATE CLONE x SCHEDULE='11521 MINUTE' x error 091440 + target atomicity + source independence -> tests/queries/test_alert_clone_core_overrides.py::test_alert_clone_core_overrides CREATE CLONE x SCHEDULE='0 MINUTE' x error 091086 + target atomicity + source independence -> tests/queries/test_alert_clone_core_overrides.py::test_alert_clone_core_overrides CREATE CLONE x SCHEDULE='1.5 MINUTE' x error 091086 + target atomicity + source independence -> tests/queries/test_alert_clone_core_overrides.py::test_alert_clone_core_overrides CREATE CLONE x SCHEDULE='USING CRON 60 0 * * * UTC' x error 091086 + target atomicity + source independence -> tests/queries/test_alert_clone_core_overrides.py::test_alert_clone_core_overrides CREATE CLONE x SCHEDULE='USING CRON 0 0 * * * NOT_A_TIMEZONE' x error 091404 + target atomicity + source independence -> tests/queries/test_alert_clone_core_overrides.py::test_alert_clone_core_overrides CREATE CLONE x SCHEDULE=42 x error 001008 + target atomicity + source independence -> tests/queries/test_alert_clone_core_overrides.py::test_alert_clone_core_overrides CREATE CLONE x SCHEDULE=TRUE x error 001008 + target atomicity + source independence -> tests/queries/test_alert_clone_core_overrides.py::test_alert_clone_core_overrides CREATE CLONE x SCHEDULE=invalid_schedule x error 091086 + target atomicity + source independence -> tests/queries/test_alert_clone_core_overrides.py::test_alert_clone_core_overrides CREATE CLONE x SCHEDULE="2 MINUTE" x success + property readback + source independence -> tests/queries/test_alert_clone_core_overrides.py::test_alert_clone_core_overrides CREATE CLONE x COMMENT='clone comment' x success + property readback + source independence -> tests/queries/test_alert_clone_core_overrides.py::test_alert_clone_core_overrides CREATE CLONE x COMMENT=bare_comment x success + property readback + source independence -> tests/queries/test_alert_clone_core_overrides.py::test_alert_clone_core_overrides CREATE CLONE x COMMENT='' x success + property readback + source independence -> tests/queries/test_alert_clone_core_overrides.py::test_alert_clone_core_overrides CREATE CLONE x COMMENT='it''s a clone' x success + property readback + source independence -> tests/queries/test_alert_clone_core_overrides.py::test_alert_clone_core_overrides CREATE CLONE x COMMENT=$$line one\nWAREHOUSE='fake' SCHEDULE='invalid' IF (EXISTS (SELECT 0)) THEN SELECT 0$$ x success + property readback + source independence -> tests/queries/test_alert_clone_core_overrides.py::test_alert_clone_core_overrides CREATE CLONE x COMMENT='雪 ❄' x success + property readback + source independence -> tests/queries/test_alert_clone_core_overrides.py::test_alert_clone_core_overrides CREATE CLONE x COMMENT=NULL x success + property readback + source independence -> tests/queries/test_alert_clone_core_overrides.py::test_alert_clone_core_overrides CREATE CLONE x COMMENT=42 x error 001008 + target atomicity + source independence -> tests/queries/test_alert_clone_core_overrides.py::test_alert_clone_core_overrides CREATE CLONE x COMMENT=TRUE x error 001008 + target atomicity + source independence -> tests/queries/test_alert_clone_core_overrides.py::test_alert_clone_core_overrides CREATE CLONE x COMMENT="clone comment" x success + property readback + source independence -> tests/queries/test_alert_clone_core_overrides.py::test_alert_clone_core_overrides CREATE CLONE x WAREHOUSE=COMPUTE_WH SCHEDULE='2 MINUTE' COMMENT='combined' x success + property readback + source independence -> tests/queries/test_alert_clone_core_overrides.py::test_alert_clone_core_overrides CREATE CLONE x COMMENT='combined reversed' SCHEDULE=$$60 S$$ WAREHOUSE='COMPUTE_WH' x success + property readback + source independence -> tests/queries/test_alert_clone_core_overrides.py::test_alert_clone_core_overrides CREATE CLONE x COMMENT='changed' SCHEDULE='0 MINUTE' x error 091086 + target atomicity + source independence -> tests/queries/test_alert_clone_core_overrides.py::test_alert_clone_core_overrides CREATE CLONE x source schedule absent x omitted -> tests/queries/test_alert_clone_core_overrides.py::test_alert_clone_schedule_presence CREATE CLONE x source schedule absent x SCHEDULE=NULL -> tests/queries/test_alert_clone_core_overrides.py::test_alert_clone_schedule_presence CREATE CLONE x source schedule absent x SCHEDULE='2 MINUTE' -> tests/queries/test_alert_clone_core_overrides.py::test_alert_clone_schedule_presence CREATE CLONE x source schedule present x omitted -> tests/queries/test_alert_clone_core_overrides.py::test_alert_clone_schedule_presence CREATE CLONE x source schedule present x SCHEDULE=NULL -> tests/queries/test_alert_clone_core_overrides.py::test_alert_clone_schedule_presence CREATE CLONE x source schedule present x SCHEDULE='2 MINUTE' -> tests/queries/test_alert_clone_core_overrides.py::test_alert_clone_schedule_presence CREATE CLONE x overridden 60 S x before overridden interval -> tests/queries/test_alert_clone_core_overrides.py::test_alert_clone_override_scheduler CREATE CLONE x overridden 60 S x scheduled insertion -> tests/queries/test_alert_clone_core_overrides.py::test_alert_clone_override_scheduler CREATE CLONE x overridden 60 S x unchanged suspended source -> tests/queries/test_alert_clone_core_overrides.py::test_alert_clone_override_scheduler OR REPLACE CLONE x omitted core overrides x success + property readback + source independence -> tests/queries/test_alert_clone_core_overrides.py::test_alert_clone_core_overrides OR REPLACE CLONE x WAREHOUSE=COMPUTE_WH x success + property readback + source independence -> tests/queries/test_alert_clone_core_overrides.py::test_alert_clone_core_overrides OR REPLACE CLONE x WAREHOUSE=compute_wh x success + property readback + source independence -> tests/queries/test_alert_clone_core_overrides.py::test_alert_clone_core_overrides OR REPLACE CLONE x WAREHOUSE="COMPUTE_WH" x success + property readback + source independence -> tests/queries/test_alert_clone_core_overrides.py::test_alert_clone_core_overrides OR REPLACE CLONE x WAREHOUSE='COMPUTE_WH' x success + property readback + source independence -> tests/queries/test_alert_clone_core_overrides.py::test_alert_clone_core_overrides OR REPLACE CLONE x WAREHOUSE=$$COMPUTE_WH$$ x success + property readback + source independence -> tests/queries/test_alert_clone_core_overrides.py::test_alert_clone_core_overrides OR REPLACE CLONE x WAREHOUSE=NULL x error 001008 + target atomicity + source independence -> tests/queries/test_alert_clone_core_overrides.py::test_alert_clone_core_overrides OR REPLACE CLONE x WAREHOUSE=42 x error 001008 + target atomicity + source independence -> tests/queries/test_alert_clone_core_overrides.py::test_alert_clone_core_overrides OR REPLACE CLONE x WAREHOUSE=-42 x error 001008 + target atomicity + source independence -> tests/queries/test_alert_clone_core_overrides.py::test_alert_clone_core_overrides OR REPLACE CLONE x WAREHOUSE=TRUE x error 001008 + target atomicity + source independence -> tests/queries/test_alert_clone_core_overrides.py::test_alert_clone_core_overrides OR REPLACE CLONE x SCHEDULE='2 MINUTE' x success + property readback + source independence -> tests/queries/test_alert_clone_core_overrides.py::test_alert_clone_core_overrides OR REPLACE CLONE x SCHEDULE=$$60 S$$ x success + property readback + source independence -> tests/queries/test_alert_clone_core_overrides.py::test_alert_clone_core_overrides OR REPLACE CLONE x SCHEDULE='10 SECOND' x success + property readback + source independence -> tests/queries/test_alert_clone_core_overrides.py::test_alert_clone_core_overrides OR REPLACE CLONE x SCHEDULE='11520 MINUTE' x success + property readback + source independence -> tests/queries/test_alert_clone_core_overrides.py::test_alert_clone_core_overrides OR REPLACE CLONE x SCHEDULE='USING CRON 0 0 L * * UTC' x success + property readback + source independence -> tests/queries/test_alert_clone_core_overrides.py::test_alert_clone_core_overrides OR REPLACE CLONE x SCHEDULE='USING CRON 0 0 * JAN MON America/New_York' x success + property readback + source independence -> tests/queries/test_alert_clone_core_overrides.py::test_alert_clone_core_overrides OR REPLACE CLONE x SCHEDULE=NULL x success + property readback + source independence -> tests/queries/test_alert_clone_core_overrides.py::test_alert_clone_core_overrides OR REPLACE CLONE x SCHEDULE='' x error 091086 + target atomicity + source independence -> tests/queries/test_alert_clone_core_overrides.py::test_alert_clone_core_overrides OR REPLACE CLONE x SCHEDULE='9 SECOND' x error 091851 + target atomicity + source independence -> tests/queries/test_alert_clone_core_overrides.py::test_alert_clone_core_overrides OR REPLACE CLONE x SCHEDULE='11521 MINUTE' x error 091440 + target atomicity + source independence -> tests/queries/test_alert_clone_core_overrides.py::test_alert_clone_core_overrides OR REPLACE CLONE x SCHEDULE='0 MINUTE' x error 091086 + target atomicity + source independence -> tests/queries/test_alert_clone_core_overrides.py::test_alert_clone_core_overrides OR REPLACE CLONE x SCHEDULE='1.5 MINUTE' x error 091086 + target atomicity + source independence -> tests/queries/test_alert_clone_core_overrides.py::test_alert_clone_core_overrides OR REPLACE CLONE x SCHEDULE='USING CRON 60 0 * * * UTC' x error 091086 + target atomicity + source independence -> tests/queries/test_alert_clone_core_overrides.py::test_alert_clone_core_overrides OR REPLACE CLONE x SCHEDULE='USING CRON 0 0 * * * NOT_A_TIMEZONE' x error 091404 + target atomicity + source independence -> tests/queries/test_alert_clone_core_overrides.py::test_alert_clone_core_overrides OR REPLACE CLONE x SCHEDULE=42 x error 001008 + target atomicity + source independence -> tests/queries/test_alert_clone_core_overrides.py::test_alert_clone_core_overrides OR REPLACE CLONE x SCHEDULE=TRUE x error 001008 + target atomicity + source independence -> tests/queries/test_alert_clone_core_overrides.py::test_alert_clone_core_overrides OR REPLACE CLONE x SCHEDULE=invalid_schedule x error 091086 + target atomicity + source independence -> tests/queries/test_alert_clone_core_overrides.py::test_alert_clone_core_overrides OR REPLACE CLONE x SCHEDULE="2 MINUTE" x success + property readback + source independence -> tests/queries/test_alert_clone_core_overrides.py::test_alert_clone_core_overrides OR REPLACE CLONE x COMMENT='clone comment' x success + property readback + source independence -> tests/queries/test_alert_clone_core_overrides.py::test_alert_clone_core_overrides OR REPLACE CLONE x COMMENT=bare_comment x success + property readback + source independence -> tests/queries/test_alert_clone_core_overrides.py::test_alert_clone_core_overrides OR REPLACE CLONE x COMMENT='' x success + property readback + source independence -> tests/queries/test_alert_clone_core_overrides.py::test_alert_clone_core_overrides OR REPLACE CLONE x COMMENT='it''s a clone' x success + property readback + source independence -> tests/queries/test_alert_clone_core_overrides.py::test_alert_clone_core_overrides OR REPLACE CLONE x COMMENT=$$line one\nWAREHOUSE='fake' SCHEDULE='invalid' IF (EXISTS (SELECT 0)) THEN SELECT 0$$ x success + property readback + source independence -> tests/queries/test_alert_clone_core_overrides.py::test_alert_clone_core_overrides OR REPLACE CLONE x COMMENT='雪 ❄' x success + property readback + source independence -> tests/queries/test_alert_clone_core_overrides.py::test_alert_clone_core_overrides OR REPLACE CLONE x COMMENT=NULL x success + property readback + source independence -> tests/queries/test_alert_clone_core_overrides.py::test_alert_clone_core_overrides OR REPLACE CLONE x COMMENT=42 x error 001008 + target atomicity + source independence -> tests/queries/test_alert_clone_core_overrides.py::test_alert_clone_core_overrides OR REPLACE CLONE x COMMENT=TRUE x error 001008 + target atomicity + source independence -> tests/queries/test_alert_clone_core_overrides.py::test_alert_clone_core_overrides OR REPLACE CLONE x COMMENT="clone comment" x success + property readback + source independence -> tests/queries/test_alert_clone_core_overrides.py::test_alert_clone_core_overrides OR REPLACE CLONE x WAREHOUSE=COMPUTE_WH SCHEDULE='2 MINUTE' COMMENT='combined' x success + property readback + source independence -> tests/queries/test_alert_clone_core_overrides.py::test_alert_clone_core_overrides OR REPLACE CLONE x COMMENT='combined reversed' SCHEDULE=$$60 S$$ WAREHOUSE='COMPUTE_WH' x success + property readback + source independence -> tests/queries/test_alert_clone_core_overrides.py::test_alert_clone_core_overrides OR REPLACE CLONE x COMMENT='changed' SCHEDULE='0 MINUTE' x error 091086 + target atomicity + source independence -> tests/queries/test_alert_clone_core_overrides.py::test_alert_clone_core_overrides OR REPLACE CLONE x source schedule absent x omitted -> tests/queries/test_alert_clone_core_overrides.py::test_alert_clone_schedule_presence OR REPLACE CLONE x source schedule absent x SCHEDULE=NULL -> tests/queries/test_alert_clone_core_overrides.py::test_alert_clone_schedule_presence OR REPLACE CLONE x source schedule absent x SCHEDULE='2 MINUTE' -> tests/queries/test_alert_clone_core_overrides.py::test_alert_clone_schedule_presence OR REPLACE CLONE x source schedule present x omitted -> tests/queries/test_alert_clone_core_overrides.py::test_alert_clone_schedule_presence OR REPLACE CLONE x source schedule present x SCHEDULE=NULL -> tests/queries/test_alert_clone_core_overrides.py::test_alert_clone_schedule_presence OR REPLACE CLONE x source schedule present x SCHEDULE='2 MINUTE' -> tests/queries/test_alert_clone_core_overrides.py::test_alert_clone_schedule_presence OR REPLACE CLONE x overridden 60 S x before overridden interval -> tests/queries/test_alert_clone_core_overrides.py::test_alert_clone_override_scheduler OR REPLACE CLONE x overridden 60 S x scheduled insertion -> tests/queries/test_alert_clone_core_overrides.py::test_alert_clone_override_scheduler OR REPLACE CLONE x overridden 60 S x unchanged suspended source -> tests/queries/test_alert_clone_core_overrides.py::test_alert_clone_override_scheduler Base-clone lifecycle/body/tag/reset/independence regression -> tests/queries/test_alert_clone.py Shared accepted schedule literals/calendar forms and scheduler regression -> tests/queries/test_alert_schedules.py Shared schedule invalid forms and failure atomicity regression -> tests/queries/test_alert_schedule_errors.py Shared quoted literal and keyword-boundary regression -> tests/queries/test_alert_literals.py Shared task schedule interval/timezone/config errors -> tests/queries/test_tasks.py::test_alter_task_schedule_config_value_spaces Shared task schedule extended invalid forms -> tests/queries/test_tasks.py::test_alter_task_schedule_config_extended_value_spaces Warehouse catalog rejections -> uncovered: reserved to LAV-4377 (reserved_remainder), excluded by this issue Sibling CONFIG/RUNBOOK/failure-threshold clone overrides x valid/error/boundary/consumer regressions -> tests/queries/test_alert_clone_overrides.py Templates -> uncovered: deferred to LAV-4377, excluded by this issue --------- Co-authored-by: spiral <spiral@localhost> Co-authored-by: Przemek Denkiewicz <przemyslaw.denkiewicz@localstack.cloud>
1 parent 7eed466 commit 9714238

2 files changed

Lines changed: 38 additions & 5 deletions

File tree

‎src/ast/mod.rs‎

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5457,6 +5457,12 @@ pub enum Statement {
54575457
name: ObjectName,
54585458
/// Source alert name.
54595459
source: ObjectName,
5460+
/// Explicit warehouse override.
5461+
warehouse: Option<ValueWithSpan>,
5462+
/// Explicit schedule override, including NULL.
5463+
schedule: Option<ValueWithSpan>,
5464+
/// Explicit comment override, including NULL.
5465+
comment: Option<ValueWithSpan>,
54605466
/// Configuration override.
54615467
config: Option<String>,
54625468
/// Consecutive failure threshold override.
@@ -9184,16 +9190,28 @@ impl fmt::Display for Statement {
91849190
if_not_exists,
91859191
name,
91869192
source,
9193+
warehouse,
9194+
schedule,
9195+
comment,
91879196
config,
91889197
suspend_after_num_failures,
91899198
runbook,
91909199
} => {
91919200
write!(
9192-
f,
9193-
"CREATE {}ALERT {}{name} CLONE {source}",
9194-
if *or_replace { "OR REPLACE " } else { "" },
9195-
if *if_not_exists { "IF NOT EXISTS " } else { "" },
9201+
f,
9202+
"CREATE {}ALERT {}{name} CLONE {source}",
9203+
if *or_replace { "OR REPLACE " } else { "" },
9204+
if *if_not_exists { "IF NOT EXISTS " } else { "" },
91969205
)?;
9206+
if let Some(value) = warehouse {
9207+
write!(f, " WAREHOUSE = {value}")?;
9208+
}
9209+
if let Some(value) = schedule {
9210+
write!(f, " SCHEDULE = {value}")?;
9211+
}
9212+
if let Some(value) = comment {
9213+
write!(f, " COMMENT = {value}")?;
9214+
}
91979215
if let Some(config) = config {
91989216
write!(f, " CONFIG = '{}'", value::escape_single_quote_string(config))?;
91999217
}

‎src/parser/mod.rs‎

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6502,6 +6502,9 @@ impl<'a> Parser<'a> {
65026502
} else {
65036503
None
65046504
};
6505+
let mut clone_warehouse = None;
6506+
let mut clone_schedule = None;
6507+
let mut clone_comment = None;
65056508
let mut warehouse: Option<Ident> = None;
65066509
let mut schedule: Option<String> = None;
65076510
let mut comment: Option<String> = None;
@@ -6510,7 +6513,16 @@ impl<'a> Parser<'a> {
65106513
let mut runbook = None;
65116514

65126515
loop {
6513-
if source.is_none() && self.parse_keyword(Keyword::WAREHOUSE) {
6516+
if source.is_some() && self.parse_keyword(Keyword::WAREHOUSE) {
6517+
self.expect_token(&Token::Eq)?;
6518+
clone_warehouse = Some(self.parse_signed_option_value()?);
6519+
} else if source.is_some() && self.parse_keyword(Keyword::SCHEDULE) {
6520+
self.expect_token(&Token::Eq)?;
6521+
clone_schedule = Some(self.parse_signed_option_value()?);
6522+
} else if source.is_some() && self.parse_keyword(Keyword::COMMENT) {
6523+
self.expect_token(&Token::Eq)?;
6524+
clone_comment = Some(self.parse_signed_option_value()?);
6525+
} else if source.is_none() && self.parse_keyword(Keyword::WAREHOUSE) {
65146526
self.expect_token(&Token::Eq)?;
65156527
warehouse = Some(if matches!(self.peek_token().token, Token::DollarQuotedString(_)) {
65166528
Ident::new(self.parse_literal_string()?)
@@ -6561,6 +6573,9 @@ impl<'a> Parser<'a> {
65616573
if_not_exists,
65626574
name,
65636575
source,
6576+
warehouse: clone_warehouse,
6577+
schedule: clone_schedule,
6578+
comment: clone_comment,
65646579
config,
65656580
suspend_after_num_failures,
65666581
runbook,

0 commit comments

Comments
 (0)