Skip to content

fix(lifecycle): apply every field when an update changes several fields - #91

Merged
noel merged 2 commits into
datacoves:mainfrom
GClunies:snowcap-89
Oct 16, 2026
Merged

noel merged 2 commits into
datacoves:mainfrom
GClunies:snowcap-89

Conversation

@GClunies

Copy link
Copy Markdown
Contributor

What and why

A plan that changes several fields of one object now applies all of them. Before, update_schema, update_table, update_iceberg_table, update_masking_policy, and update_scanner_package called data.popitem() and wrote SQL for one field only. update_procedure wrote only EXECUTE AS when execute_as changed. The apply reported success, and the next plan showed the lost changes as drift. Fixes #89.

Handler A change to several fields now gives
schema, masking policy, procedure One statement for the field with its own ALTER syntax (ENABLE/DISABLE MANAGED ACCESS, SET BODY ->, EXECUTE AS), then one update__default statement for the other fields
table, Iceberg table One update__default statement. A column change raises NotImplementedError.
scanner package One SET_CONFIGURATION call for each field

A new helper, _update_with_separate_clauses, holds the shared shape. A rename must still arrive alone, as in update__default. A schema transient change now raises NotImplementedError instead of Exception. The handlers no longer change the caller's change.delta. Schema SET values now render through Schema.props, so they use $$ quoting, for example SET COMMENT = $$it's here$$.

Validation

  • Unit tests: tests/test_lifecycle.py has a multi-field test for each of the six handlers. 9 tests failed before the fix. On commit 73d5443, make check passes (2235 passed) and mypy is clean.
  • Live plan, apply, and re-plan with Blueprint on the test account:
Object SQL that snowcap ran Re-plan
Schema ENABLE MANAGED ACCESS, SET MAX_DATA_EXTENSION_TIME_IN_DAYS = 9 COMMENT = $$v2$$ 0 changes
Masking policy SET BODY -> '***', SET COMMENT = $$v2$$ 0 changes
Procedure EXECUTE AS CALLER, SET COMMENT = $$v2$$ 0 changes
Table SET DATA_RETENTION_TIME_IN_DAYS = 3 COMMENT = $$v2$$ 1 change

For the table, SHOW TABLES after the apply showed retention_time=3 and comment=v2. The re-plan still shows data_retention_time_in_days because fetch_table never reads it (the mapping is commented out in data_provider.py). That gap is older than this PR.

  • The live run used the code before one review change, the transient exception type. The live run does not reach that path.
  • Not run live: Iceberg table (needs an external volume) and scanner package (needs Trust Center access). They have unit tests only.

How to review

  • Start with _update_with_separate_clauses in snowcap/lifecycle.py, then the six handlers.
  • Feedback wanted: is one ALTER per special clause the right shape, compared to refusing those combinations?
  • Overlap with Quote ALTER SCHEMA SET values #70: both PRs rewrite update_schema and update_scanner_package, and they conflict in lifecycle.py. This PR already fixes the update_schema apostrophe bug from Quote ALTER SCHEMA SET values #70. To resolve, keep this PR's update_schema. In update_scanner_package, keep this PR's loop and use Quote ALTER SCHEMA SET values #70's quote_value quoting.
  • Out of scope: update_task (fix(task): apply a body change together with other fields #88). update__default still skips fields that have no prop (Props.render), and it still puts fields such as cluster_by after SET. These need separate issues.

GClunies and others added 2 commits October 9, 2026 09:49
datacoves#70/datacoves#93 merged into main while this branch was open, rewriting
update_scanner_package/update_schema to escape values via quote_value.
This branch independently restructured the same two functions to apply
every field in a multi-field delta. Combined both: the per-field-call
loop and the _update_with_separate_clauses delegation from this branch,
using quote_value's escaping from main.

Also:
- Fixed two tests left over from the merge that predated this branch's
  rewrite (MockProps stub, lowercase attr names, old $$-less
  expectations).
- Widened create_scanner_package/drop_scanner_package's return type to
  Union[str, list[str]] to match update_scanner_package's now-broader
  return type.
- Fixed a casing bug surfaced by code review: Python's str(False) is
  "False", not SQL's FALSE, so update_scanner_package quoted a bool
  "enabled" value as $$False$$, which Snowflake's SET_CONFIGURATION
  rejects. Bools now render as TRUE/FALSE before quoting.

make check (black, ruff, mypy, full test suite) passes clean.
@noel

noel commented Oct 16, 2026

Copy link
Copy Markdown
Contributor

Resolved the merge conflict with main (this branch and #70/#93 both independently rewrote update_scanner_package/update_schema). Combined both: kept this branch's per-field-call loop in update_scanner_package and _update_with_separate_clauses delegation in update_schema, using quote_value's escaping from main.

Also found and fixed, via code review on the resolution itself:

  • Two leftover tests from the merge needed updating for the new escaping ($$...$$ instead of bare '...', uppercase COMMENT from real Schema.props instead of a MockProps stub).
  • create_scanner_package/drop_scanner_package's return type needed widening to Union[str, list[str]] to match update_scanner_package's now-broader return type (mypy caught this).
  • A casing bug: Python's str(False) is "False", not SQL's FALSE. A Python bool flowing into update_scanner_package (e.g. toggling a scanner package's enabled field) rendered as $$False$$, which Snowflake's SET_CONFIGURATION would reject. Bools now render as TRUE/FALSE before quoting, with a regression test.

make check (black, ruff, mypy, full test suite, 2267 passed) is clean on the resolved branch.

@noel
noel merged commit 584e44b into datacoves:main Oct 16, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Updates that change several fields on a schema, table, masking policy, procedure, Iceberg table, or scanner package apply only one field

2 participants