Skip to content

Commit d82f650

Browse files
committed
Implement SNAPSHOT_POLICY, SNAPSHOT_SET; Add workaround for user TYPE
1 parent df220c6 commit d82f650

File tree

29 files changed

+496
-1
lines changed

29 files changed

+496
-1
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
# Changelog
22

3+
## [0.57.0] - 2025-09-16
4+
5+
- Introduced workaround for user type `NULL` being replaced with `PERSON` in bundle `2025_05`.
6+
- Implemented initial logic for `SNAPSHOT POLICY` and `SNAPSHOT SET` object types.
7+
38
## [0.56.0] - 2025-09-02
49

510
- Reworked and extended drop intention cache logic to table columns.

snowddl/blueprint/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@
3535
SecretBlueprint,
3636
SemanticViewBlueprint,
3737
SequenceBlueprint,
38+
SnapshotPolicyBlueprint,
39+
SnapshotSetBlueprint,
3840
StageBlueprint,
3941
StageFileBlueprint,
4042
StreamBlueprint,

snowddl/blueprint/blueprint.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -374,6 +374,17 @@ class SequenceBlueprint(SchemaObjectBlueprint):
374374
is_ordered: Optional[bool] = None
375375

376376

377+
class SnapshotPolicyBlueprint(SchemaObjectBlueprint):
378+
schedule: Optional[str] = None
379+
expire_after_days: Optional[int] = None
380+
381+
382+
class SnapshotSetBlueprint(SchemaObjectBlueprint):
383+
object_type: ObjectType
384+
object_name: Union[DatabaseIdent, SchemaIdent, SchemaObjectIdent]
385+
snapshot_policy: Optional[SchemaObjectIdent] = None
386+
387+
377388
class StageBlueprint(SchemaObjectBlueprint):
378389
url: Optional[str] = None
379390
storage_integration: Optional[Ident] = None

snowddl/blueprint/object_type.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,18 @@ class ObjectType(Enum):
247247
"blueprint_cls": "OutboundShareBlueprint",
248248
}
249249

