snowflake-cli 3.9.1__py3-none-any.whl → 3.10.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- snowflake/cli/__about__.py +1 -1
- snowflake/cli/_app/printing.py +53 -13
- snowflake/cli/_app/snow_connector.py +1 -0
- snowflake/cli/_app/telemetry.py +2 -0
- snowflake/cli/_app/version_check.py +73 -6
- snowflake/cli/_plugins/cortex/commands.py +8 -3
- snowflake/cli/_plugins/cortex/manager.py +24 -20
- snowflake/cli/_plugins/dbt/commands.py +5 -2
- snowflake/cli/_plugins/git/manager.py +1 -11
- snowflake/cli/_plugins/nativeapp/codegen/snowpark/python_processor.py +4 -0
- snowflake/cli/_plugins/nativeapp/commands.py +3 -4
- snowflake/cli/_plugins/nativeapp/entities/application_package.py +1 -1
- snowflake/cli/_plugins/nativeapp/release_channel/commands.py +1 -2
- snowflake/cli/_plugins/nativeapp/version/commands.py +1 -2
- snowflake/cli/_plugins/project/commands.py +54 -7
- snowflake/cli/_plugins/project/manager.py +20 -1
- snowflake/cli/_plugins/snowpark/common.py +23 -11
- snowflake/cli/_plugins/snowpark/snowpark_entity.py +13 -5
- snowflake/cli/_plugins/snowpark/snowpark_entity_model.py +10 -2
- snowflake/cli/_plugins/sql/commands.py +49 -1
- snowflake/cli/_plugins/sql/manager.py +14 -4
- snowflake/cli/_plugins/sql/repl.py +4 -0
- snowflake/cli/_plugins/stage/commands.py +30 -11
- snowflake/cli/_plugins/stage/diff.py +2 -0
- snowflake/cli/_plugins/stage/manager.py +79 -55
- snowflake/cli/_plugins/streamlit/streamlit_entity.py +17 -30
- snowflake/cli/api/artifacts/upload.py +1 -1
- snowflake/cli/api/cli_global_context.py +5 -14
- snowflake/cli/api/commands/decorators.py +7 -0
- snowflake/cli/api/commands/flags.py +12 -0
- snowflake/cli/api/commands/snow_typer.py +23 -2
- snowflake/cli/api/config.py +9 -5
- snowflake/cli/api/connections.py +1 -0
- snowflake/cli/api/entities/common.py +16 -13
- snowflake/cli/api/entities/utils.py +15 -9
- snowflake/cli/api/feature_flags.py +2 -5
- snowflake/cli/api/output/formats.py +6 -0
- snowflake/cli/api/output/types.py +48 -2
- snowflake/cli/api/rendering/sql_templates.py +67 -11
- snowflake/cli/api/stage_path.py +37 -5
- {snowflake_cli-3.9.1.dist-info → snowflake_cli-3.10.0.dist-info}/METADATA +45 -12
- {snowflake_cli-3.9.1.dist-info → snowflake_cli-3.10.0.dist-info}/RECORD +45 -46
- snowflake/cli/_plugins/project/feature_flags.py +0 -22
- {snowflake_cli-3.9.1.dist-info → snowflake_cli-3.10.0.dist-info}/WHEEL +0 -0
- {snowflake_cli-3.9.1.dist-info → snowflake_cli-3.10.0.dist-info}/entry_points.txt +0 -0
- {snowflake_cli-3.9.1.dist-info → snowflake_cli-3.10.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -18,7 +18,6 @@ import typer
|
|
|
18
18
|
from snowflake.cli._plugins.object.command_aliases import add_object_command_aliases
|
|
19
19
|
from snowflake.cli._plugins.object.commands import scope_option
|
|
20
20
|
from snowflake.cli._plugins.object.manager import ObjectManager
|
|
21
|
-
from snowflake.cli._plugins.project.feature_flags import FeatureFlag
|
|
22
21
|
from snowflake.cli._plugins.project.manager import ProjectManager
|
|
23
22
|
from snowflake.cli._plugins.project.project_entity_model import (
|
|
24
23
|
ProjectEntityModel,
|
|
@@ -26,6 +25,7 @@ from snowflake.cli._plugins.project.project_entity_model import (
|
|
|
26
25
|
from snowflake.cli.api.cli_global_context import get_cli_context
|
|
27
26
|
from snowflake.cli.api.commands.decorators import with_project_definition
|
|
28
27
|
from snowflake.cli.api.commands.flags import (
|
|
28
|
+
IfExistsOption,
|
|
29
29
|
IfNotExistsOption,
|
|
30
30
|
OverrideableOption,
|
|
31
31
|
PruneOption,
|
|
@@ -39,6 +39,7 @@ from snowflake.cli.api.commands.utils import get_entity_for_operation
|
|
|
39
39
|
from snowflake.cli.api.console.console import cli_console
|
|
40
40
|
from snowflake.cli.api.constants import ObjectType
|
|
41
41
|
from snowflake.cli.api.exceptions import CliError
|
|
42
|
+
from snowflake.cli.api.feature_flags import FeatureFlag
|
|
42
43
|
from snowflake.cli.api.identifiers import FQN
|
|
43
44
|
from snowflake.cli.api.output.types import (
|
|
44
45
|
MessageResult,
|
|
@@ -56,7 +57,7 @@ project_identifier = identifier_argument(sf_object="project", example="MY_PROJEC
|
|
|
56
57
|
version_flag = typer.Option(
|
|
57
58
|
None,
|
|
58
59
|
"--version",
|
|
59
|
-
help="Version of the project to use. If not specified default version is used",
|
|
60
|
+
help="Version of the project to use. If not specified default version is used. For names containing '$', use single quotes to prevent shell expansion (e.g., 'VERSION$1').",
|
|
60
61
|
show_default=False,
|
|
61
62
|
)
|
|
62
63
|
variables_flag = variables_option(
|
|
@@ -71,7 +72,6 @@ configuration_flag = typer.Option(
|
|
|
71
72
|
from_option = OverrideableOption(
|
|
72
73
|
None,
|
|
73
74
|
"--from",
|
|
74
|
-
help="Create a new version using given stage instead of uploading local files.",
|
|
75
75
|
show_default=False,
|
|
76
76
|
)
|
|
77
77
|
|
|
@@ -81,7 +81,7 @@ add_object_command_aliases(
|
|
|
81
81
|
object_type=ObjectType.PROJECT,
|
|
82
82
|
name_argument=project_identifier,
|
|
83
83
|
like_option=like_option(
|
|
84
|
-
help_example='`list --like "my%"` lists all projects that begin with
|
|
84
|
+
help_example='`list --like "my%"` lists all projects that begin with "my"'
|
|
85
85
|
),
|
|
86
86
|
scope_option=scope_option(help_example="`list --in database my_db`"),
|
|
87
87
|
ommit_commands=["create", "describe"],
|
|
@@ -92,6 +92,9 @@ add_object_command_aliases(
|
|
|
92
92
|
def execute(
|
|
93
93
|
identifier: FQN = project_identifier,
|
|
94
94
|
version: Optional[str] = version_flag,
|
|
95
|
+
from_stage: Optional[str] = from_option(
|
|
96
|
+
help="Execute project from given stage instead of using a specific version."
|
|
97
|
+
),
|
|
95
98
|
variables: Optional[List[str]] = variables_flag,
|
|
96
99
|
configuration: Optional[str] = configuration_flag,
|
|
97
100
|
**options,
|
|
@@ -99,10 +102,14 @@ def execute(
|
|
|
99
102
|
"""
|
|
100
103
|
Executes a project.
|
|
101
104
|
"""
|
|
105
|
+
if version and from_stage:
|
|
106
|
+
raise CliError("--version and --from are mutually exclusive.")
|
|
107
|
+
|
|
102
108
|
result = ProjectManager().execute(
|
|
103
109
|
project_name=identifier,
|
|
104
110
|
configuration=configuration,
|
|
105
111
|
version=version,
|
|
112
|
+
from_stage=from_stage,
|
|
106
113
|
variables=variables,
|
|
107
114
|
)
|
|
108
115
|
return QueryJsonValueResult(result)
|
|
@@ -112,6 +119,9 @@ def execute(
|
|
|
112
119
|
def dry_run(
|
|
113
120
|
identifier: FQN = project_identifier,
|
|
114
121
|
version: Optional[str] = version_flag,
|
|
122
|
+
from_stage: Optional[str] = from_option(
|
|
123
|
+
help="Execute project from given stage instead of using a specific version."
|
|
124
|
+
),
|
|
115
125
|
variables: Optional[List[str]] = variables_flag,
|
|
116
126
|
configuration: Optional[str] = configuration_flag,
|
|
117
127
|
**options,
|
|
@@ -119,10 +129,14 @@ def dry_run(
|
|
|
119
129
|
"""
|
|
120
130
|
Validates a project.
|
|
121
131
|
"""
|
|
132
|
+
if version and from_stage:
|
|
133
|
+
raise CliError("--version and --from are mutually exclusive.")
|
|
134
|
+
|
|
122
135
|
result = ProjectManager().execute(
|
|
123
136
|
project_name=identifier,
|
|
124
137
|
configuration=configuration,
|
|
125
138
|
version=version,
|
|
139
|
+
from_stage=from_stage,
|
|
126
140
|
dry_run=True,
|
|
127
141
|
variables=variables,
|
|
128
142
|
)
|
|
@@ -181,7 +195,9 @@ def create(
|
|
|
181
195
|
@with_project_definition()
|
|
182
196
|
def add_version(
|
|
183
197
|
entity_id: str = entity_argument("project"),
|
|
184
|
-
_from: Optional[str] = from_option(
|
|
198
|
+
_from: Optional[str] = from_option(
|
|
199
|
+
help="Create a new version using given stage instead of uploading local files."
|
|
200
|
+
),
|
|
185
201
|
_alias: Optional[str] = typer.Option(
|
|
186
202
|
None, "--alias", help="Alias for the version.", show_default=False
|
|
187
203
|
),
|
|
@@ -207,7 +223,7 @@ def add_version(
|
|
|
207
223
|
om = ObjectManager()
|
|
208
224
|
if not om.object_exists(object_type="project", fqn=project.fqn):
|
|
209
225
|
raise CliError(
|
|
210
|
-
f"Project '{project.fqn}' does not exist. Use `project create` command first"
|
|
226
|
+
f"Project '{project.fqn}' does not exist. Use `project create` command first."
|
|
211
227
|
)
|
|
212
228
|
ProjectManager().add_version(
|
|
213
229
|
project=project,
|
|
@@ -218,7 +234,7 @@ def add_version(
|
|
|
218
234
|
)
|
|
219
235
|
alias_str = "" if _alias is None else f"'{_alias}' "
|
|
220
236
|
return MessageResult(
|
|
221
|
-
f"New project version {alias_str}added to project '{project.fqn}'"
|
|
237
|
+
f"New project version {alias_str}added to project '{project.fqn}'."
|
|
222
238
|
)
|
|
223
239
|
|
|
224
240
|
|
|
@@ -233,3 +249,34 @@ def list_versions(
|
|
|
233
249
|
pm = ProjectManager()
|
|
234
250
|
results = pm.list_versions(project_name=identifier)
|
|
235
251
|
return QueryResult(results)
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
@app.command(requires_connection=True)
|
|
255
|
+
def drop_version(
|
|
256
|
+
identifier: FQN = project_identifier,
|
|
257
|
+
version_name: str = typer.Argument(
|
|
258
|
+
help="Name or alias of the version to drop. For names containing '$', use single quotes to prevent shell expansion (e.g., 'VERSION$1').",
|
|
259
|
+
show_default=False,
|
|
260
|
+
),
|
|
261
|
+
if_exists: bool = IfExistsOption(help="Do nothing if the version does not exist."),
|
|
262
|
+
**options,
|
|
263
|
+
):
|
|
264
|
+
"""
|
|
265
|
+
Drops a version from the project.
|
|
266
|
+
"""
|
|
267
|
+
# Detect potential shell expansion issues
|
|
268
|
+
if version_name and version_name.upper() == "VERSION":
|
|
269
|
+
cli_console.warning(
|
|
270
|
+
f"Version name '{version_name}' might be truncated due to shell expansion. "
|
|
271
|
+
f"If you meant to use a version like 'VERSION$1', try using single quotes: 'VERSION$1'."
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
pm = ProjectManager()
|
|
275
|
+
pm.drop_version(
|
|
276
|
+
project_name=identifier,
|
|
277
|
+
version_name=version_name,
|
|
278
|
+
if_exists=if_exists,
|
|
279
|
+
)
|
|
280
|
+
return MessageResult(
|
|
281
|
+
f"Version '{version_name}' dropped from project '{identifier}'."
|
|
282
|
+
)
|
|
@@ -33,6 +33,7 @@ class ProjectManager(SqlExecutionMixin):
|
|
|
33
33
|
project_name: FQN,
|
|
34
34
|
configuration: str | None = None,
|
|
35
35
|
version: str | None = None,
|
|
36
|
+
from_stage: str | None = None,
|
|
36
37
|
variables: List[str] | None = None,
|
|
37
38
|
dry_run: bool = False,
|
|
38
39
|
):
|
|
@@ -47,6 +48,9 @@ class ProjectManager(SqlExecutionMixin):
|
|
|
47
48
|
).removeprefix(" using")
|
|
48
49
|
if version:
|
|
49
50
|
query += f" WITH VERSION {version}"
|
|
51
|
+
elif from_stage:
|
|
52
|
+
stage_path = StagePath.from_stage_str(from_stage)
|
|
53
|
+
query += f" FROM {stage_path.absolute_path()}"
|
|
50
54
|
if dry_run:
|
|
51
55
|
query += " DRY_RUN=TRUE"
|
|
52
56
|
return self.execute_query(query=query)
|
|
@@ -72,7 +76,7 @@ class ProjectManager(SqlExecutionMixin):
|
|
|
72
76
|
stage_path = StagePath.from_stage_str(from_stage)
|
|
73
77
|
query = f"ALTER PROJECT {project_name.identifier} ADD VERSION"
|
|
74
78
|
if alias:
|
|
75
|
-
query += f
|
|
79
|
+
query += f" IF NOT EXISTS {alias}"
|
|
76
80
|
query += f" FROM {stage_path.absolute_path(at_prefix=True)}"
|
|
77
81
|
if comment:
|
|
78
82
|
query += f" COMMENT = '{comment}'"
|
|
@@ -113,3 +117,18 @@ class ProjectManager(SqlExecutionMixin):
|
|
|
113
117
|
def list_versions(self, project_name: FQN):
|
|
114
118
|
query = f"SHOW VERSIONS IN PROJECT {project_name.identifier}"
|
|
115
119
|
return self.execute_query(query=query)
|
|
120
|
+
|
|
121
|
+
def drop_version(
|
|
122
|
+
self,
|
|
123
|
+
project_name: FQN,
|
|
124
|
+
version_name: str,
|
|
125
|
+
if_exists: bool = False,
|
|
126
|
+
):
|
|
127
|
+
"""
|
|
128
|
+
Drops a version from the project.
|
|
129
|
+
"""
|
|
130
|
+
query = f"ALTER PROJECT {project_name.identifier} DROP VERSION"
|
|
131
|
+
if if_exists:
|
|
132
|
+
query += " IF EXISTS"
|
|
133
|
+
query += f" {version_name}"
|
|
134
|
+
return self.execute_query(query=query)
|
|
@@ -79,7 +79,6 @@ class SnowparkObjectManager(SqlExecutionMixin):
|
|
|
79
79
|
) -> str:
|
|
80
80
|
entity.imports.extend(artifact_files)
|
|
81
81
|
imports = [f"'{x}'" for x in entity.imports]
|
|
82
|
-
packages_list = ",".join(f"'{p}'" for p in snowflake_dependencies)
|
|
83
82
|
|
|
84
83
|
object_type = entity.get_type()
|
|
85
84
|
|
|
@@ -91,7 +90,6 @@ class SnowparkObjectManager(SqlExecutionMixin):
|
|
|
91
90
|
f"runtime_version={entity.runtime or DEFAULT_RUNTIME}",
|
|
92
91
|
f"imports=({', '.join(imports)})",
|
|
93
92
|
f"handler='{entity.handler}'",
|
|
94
|
-
f"packages=({packages_list})",
|
|
95
93
|
]
|
|
96
94
|
|
|
97
95
|
if entity.external_access_integrations:
|
|
@@ -100,17 +98,24 @@ class SnowparkObjectManager(SqlExecutionMixin):
|
|
|
100
98
|
if entity.secrets:
|
|
101
99
|
query.append(entity.get_secrets_sql())
|
|
102
100
|
|
|
103
|
-
if
|
|
104
|
-
|
|
101
|
+
if entity.artifact_repository_packages and entity.packages:
|
|
102
|
+
raise UsageError(
|
|
103
|
+
"You cannot specify both artifact_repository_packages and packages.",
|
|
104
|
+
)
|
|
105
105
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
106
|
+
packages_list = snowflake_dependencies.copy()
|
|
107
|
+
if entity.artifact_repository and (
|
|
108
|
+
entity.artifact_repository_packages or entity.packages
|
|
109
|
+
):
|
|
110
|
+
if entity.artifact_repository_packages:
|
|
111
|
+
packages_list.extend(entity.artifact_repository_packages)
|
|
112
|
+
else:
|
|
113
|
+
packages_list.extend(entity.packages)
|
|
114
|
+
query.append(
|
|
115
|
+
f"ARTIFACT_REPOSITORY= {entity.artifact_repository}",
|
|
113
116
|
)
|
|
117
|
+
packages = [f"'{item}'" for item in packages_list]
|
|
118
|
+
query.append(f"packages=({','.join(packages)})")
|
|
114
119
|
|
|
115
120
|
if entity.resource_constraint:
|
|
116
121
|
constraints = ",".join(
|
|
@@ -118,6 +123,9 @@ class SnowparkObjectManager(SqlExecutionMixin):
|
|
|
118
123
|
)
|
|
119
124
|
query.append(f"RESOURCE_CONSTRAINT=({constraints})")
|
|
120
125
|
|
|
126
|
+
if isinstance(entity, ProcedureEntityModel) and entity.execute_as_caller:
|
|
127
|
+
query.append("execute as caller")
|
|
128
|
+
|
|
121
129
|
return self.execute_query("\n".join(query))
|
|
122
130
|
|
|
123
131
|
def deploy_entity(
|
|
@@ -215,6 +223,10 @@ def _check_if_replace_is_required(
|
|
|
215
223
|
)
|
|
216
224
|
return True
|
|
217
225
|
|
|
226
|
+
if entity.packages != resource_json.get("artifact_repository_packages", None):
|
|
227
|
+
log.info("Packages do not match. Replacing the %s", object_type)
|
|
228
|
+
return True
|
|
229
|
+
|
|
218
230
|
if isinstance(entity, ProcedureEntityModel):
|
|
219
231
|
if resource_json.get("execute as", "OWNER") != (
|
|
220
232
|
"CALLER" if entity.execute_as_caller else "OWNER"
|
|
@@ -173,20 +173,28 @@ class SnowparkEntity(EntityBase[Generic[T]]):
|
|
|
173
173
|
if self.model.secrets:
|
|
174
174
|
query.append(self.model.get_secrets_sql())
|
|
175
175
|
|
|
176
|
-
if self.model.
|
|
177
|
-
|
|
176
|
+
if self.model.artifact_repository and (
|
|
177
|
+
self.model.artifact_repository_packages or self.model.packages
|
|
178
|
+
):
|
|
179
|
+
if self.model.artifact_repository_packages:
|
|
180
|
+
packages = [
|
|
181
|
+
f"'{item}'" for item in self.model.artifact_repository_packages
|
|
182
|
+
]
|
|
183
|
+
else:
|
|
184
|
+
packages = [f"'{item}'" for item in self.model.packages]
|
|
178
185
|
|
|
179
|
-
if self.model.artifact_repository and self.model.artifact_repository_packages:
|
|
180
|
-
packages = [f"'{item}'" for item in self.model.artifact_repository_packages]
|
|
181
186
|
query.extend(
|
|
182
187
|
[
|
|
183
188
|
f"ARTIFACT_REPOSITORY= {self.model.artifact_repository} ",
|
|
184
|
-
f"
|
|
189
|
+
f"PACKAGES=({','.join(packages)})",
|
|
185
190
|
]
|
|
186
191
|
)
|
|
187
192
|
if self.model.resource_constraint:
|
|
188
193
|
query.append(self._get_resource_constraints_sql())
|
|
189
194
|
|
|
195
|
+
if self.model.type == "procedure" and self.model.execute_as_caller:
|
|
196
|
+
query.append("EXECUTE AS CALLER")
|
|
197
|
+
|
|
190
198
|
return "\n".join(query)
|
|
191
199
|
|
|
192
200
|
def get_execute_sql(self, execution_arguments: List[str] | None = None):
|
|
@@ -54,6 +54,9 @@ class SnowparkEntityModel(
|
|
|
54
54
|
default=None, title="Artifact repository to be used"
|
|
55
55
|
)
|
|
56
56
|
artifact_repository_packages: Optional[List[str]] = Field(
|
|
57
|
+
default=None, title="Alias for packages"
|
|
58
|
+
)
|
|
59
|
+
packages: Optional[List[str]] = Field(
|
|
57
60
|
default=None, title="Packages to be installed from artifact repository"
|
|
58
61
|
)
|
|
59
62
|
|
|
@@ -91,9 +94,14 @@ class SnowparkEntityModel(
|
|
|
91
94
|
def check_artifact_repository(cls, values: dict) -> dict:
|
|
92
95
|
artifact_repository = values.get("artifact_repository")
|
|
93
96
|
artifact_repository_packages = values.get("artifact_repository_packages")
|
|
94
|
-
|
|
97
|
+
packages = values.get("packages")
|
|
98
|
+
if artifact_repository_packages and packages:
|
|
99
|
+
raise ValueError(
|
|
100
|
+
"You cannot specify both artifact_repository_packages and packages.",
|
|
101
|
+
)
|
|
102
|
+
if (artifact_repository_packages or packages) and not artifact_repository:
|
|
95
103
|
raise ValueError(
|
|
96
|
-
"You specified
|
|
104
|
+
"You specified packages / artifact_repository_packages without setting artifact_repository.",
|
|
97
105
|
)
|
|
98
106
|
return values
|
|
99
107
|
|
|
@@ -15,11 +15,13 @@
|
|
|
15
15
|
from __future__ import annotations
|
|
16
16
|
|
|
17
17
|
import sys
|
|
18
|
+
from enum import Enum
|
|
18
19
|
from logging import getLogger
|
|
19
20
|
from pathlib import Path
|
|
20
21
|
from typing import List, Optional
|
|
21
22
|
|
|
22
23
|
import typer
|
|
24
|
+
from click import UsageError
|
|
23
25
|
from snowflake.cli._plugins.sql.manager import SqlManager
|
|
24
26
|
from snowflake.cli.api.commands.decorators import with_project_definition
|
|
25
27
|
from snowflake.cli.api.commands.flags import (
|
|
@@ -34,6 +36,7 @@ from snowflake.cli.api.output.types import (
|
|
|
34
36
|
MultipleResults,
|
|
35
37
|
QueryResult,
|
|
36
38
|
)
|
|
39
|
+
from snowflake.cli.api.rendering.sql_templates import SQLTemplateSyntaxConfig
|
|
37
40
|
|
|
38
41
|
logger = getLogger(__name__)
|
|
39
42
|
# simple Typer with defaults because it won't become a command group as it contains only one command
|
|
@@ -46,6 +49,37 @@ SourceOption = OverrideableOption(
|
|
|
46
49
|
)
|
|
47
50
|
|
|
48
51
|
|
|
52
|
+
class _EnabledTemplating(str, Enum):
|
|
53
|
+
LEGACY = "LEGACY"
|
|
54
|
+
STANDARD = "STANDARD"
|
|
55
|
+
JINJA = "JINJA"
|
|
56
|
+
ALL = "ALL"
|
|
57
|
+
NONE = "NONE"
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _parse_template_syntax_config(
|
|
61
|
+
enabled_syntaxes: List[_EnabledTemplating],
|
|
62
|
+
) -> SQLTemplateSyntaxConfig:
|
|
63
|
+
if (
|
|
64
|
+
_EnabledTemplating.ALL in enabled_syntaxes
|
|
65
|
+
or _EnabledTemplating.NONE in enabled_syntaxes
|
|
66
|
+
) and len(enabled_syntaxes) > 1:
|
|
67
|
+
raise UsageError(
|
|
68
|
+
"ALL and NONE template syntax options should not be used with other options."
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
if _EnabledTemplating.ALL in enabled_syntaxes:
|
|
72
|
+
return SQLTemplateSyntaxConfig(True, True, True)
|
|
73
|
+
if _EnabledTemplating.NONE in enabled_syntaxes:
|
|
74
|
+
return SQLTemplateSyntaxConfig(False, False, False)
|
|
75
|
+
|
|
76
|
+
result = SQLTemplateSyntaxConfig()
|
|
77
|
+
result.enable_legacy_syntax = _EnabledTemplating.LEGACY in enabled_syntaxes
|
|
78
|
+
result.enable_standard_syntax = _EnabledTemplating.STANDARD in enabled_syntaxes
|
|
79
|
+
result.enable_jinja_syntax = _EnabledTemplating.JINJA in enabled_syntaxes
|
|
80
|
+
return result
|
|
81
|
+
|
|
82
|
+
|
|
49
83
|
@app.command(name="sql", requires_connection=True, no_args_is_help=False)
|
|
50
84
|
@with_project_definition(is_optional=True)
|
|
51
85
|
def execute_sql(
|
|
@@ -83,6 +117,12 @@ def execute_sql(
|
|
|
83
117
|
flag_value=False,
|
|
84
118
|
is_flag=True,
|
|
85
119
|
),
|
|
120
|
+
enabled_templating: List[_EnabledTemplating] = typer.Option(
|
|
121
|
+
[_EnabledTemplating.LEGACY, _EnabledTemplating.STANDARD],
|
|
122
|
+
"--enable-templating",
|
|
123
|
+
help="Syntax used to resolve variables before passing queries to Snowflake.",
|
|
124
|
+
case_sensitive=False,
|
|
125
|
+
),
|
|
86
126
|
**options,
|
|
87
127
|
) -> CommandResult:
|
|
88
128
|
"""
|
|
@@ -100,6 +140,8 @@ def execute_sql(
|
|
|
100
140
|
if data_override:
|
|
101
141
|
data = {v.key: v.value for v in parse_key_value_variables(data_override)}
|
|
102
142
|
|
|
143
|
+
template_syntax_config = _parse_template_syntax_config(enabled_templating)
|
|
144
|
+
|
|
103
145
|
retain_comments = bool(retain_comments)
|
|
104
146
|
single_transaction = bool(single_transaction)
|
|
105
147
|
std_in = bool(std_in)
|
|
@@ -116,7 +158,12 @@ def execute_sql(
|
|
|
116
158
|
raise CliArgumentError("single transaction cannot be used with REPL")
|
|
117
159
|
from snowflake.cli._plugins.sql.repl import Repl
|
|
118
160
|
|
|
119
|
-
Repl(
|
|
161
|
+
Repl(
|
|
162
|
+
SqlManager(),
|
|
163
|
+
data=data,
|
|
164
|
+
retain_comments=retain_comments,
|
|
165
|
+
template_syntax_config=template_syntax_config,
|
|
166
|
+
).run()
|
|
120
167
|
sys.exit(0)
|
|
121
168
|
|
|
122
169
|
manager = SqlManager()
|
|
@@ -128,6 +175,7 @@ def execute_sql(
|
|
|
128
175
|
data=data,
|
|
129
176
|
retain_comments=retain_comments,
|
|
130
177
|
single_transaction=single_transaction,
|
|
178
|
+
template_syntax_config=template_syntax_config,
|
|
131
179
|
)
|
|
132
180
|
if expected_results_cnt == 0:
|
|
133
181
|
# case expected if input only scheduled async queries
|
|
@@ -32,7 +32,10 @@ from snowflake.cli.api.cli_global_context import get_cli_context
|
|
|
32
32
|
from snowflake.cli.api.console import cli_console
|
|
33
33
|
from snowflake.cli.api.exceptions import CliArgumentError, CliSqlError
|
|
34
34
|
from snowflake.cli.api.output.types import CollectionResult
|
|
35
|
-
from snowflake.cli.api.rendering.sql_templates import
|
|
35
|
+
from snowflake.cli.api.rendering.sql_templates import (
|
|
36
|
+
SQLTemplateSyntaxConfig,
|
|
37
|
+
snowflake_sql_jinja_render,
|
|
38
|
+
)
|
|
36
39
|
from snowflake.cli.api.secure_path import SecurePath
|
|
37
40
|
from snowflake.cli.api.sql_execution import SqlExecutionMixin, VerboseCursor
|
|
38
41
|
from snowflake.connector.cursor import SnowflakeCursor
|
|
@@ -51,6 +54,7 @@ class SqlManager(SqlExecutionMixin):
|
|
|
51
54
|
data: Dict | None = None,
|
|
52
55
|
retain_comments: bool = False,
|
|
53
56
|
single_transaction: bool = False,
|
|
57
|
+
template_syntax_config: SQLTemplateSyntaxConfig = SQLTemplateSyntaxConfig(),
|
|
54
58
|
) -> Tuple[ExpectedResultsCount, Iterable[SnowflakeCursor]]:
|
|
55
59
|
"""Reads, transforms and execute statements from input.
|
|
56
60
|
|
|
@@ -62,9 +66,15 @@ class SqlManager(SqlExecutionMixin):
|
|
|
62
66
|
"""
|
|
63
67
|
query = sys.stdin.read() if std_in else query
|
|
64
68
|
|
|
65
|
-
stmt_operators =
|
|
66
|
-
|
|
67
|
-
|
|
69
|
+
stmt_operators = []
|
|
70
|
+
if template_syntax_config.enable_legacy_syntax:
|
|
71
|
+
stmt_operators.append(transpile_snowsql_templates)
|
|
72
|
+
stmt_operators.append(
|
|
73
|
+
partial(
|
|
74
|
+
snowflake_sql_jinja_render,
|
|
75
|
+
template_syntax_config=template_syntax_config,
|
|
76
|
+
data=data,
|
|
77
|
+
)
|
|
68
78
|
)
|
|
69
79
|
remove_comments = not retain_comments
|
|
70
80
|
|
|
@@ -13,6 +13,7 @@ from snowflake.cli._plugins.sql.manager import SqlManager
|
|
|
13
13
|
from snowflake.cli.api.cli_global_context import get_cli_context_manager
|
|
14
14
|
from snowflake.cli.api.console import cli_console
|
|
15
15
|
from snowflake.cli.api.output.types import MultipleResults, QueryResult
|
|
16
|
+
from snowflake.cli.api.rendering.sql_templates import SQLTemplateSyntaxConfig
|
|
16
17
|
from snowflake.cli.api.secure_path import SecurePath
|
|
17
18
|
from snowflake.connector.config_manager import CONFIG_MANAGER
|
|
18
19
|
from snowflake.connector.cursor import SnowflakeCursor
|
|
@@ -35,6 +36,7 @@ class Repl:
|
|
|
35
36
|
sql_manager: SqlManager,
|
|
36
37
|
data: dict | None = None,
|
|
37
38
|
retain_comments: bool = False,
|
|
39
|
+
template_syntax_config: SQLTemplateSyntaxConfig = SQLTemplateSyntaxConfig(),
|
|
38
40
|
):
|
|
39
41
|
"""Requires a `SqlManager` instance to execute queries.
|
|
40
42
|
|
|
@@ -46,6 +48,7 @@ class Repl:
|
|
|
46
48
|
setattr(get_cli_context_manager(), "is_repl", True)
|
|
47
49
|
self._data = data or {}
|
|
48
50
|
self._retain_comments = retain_comments
|
|
51
|
+
self._template_syntax_config = template_syntax_config
|
|
49
52
|
self._history = FileHistory(HISTORY_FILE)
|
|
50
53
|
self._lexer = PygmentsLexer(CliLexer)
|
|
51
54
|
self._completer = cli_completer
|
|
@@ -155,6 +158,7 @@ class Repl:
|
|
|
155
158
|
std_in=False,
|
|
156
159
|
data=self._data,
|
|
157
160
|
retain_comments=self._retain_comments,
|
|
161
|
+
template_syntax_config=self._template_syntax_config,
|
|
158
162
|
)
|
|
159
163
|
return cursors
|
|
160
164
|
|
|
@@ -48,7 +48,6 @@ from snowflake.cli.api.commands.snow_typer import SnowTyperFactory
|
|
|
48
48
|
from snowflake.cli.api.console import cli_console
|
|
49
49
|
from snowflake.cli.api.constants import ObjectType
|
|
50
50
|
from snowflake.cli.api.identifiers import FQN
|
|
51
|
-
from snowflake.cli.api.output.formats import OutputFormat
|
|
52
51
|
from snowflake.cli.api.output.types import (
|
|
53
52
|
CollectionResult,
|
|
54
53
|
CommandResult,
|
|
@@ -74,7 +73,7 @@ add_object_command_aliases(
|
|
|
74
73
|
object_type=ObjectType.STAGE,
|
|
75
74
|
name_argument=StageNameArgument,
|
|
76
75
|
like_option=like_option(
|
|
77
|
-
help_example='`list --like "my%"` lists all stages that begin with
|
|
76
|
+
help_example='`list --like "my%"` lists all stages that begin with "my"',
|
|
78
77
|
),
|
|
79
78
|
scope_option=scope_option(help_example="`list --in database my_db`"),
|
|
80
79
|
)
|
|
@@ -98,7 +97,7 @@ def copy(
|
|
|
98
97
|
show_default=False,
|
|
99
98
|
),
|
|
100
99
|
destination_path: str = typer.Argument(
|
|
101
|
-
help="Target directory path for copy operation.
|
|
100
|
+
help="Target directory path for copy operation.",
|
|
102
101
|
show_default=False,
|
|
103
102
|
),
|
|
104
103
|
overwrite: bool = typer.Option(
|
|
@@ -117,19 +116,24 @@ def copy(
|
|
|
117
116
|
default=False,
|
|
118
117
|
help="Specifies whether Snowflake uses gzip to compress files during upload. Ignored when downloading.",
|
|
119
118
|
),
|
|
119
|
+
refresh: bool = typer.Option(
|
|
120
|
+
default=False,
|
|
121
|
+
help="Specifies whether ALTER STAGE {name} REFRESH should be executed after uploading.",
|
|
122
|
+
),
|
|
120
123
|
**options,
|
|
121
124
|
) -> CommandResult:
|
|
122
125
|
"""
|
|
123
|
-
Copies all files from
|
|
124
|
-
to and downloading files from the stage.
|
|
126
|
+
Copies all files from source path to target directory. This works for uploading
|
|
127
|
+
to and downloading files from the stage, and copying between named stages.
|
|
125
128
|
"""
|
|
126
129
|
is_get = is_stage_path(source_path)
|
|
127
130
|
is_put = is_stage_path(destination_path)
|
|
128
131
|
|
|
129
132
|
if is_get and is_put:
|
|
130
|
-
|
|
131
|
-
|
|
133
|
+
cursor = StageManager().copy_files(
|
|
134
|
+
source_path=source_path, destination_path=destination_path
|
|
132
135
|
)
|
|
136
|
+
return QueryResult(cursor)
|
|
133
137
|
if not is_get and not is_put:
|
|
134
138
|
raise click.ClickException(
|
|
135
139
|
"Both source and target path are local. This operation is not supported."
|
|
@@ -149,6 +153,7 @@ def copy(
|
|
|
149
153
|
parallel=parallel,
|
|
150
154
|
overwrite=overwrite,
|
|
151
155
|
auto_compress=auto_compress,
|
|
156
|
+
refresh=refresh,
|
|
152
157
|
)
|
|
153
158
|
|
|
154
159
|
|
|
@@ -160,12 +165,19 @@ def stage_create(
|
|
|
160
165
|
"--encryption",
|
|
161
166
|
help="Type of encryption supported for all files stored on the stage.",
|
|
162
167
|
),
|
|
168
|
+
enable_directory: bool = typer.Option(
|
|
169
|
+
False,
|
|
170
|
+
"--enable-directory",
|
|
171
|
+
help="Specifies whether directory support is enabled for the stage.",
|
|
172
|
+
),
|
|
163
173
|
**options,
|
|
164
174
|
) -> CommandResult:
|
|
165
175
|
"""
|
|
166
176
|
Creates a named stage if it does not already exist.
|
|
167
177
|
"""
|
|
168
|
-
cursor = StageManager().create(
|
|
178
|
+
cursor = StageManager().create(
|
|
179
|
+
fqn=stage_name, encryption=encryption, enable_directory=enable_directory
|
|
180
|
+
)
|
|
169
181
|
return SingleQueryResult(cursor)
|
|
170
182
|
|
|
171
183
|
|
|
@@ -206,7 +218,7 @@ def stage_diff(
|
|
|
206
218
|
local_root=Path(folder_name),
|
|
207
219
|
stage_path=StageManager.stage_path_parts_from_str(stage_name), # noqa: SLF001
|
|
208
220
|
)
|
|
209
|
-
if get_cli_context().output_format
|
|
221
|
+
if get_cli_context().output_format.is_json:
|
|
210
222
|
return ObjectResult(diff.to_dict())
|
|
211
223
|
else:
|
|
212
224
|
print_diff_to_console(diff)
|
|
@@ -264,6 +276,7 @@ def _put(
|
|
|
264
276
|
parallel: int,
|
|
265
277
|
overwrite: bool,
|
|
266
278
|
auto_compress: bool,
|
|
279
|
+
refresh: bool,
|
|
267
280
|
):
|
|
268
281
|
if recursive and not source_path.is_file():
|
|
269
282
|
cursor_generator = StageManager().put_recursive(
|
|
@@ -273,7 +286,7 @@ def _put(
|
|
|
273
286
|
parallel=parallel,
|
|
274
287
|
auto_compress=auto_compress,
|
|
275
288
|
)
|
|
276
|
-
|
|
289
|
+
output = CollectionResult(cursor_generator)
|
|
277
290
|
else:
|
|
278
291
|
cursor = StageManager().put(
|
|
279
292
|
local_path=source_path.resolve(),
|
|
@@ -282,4 +295,10 @@ def _put(
|
|
|
282
295
|
parallel=parallel,
|
|
283
296
|
auto_compress=auto_compress,
|
|
284
297
|
)
|
|
285
|
-
|
|
298
|
+
output = QueryResult(cursor)
|
|
299
|
+
|
|
300
|
+
if refresh:
|
|
301
|
+
StageManager().refresh(
|
|
302
|
+
StageManager.stage_path_parts_from_str(destination_path).stage_name
|
|
303
|
+
)
|
|
304
|
+
return output
|
|
@@ -239,6 +239,7 @@ def sync_local_diff_with_stage(
|
|
|
239
239
|
deploy_root_path: Path,
|
|
240
240
|
diff_result: DiffResult,
|
|
241
241
|
stage_full_path: str,
|
|
242
|
+
force_overwrite: bool = False,
|
|
242
243
|
):
|
|
243
244
|
"""
|
|
244
245
|
Syncs a given local directory's contents with a Snowflake stage, including removing old files, and re-uploading modified and new files.
|
|
@@ -267,6 +268,7 @@ def sync_local_diff_with_stage(
|
|
|
267
268
|
deploy_root_path=deploy_root_path,
|
|
268
269
|
stage_paths=diff_result.only_local,
|
|
269
270
|
role=role,
|
|
271
|
+
overwrite=force_overwrite,
|
|
270
272
|
)
|
|
271
273
|
except Exception as err:
|
|
272
274
|
# Could be ProgrammingError or IntegrityError from SnowflakeCursor
|