Skip to content

Commit 22d1174

Browse files
authored
SNOW-2211956 support copy grants with create replace view (#3755)
1 parent 828f8eb commit 22d1174

File tree

11 files changed

+134
-51
lines changed

11 files changed

+134
-51
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@
1111
- Added a new datatype `DayTimeIntervalType` that allows users to create intervals for datetime operations.
1212
- Added support for `FileOperation.list` to list files in a stage with metadata.
1313
- Added support for `FileOperation.remove` to remove files in a stage.
14+
- Added an option to specify `copy_grants` for the following `DataFrame` APIs:
15+
- `create_or_replace_view`
16+
- `create_or_replace_temp_view`
17+
- `create_or_replace_dynamic_table`
1418
- Added a new function `snowflake.snowpark.functions.vectorized` that allows users to mark a function as vectorized UDF.
1519
- Added support for parameter `use_vectorized_scanner` in function `Session.write_pandas()`.
1620
- Added support for the following scalar functions in `functions.py`:

src/snowflake/snowpark/_internal/analyzer/analyzer.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1338,12 +1338,13 @@ def do_resolve_with_resolved_children(
13381338
)
13391339

13401340
return self.plan_builder.create_or_replace_view(
1341-
logical_plan.name,
1342-
resolved_children[logical_plan.child],
1343-
is_temp,
1344-
logical_plan.comment,
1345-
logical_plan.replace,
1346-
logical_plan,
1341+
name=logical_plan.name,
1342+
child=resolved_children[logical_plan.child],
1343+
is_temp=is_temp,
1344+
comment=logical_plan.comment,
1345+
replace=logical_plan.replace,
1346+
copy_grants=logical_plan.copy_grants,
1347+
source_plan=logical_plan,
13471348
)
13481349

13491350
if isinstance(logical_plan, CreateDynamicTableCommand):
@@ -1365,6 +1366,7 @@ def do_resolve_with_resolved_children(
13651366
child=resolved_children[logical_plan.child],
13661367
source_plan=logical_plan,
13671368
iceberg_config=logical_plan.iceberg_config,
1369+
copy_grants=logical_plan.copy_grants,
13681370
)
13691371

13701372
if isinstance(logical_plan, ReadFileNode):

src/snowflake/snowpark/_internal/analyzer/analyzer_utils.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1376,7 +1376,12 @@ def order_expression(name: str, direction: str, null_ordering: str) -> str:
13761376

13771377

13781378
def create_or_replace_view_statement(
1379-
name: str, child: str, is_temp: bool, comment: Optional[str], replace: bool
1379+
name: str,
1380+
child: str,
1381+
is_temp: bool,
1382+
comment: Optional[str],
1383+
replace: bool,
1384+
copy_grants: bool,
13801385
) -> str:
13811386
comment_sql = get_comment_sql(comment)
13821387
return (
@@ -1386,6 +1391,7 @@ def create_or_replace_view_statement(
13861391
+ VIEW
13871392
+ name
13881393
+ comment_sql
1394+
+ (COPY_GRANTS if copy_grants else EMPTY_STRING)
13891395
+ AS
13901396
+ project_statement([], child)
13911397
)
@@ -1406,6 +1412,7 @@ def create_or_replace_dynamic_table_statement(
14061412
max_data_extension_time: Optional[int],
14071413
child: str,
14081414
iceberg_config: Optional[dict] = None,
1415+
copy_grants: bool = False,
14091416
) -> str:
14101417
cluster_by_sql = (
14111418
f"{CLUSTER_BY}{LEFT_PARENTHESIS}{COMMA.join(clustering_keys)}{RIGHT_PARENTHESIS}"
@@ -1436,7 +1443,7 @@ def create_or_replace_dynamic_table_statement(
14361443
f"{IF + NOT + EXISTS if if_not_exists else EMPTY_STRING}{name}{LAG}{EQUALS}"
14371444
f"{convert_value_to_sql_option(lag)}{WAREHOUSE}{EQUALS}{warehouse}"
14381445
f"{refresh_and_initialize_options}{cluster_by_sql}{data_retention_options}{iceberg_options}"
1439-
f"{comment_sql}{AS}{project_statement([], child)}"
1446+
f"{comment_sql}{COPY_GRANTS if copy_grants else EMPTY_STRING}{AS}{project_statement([], child)}"
14401447
)
14411448

14421449

src/snowflake/snowpark/_internal/analyzer/snowflake_plan.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1548,6 +1548,7 @@ def create_or_replace_view(
15481548
is_temp: bool,
15491549
comment: Optional[str],
15501550
replace: bool,
1551+
copy_grants: bool,
15511552
source_plan: Optional[LogicalPlan],
15521553
) -> SnowflakePlan:
15531554
if len(child.queries) != 1:
@@ -1574,7 +1575,7 @@ def create_or_replace_view(
15741575

15751576
return self.build(
15761577
lambda x: create_or_replace_view_statement(
1577-
name, x, is_temp, comment, replace
1578+
name, x, is_temp, comment, replace, copy_grants
15781579
),
15791580
child,
15801581
source_plan,
@@ -1666,6 +1667,7 @@ def create_or_replace_dynamic_table(
16661667
child: SnowflakePlan,
16671668
source_plan: Optional[LogicalPlan],
16681669
iceberg_config: Optional[dict] = None,
1670+
copy_grants: bool = False,
16691671
) -> SnowflakePlan:
16701672

16711673
child = self.find_and_update_table_function_plan(child)
@@ -1705,6 +1707,7 @@ def create_or_replace_dynamic_table(
17051707
max_data_extension_time=max_data_extension_time,
17061708
child=x,
17071709
iceberg_config=iceberg_config,
1710+
copy_grants=copy_grants,
17081711
),
17091712
child,
17101713
source_plan,

src/snowflake/snowpark/_internal/analyzer/unary_plan_node.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -322,13 +322,15 @@ def __init__(
322322
view_type: ViewType,
323323
comment: Optional[str],
324324
replace: bool,
325+
copy_grants: bool,
325326
child: LogicalPlan,
326327
) -> None:
327328
super().__init__(child)
328329
self.name = name
329330
self.view_type = view_type
330331
self.comment = comment
331332
self.replace = replace
333+
self.copy_grants = copy_grants
332334

333335

334336
class CreateDynamicTableCommand(UnaryNode):
@@ -347,6 +349,7 @@ def __init__(
347349
max_data_extension_time: Optional[int],
348350
child: LogicalPlan,
349351
iceberg_config: Optional[dict] = None,
352+
copy_grants: bool = False,
350353
) -> None:
351354
super().__init__(child)
352355
self.name = name
@@ -361,3 +364,4 @@ def __init__(
361364
self.data_retention_time = data_retention_time
362365
self.max_data_extension_time = max_data_extension_time
363366
self.iceberg_config = iceberg_config
367+
self.copy_grants = copy_grants

src/snowflake/snowpark/_internal/proto/ast.proto

Lines changed: 21 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -835,7 +835,7 @@ message DataframeCollect {
835835
repeated Tuple_String_String statement_params = 7;
836836
}
837837

838-
// dataframe-io.ir:163
838+
// dataframe-io.ir:165
839839
message DataframeCopyIntoTable {
840840
repeated Tuple_String_Expr copy_options = 1;
841841
Expr df = 2;
@@ -859,32 +859,34 @@ message DataframeCount {
859859
repeated Tuple_String_String statement_params = 4;
860860
}
861861

862-
// dataframe-io.ir:147
862+
// dataframe-io.ir:148
863863
message DataframeCreateOrReplaceDynamicTable {
864864
repeated Expr clustering_keys = 1;
865865
google.protobuf.StringValue comment = 2;
866-
google.protobuf.Int64Value data_retention_time = 3;
867-
Expr df = 4;
868-
google.protobuf.StringValue initialize = 5;
869-
bool is_transient = 6;
870-
string lag = 7;
871-
google.protobuf.Int64Value max_data_extension_time = 8;
872-
SaveMode mode = 9;
873-
NameRef name = 10;
874-
google.protobuf.StringValue refresh_mode = 11;
875-
SrcPosition src = 12;
876-
repeated Tuple_String_String statement_params = 13;
877-
string warehouse = 14;
866+
bool copy_grants = 3;
867+
google.protobuf.Int64Value data_retention_time = 4;
868+
Expr df = 5;
869+
google.protobuf.StringValue initialize = 6;
870+
bool is_transient = 7;
871+
string lag = 8;
872+
google.protobuf.Int64Value max_data_extension_time = 9;
873+
SaveMode mode = 10;
874+
NameRef name = 11;
875+
google.protobuf.StringValue refresh_mode = 12;
876+
SrcPosition src = 13;
877+
repeated Tuple_String_String statement_params = 14;
878+
string warehouse = 15;
878879
}
879880

880881
// dataframe-io.ir:139
881882
message DataframeCreateOrReplaceView {
882883
google.protobuf.StringValue comment = 1;
883-
Expr df = 2;
884-
bool is_temp = 3;
885-
NameRef name = 4;
886-
SrcPosition src = 5;
887-
repeated Tuple_String_String statement_params = 6;
884+
bool copy_grants = 2;
885+
Expr df = 3;
886+
bool is_temp = 4;
887+
NameRef name = 5;
888+
SrcPosition src = 6;
889+
repeated Tuple_String_String statement_params = 7;
888890
}
889891

890892
// dataframe.ir:185

src/snowflake/snowpark/dataframe.py

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5246,6 +5246,7 @@ def create_or_replace_view(
52465246
*,
52475247
comment: Optional[str] = None,
52485248
statement_params: Optional[Dict[str, str]] = None,
5249+
copy_grants: bool = False,
52495250
_emit_ast: bool = True,
52505251
) -> List[Row]:
52515252
"""Creates a view that captures the computation expressed by this DataFrame.
@@ -5261,6 +5262,8 @@ def create_or_replace_view(
52615262
that specifies the database name, schema name, and view name.
52625263
comment: Adds a comment for the created view. See
52635264
`COMMENT <https://docs.snowflake.com/en/sql-reference/sql/comment>`_.
5265+
copy_grants: A boolean value that specifies whether to retain the access permissions from the original view
5266+
when a new view is created. Defaults to False.
52645267
statement_params: Dictionary of statement level parameters to be set while executing this action.
52655268
"""
52665269

@@ -5272,6 +5275,7 @@ def create_or_replace_view(
52725275
stmt = self._session._ast_batch.bind()
52735276
expr = with_src_position(stmt.expr.dataframe_create_or_replace_view, stmt)
52745277
expr.is_temp = False
5278+
expr.copy_grants = copy_grants
52755279
self._set_ast_ref(expr.df)
52765280
build_view_name(expr.name, name)
52775281
if comment is not None:
@@ -5282,6 +5286,7 @@ def create_or_replace_view(
52825286
formatted_name,
52835287
PersistedView(),
52845288
comment=comment,
5289+
copy_grants=copy_grants,
52855290
_statement_params=create_or_update_statement_params_with_query_tag(
52865291
statement_params or self._statement_params,
52875292
self._session.query_tag,
@@ -5311,6 +5316,7 @@ def create_or_replace_dynamic_table(
53115316
max_data_extension_time: Optional[int] = None,
53125317
statement_params: Optional[Dict[str, str]] = None,
53135318
iceberg_config: Optional[dict] = None,
5319+
copy_grants: bool = False,
53145320
_emit_ast: bool = True,
53155321
) -> List[Row]:
53165322
"""Creates a dynamic table that captures the computation expressed by this DataFrame.
@@ -5354,6 +5360,8 @@ def create_or_replace_dynamic_table(
53545360
- base_location: the base directory that snowflake can write iceberg metadata and files to.
53555361
- catalog_sync: optionally sets the catalog integration configured for Polaris Catalog.
53565362
- storage_serialization_policy: specifies the storage serialization policy for the table.
5363+
copy_grants: A boolean value that specifies whether to retain the access permissions from the original view
5364+
when a new view is created. Defaults to False.
53575365
53585366
53595367
Note:
@@ -5407,6 +5415,7 @@ def create_or_replace_dynamic_table(
54075415

54085416
if statement_params is not None:
54095417
build_expr_from_dict_str_str(expr.statement_params, statement_params)
5418+
expr.copy_grants = copy_grants
54105419
# TODO: Support create_or_replace_dynamic_table in MockServerConnection.
54115420
from snowflake.snowpark.mock._connection import MockServerConnection
54125421

@@ -5443,6 +5452,7 @@ def create_or_replace_dynamic_table(
54435452
),
54445453
),
54455454
iceberg_config=iceberg_config,
5455+
copy_grants=copy_grants,
54465456
)
54475457

54485458
@df_collect_api_telemetry
@@ -5453,6 +5463,7 @@ def create_or_replace_temp_view(
54535463
*,
54545464
comment: Optional[str] = None,
54555465
statement_params: Optional[Dict[str, str]] = None,
5466+
copy_grants: bool = False,
54565467
_emit_ast: bool = True,
54575468
) -> List[Row]:
54585469
"""Creates or replace a temporary view that returns the same results as this DataFrame.
@@ -5472,6 +5483,8 @@ def create_or_replace_temp_view(
54725483
that specifies the database name, schema name, and view name.
54735484
comment: Adds a comment for the created view. See
54745485
`COMMENT <https://docs.snowflake.com/en/sql-reference/sql/comment>`_.
5486+
copy_grants: A boolean value that specifies whether to retain the access permissions from the original view
5487+
when a new view is created. Defaults to False.
54755488
statement_params: Dictionary of statement level parameters to be set while executing this action.
54765489
"""
54775490

@@ -5489,11 +5502,13 @@ def create_or_replace_temp_view(
54895502
expr.comment.value = comment
54905503
if statement_params is not None:
54915504
build_expr_from_dict_str_str(expr.statement_params, statement_params)
5505+
expr.copy_grants = copy_grants
54925506

54935507
return self._do_create_or_replace_view(
54945508
formatted_name,
54955509
LocalTempView(),
54965510
comment=comment,
5511+
copy_grants=copy_grants,
54975512
_statement_params=create_or_update_statement_params_with_query_tag(
54985513
statement_params or self._statement_params,
54995514
self._session.query_tag,
@@ -5573,16 +5588,18 @@ def _do_create_or_replace_view(
55735588
view_type: ViewType,
55745589
comment: Optional[str],
55755590
replace: bool = True,
5591+
copy_grants: bool = False,
55765592
_ast_stmt: Optional[proto.Bind] = None,
55775593
**kwargs,
55785594
):
55795595
validate_object_name(view_name)
55805596
cmd = CreateViewCommand(
5581-
view_name,
5582-
view_type,
5583-
comment,
5584-
replace,
5585-
self._plan,
5597+
name=view_name,
5598+
view_type=view_type,
5599+
comment=comment,
5600+
replace=replace,
5601+
copy_grants=copy_grants,
5602+
child=self._plan,
55865603
)
55875604

55885605
return self._session._conn.execute(
@@ -5603,6 +5620,7 @@ def _do_create_or_replace_dynamic_table(
56035620
data_retention_time: Optional[int] = None,
56045621
max_data_extension_time: Optional[int] = None,
56055622
iceberg_config: Optional[dict] = None,
5623+
copy_grants: bool = False,
56065624
**kwargs,
56075625
):
56085626
validate_object_name(name)
@@ -5630,6 +5648,7 @@ def _do_create_or_replace_dynamic_table(
56305648
max_data_extension_time=max_data_extension_time,
56315649
child=self._plan,
56325650
iceberg_config=iceberg_config,
5651+
copy_grants=copy_grants,
56335652
)
56345653

56355654
return self._session._conn.execute(

0 commit comments

Comments
 (0)