250+
SNAPSHOT_POLICY = {
251+
"singular": "SNAPSHOT POLICY",
252+
"plural": "SNAPSHOT POLICIES",
253+
"blueprint_cls": "SnapshotPolicyBlueprint",
254+
}
255+
256+
SNAPSHOT_SET = {
257+
"singular": "SNAPSHOT SET",
258+
"plural": "SNAPSHOT SETS",
259+
"blueprint_cls": "SnapshotSetBlueprint",
260+
}
261+
250262
STAGE = {
251263
"singular": "STAGE",
252264
"plural": "STAGES",

snowddl/parser/__init__.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@
3434
from .secret import SecretParser
3535
from .semantic_view import SemanticViewParser
3636
from .sequence import SequenceParser
37+
from .snapshot_policy import SnapshotPolicyParser
38+
from .snapshot_set import SnapshotSetParser
3739
from .stage import StageParser
3840
from .stream import StreamParser
3941
from .table import TableParser
@@ -55,6 +57,8 @@
5557
RowAccessPolicyParser,
5658
ResourceMonitorParser,
5759
AccountPolicyParser,
60+
SnapshotPolicyParser,
61+
SnapshotSetParser,
5862
# --
5963
WarehouseParser,
6064
DatabaseParser,
@@ -95,6 +99,8 @@
9599
MaskingPolicyParser,
96100
ProjectionPolicyParser,
97101
RowAccessPolicyParser,
102+
SnapshotPolicyParser,
103+
SnapshotSetParser,
98104
# --
99105
DatabaseParser,
100106
SchemaParser,

snowddl/parser/snapshot_policy.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
from snowddl.blueprint import SnapshotPolicyBlueprint, SchemaObjectIdent
2+
from snowddl.parser.abc_parser import AbstractParser, ParsedFile
3+
4+
5+
# fmt: off
6+
snapshot_policy_json_schema = {
7+
"type": "object",
8+
"properties": {
9+
"schedule": {
10+
"type": "string"
11+
},
12+
"expire_after_days": {
13+
"type": "integer"
14+
},
15+
"comment": {
16+
"type": "string"
17+
}
18+
},
19+
"additionalProperties": False
20+
}
21+
# fmt: on
22+
23+
24+
class SnapshotPolicyParser(AbstractParser):
25+
def load_blueprints(self):
26+
self.parse_schema_object_files("snapshot_policy", snapshot_policy_json_schema, self.process_snapshot_policy)
27+
28+
def process_snapshot_policy(self, f: ParsedFile):
29+
bp = SnapshotPolicyBlueprint(
30+
full_name=SchemaObjectIdent(self.env_prefix, f.database, f.schema, f.name),
31+
schedule=f.params.get("schedule"),
32+
expire_after_days=f.params.get("expire_after_days"),
33+
comment=f.params.get("comment"),
34+
)
35+
36+
self.config.add_blueprint(bp)

snowddl/parser/snapshot_set.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
from snowddl.blueprint import (
2+
DatabaseIdent,
3+
SchemaIdent,
4+
ObjectType,
5+
SnapshotSetBlueprint,
6+
SchemaObjectIdent,
7+
build_schema_object_ident,
8+
)
9+
from snowddl.parser.abc_parser import AbstractParser, ParsedFile
10+
11+
12+
# fmt: off
13+
snapshot_set_json_schema = {
14+
"type": "object",
15+
"properties": {
16+
"object_type": {
17+
"type": "string"
18+
},
19+
"object_name": {
20+
"type": "string"
21+
},
22+
"snapshot_policy": {
23+
"type": "string"
24+
},
25+
"comment": {
26+
"type": "string"
27+
}
28+
},
29+
"additionalProperties": False
30+
}
31+
# fmt: on
32+
33+
34+
class SnapshotSetParser(AbstractParser):
35+
def load_blueprints(self):
36+
self.parse_schema_object_files("snapshot_set", snapshot_set_json_schema, self.process_snapshot_set)
37+
38+
def process_snapshot_set(self, f: ParsedFile):
39+
object_type = ObjectType[f.params["object_type"]]
40+
41+
if object_type == ObjectType.DATABASE:
42+
object_name = DatabaseIdent(self.env_prefix, f.params["object_name"])
43+
elif object_type == ObjectType.SCHEMA:
44+
object_name = SchemaIdent(self.env_prefix, *f.params["object_name"].split(".", 2))
45+
else:
46+
object_name = build_schema_object_ident(self.env_prefix, f.params["object_name"], f.database, f.schema)
47+
48+
bp = SnapshotSetBlueprint(
49+
full_name=SchemaObjectIdent(self.env_prefix, f.database, f.schema, f.name),
50+
object_type=ObjectType[f.params["object_type"]],
51+
object_name=object_name,
52+
snapshot_policy=build_schema_object_ident(self.env_prefix, f.params["snapshot_policy"], f.database, f.schema) if f.params.get("snapshot_policy") else None,
53+
comment=f.params.get("comment"),
54+
)
55+
56+
self.config.add_blueprint(bp)

snowddl/resolver/__init__.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@
4141
from .schema_write_role import SchemaWriteRoleResolver
4242
from .secret import SecretResolver
4343
from .semantic_view import SemanticViewResolver
44+
from .snapshot_policy import SnapshotPolicyResolver
45+
from .snapshot_set import SnapshotSetResolver
4446
from .stage import StageResolver
4547
from .stage_file import StageFileResolver
4648
from .stream import StreamResolver
@@ -112,6 +114,8 @@
112114
NetworkPolicyResolver,
113115
ProjectionPolicyResolver,
114116
RowAccessPolicyResolver,
117+
SnapshotPolicyResolver,
118+
SnapshotSetResolver,
115119
]
116120

117121

@@ -179,6 +183,8 @@
179183
MaskingPolicyResolver,
180184
ProjectionPolicyResolver,
181185
RowAccessPolicyResolver,
186+
SnapshotPolicyResolver,
187+
SnapshotSetResolver,
182188
]
183189

184190

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
from snowddl.blueprint import SnapshotPolicyBlueprint
2+
from snowddl.resolver.abc_schema_object_resolver import AbstractSchemaObjectResolver, ResolveResult, ObjectType
3+
4+
5+
class SnapshotPolicyResolver(AbstractSchemaObjectResolver):
6+
skip_on_empty_blueprints = True
7+
8+
def get_object_type(self) -> ObjectType:
9+
return ObjectType.SNAPSHOT_POLICY
10+
11+
def get_existing_objects_in_schema(self, schema: dict):
12+
existing_objects = {}
13+
14+
cur = self.engine.execute_meta(
15+
"SHOW SNAPSHOT POLICIES IN SCHEMA {database:i}.{schema:i}",
16+
{
17+
"database": schema["database"],
18+
"schema": schema["schema"],
19+
},
20+
)
21+
22+
for r in cur:
23+
existing_objects[f"{r['database_name']}.{r['schema_name']}.{r['name']}"] = {
24+
"database": r["database_name"],
25+
"schema": r["schema_name"],
26+
"name": r["name"],
27+
"owner": r["owner"],
28+
"schedule": r["schedule"],
29+
"expire_after_days": r["expire_after_days"],
30+
"comment": r["comment"] if r["comment"] else None,
31+
}
32+
33+
return existing_objects
34+
35+
def get_blueprints(self):
36+
return self.config.get_blueprints_by_type(SnapshotPolicyBlueprint)
37+
38+
def create_object(self, bp: SnapshotPolicyBlueprint):
39+
query = self.engine.query_builder()
40+
41+
query.append(
42+
"CREATE SNAPSHOT POLICY {full_name:i}",
43+
{
44+
"full_name": bp.full_name,
45+
},
46+
)
47+
48+
if bp.schedule:
49+
query.append_nl(
50+
"SCHEDULE = {schedule}",
51+
{
52+
"schedule": bp.schedule,
53+
},
54+
)
55+
56+
if bp.expire_after_days:
57+
query.append_nl(
58+
"EXPIRE_AFTER_DAYS = {expire_after_days:d}",
59+
{
60+
"expire_after_days": bp.expire_after_days,
61+
},
62+
)
63+
64+
if bp.comment:
65+
query.append_nl(
66+
"COMMENT = {comment}",
67+
{
68+
"comment": bp.comment,
69+
},
70+
)
71+
72+
self.engine.execute_unsafe_ddl(query)
73+
74+
return ResolveResult.CREATE
75+
76+
def compare_object(self, bp: SnapshotPolicyBlueprint, row: dict):
77+
result = ResolveResult.NOCHANGE
78+
79+
if bp.schedule != row["schedule"]:
80+
if bp.schedule:
81+
self.engine.execute_unsafe_ddl(
82+
"ALTER SNAPSHOT POLICY {full_name:i} SET SCHEDULE = {schedule}",
83+
{
84+
"full_name": bp.full_name,
85+
"schedule": bp.schedule,
86+
},
87+
)
88+
else:
89+
self.engine.execute_unsafe_ddl(
90+
"ALTER SNAPSHOT POLICY {full_name:i} UNSET SCHEDULE",
91+
{
92+
"full_name": bp.full_name,
93+
},
94+
)
95+
96+
result = ResolveResult.ALTER
97+
98+
if bp.expire_after_days != row["expire_after_days"]:
99+
if bp.expire_after_days:
100+
self.engine.execute_unsafe_ddl(
101+
"ALTER SNAPSHOT POLICY {full_name:i} SET EXPIRE_AFTER_DAYS = {expire_after_days:d}",
102+
{
103+
"full_name": bp.full_name,
104+
"expire_after_days": bp.expire_after_days,
105+
},
106+
)
107+
else:
108+
self.engine.execute_unsafe_ddl(
109+
"ALTER SNAPSHOT POLICY {full_name:i} UNSET EXPIRE_AFTER_DAYS",
110+
{
111+
"full_name": bp.full_name,
112+
},
113+
)
114+
115+
result = ResolveResult.ALTER
116+
117+
if bp.comment != row["comment"]:
118+
self.engine.execute_unsafe_ddl(
119+
"ALTER SNAPSHOT POLICY {full_name:i} SET COMMENT = {comment}",
120+
{
121+
"full_name": bp.full_name,
122+
"comment": bp.comment,
123+
},
124+
)
125+
126+
result = ResolveResult.ALTER
127+
128+
return result
129+
130+
def drop_object(self, row: dict):
131+
self.engine.execute_unsafe_ddl(
132+
"DROP SNAPSHOT POLICY {database:i}.{schema:i}.{name:i}",
133+
{
134+
"database": row["database"],
135+
"schema": row["schema"],
136+
"name": row["name"],
137+
},
138+
)
139+
140+
return ResolveResult.DROP

0 commit comments

Comments
 (0)