snowflake-cli 3.5.0__py3-none-any.whl → 3.6.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 +13 -1
- snowflake/cli/_app/commands_registration/builtin_plugins.py +2 -0
- snowflake/cli/_app/snow_connector.py +5 -4
- snowflake/cli/_app/telemetry.py +3 -15
- snowflake/cli/_app/version_check.py +4 -4
- snowflake/cli/_plugins/auth/__init__.py +11 -0
- snowflake/cli/_plugins/auth/keypair/__init__.py +0 -0
- snowflake/cli/_plugins/auth/keypair/commands.py +151 -0
- snowflake/cli/_plugins/auth/keypair/manager.py +331 -0
- snowflake/cli/_plugins/auth/keypair/plugin_spec.py +30 -0
- snowflake/cli/_plugins/connection/commands.py +77 -1
- snowflake/cli/_plugins/nativeapp/entities/application.py +4 -1
- snowflake/cli/_plugins/nativeapp/sf_sql_facade.py +33 -6
- snowflake/cli/_plugins/object/command_aliases.py +3 -1
- snowflake/cli/_plugins/object/manager.py +4 -2
- snowflake/cli/_plugins/project/commands.py +16 -0
- snowflake/cli/_plugins/spcs/compute_pool/commands.py +17 -5
- snowflake/cli/_plugins/sql/manager.py +42 -51
- snowflake/cli/_plugins/sql/source_reader.py +230 -0
- snowflake/cli/_plugins/stage/manager.py +8 -2
- snowflake/cli/api/commands/flags.py +12 -2
- snowflake/cli/api/constants.py +2 -0
- snowflake/cli/api/errno.py +1 -0
- snowflake/cli/api/exceptions.py +7 -0
- snowflake/cli/api/feature_flags.py +1 -0
- snowflake/cli/api/rest_api.py +2 -3
- snowflake/cli/{_app → api}/secret.py +4 -1
- snowflake/cli/api/secure_path.py +16 -4
- snowflake/cli/api/sql_execution.py +7 -3
- {snowflake_cli-3.5.0.dist-info → snowflake_cli-3.6.0.dist-info}/METADATA +7 -7
- {snowflake_cli-3.5.0.dist-info → snowflake_cli-3.6.0.dist-info}/RECORD +34 -28
- {snowflake_cli-3.5.0.dist-info → snowflake_cli-3.6.0.dist-info}/WHEEL +0 -0
- {snowflake_cli-3.5.0.dist-info → snowflake_cli-3.6.0.dist-info}/entry_points.txt +0 -0
- {snowflake_cli-3.5.0.dist-info → snowflake_cli-3.6.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -16,8 +16,9 @@ from __future__ import annotations
|
|
|
16
16
|
|
|
17
17
|
import logging
|
|
18
18
|
import os.path
|
|
19
|
+
from copy import deepcopy
|
|
19
20
|
from pathlib import Path
|
|
20
|
-
from typing import Optional
|
|
21
|
+
from typing import Dict, Optional, Tuple
|
|
21
22
|
|
|
22
23
|
import typer
|
|
23
24
|
from click import ( # type: ignore
|
|
@@ -28,10 +29,14 @@ from click import ( # type: ignore
|
|
|
28
29
|
)
|
|
29
30
|
from click.core import ParameterSource # type: ignore
|
|
30
31
|
from snowflake import connector
|
|
32
|
+
from snowflake.cli._app.snow_connector import connect_to_snowflake
|
|
33
|
+
from snowflake.cli._plugins.auth.keypair.commands import KEY_PAIR_DEFAULT_PATH
|
|
34
|
+
from snowflake.cli._plugins.auth.keypair.manager import AuthManager
|
|
31
35
|
from snowflake.cli._plugins.connection.util import (
|
|
32
36
|
strip_if_value_present,
|
|
33
37
|
)
|
|
34
38
|
from snowflake.cli._plugins.object.manager import ObjectManager
|
|
39
|
+
from snowflake.cli.api import exceptions
|
|
35
40
|
from snowflake.cli.api.cli_global_context import get_cli_context
|
|
36
41
|
from snowflake.cli.api.commands.flags import (
|
|
37
42
|
PLAIN_PASSWORD_MSG,
|
|
@@ -67,6 +72,8 @@ from snowflake.cli.api.output.types import (
|
|
|
67
72
|
MessageResult,
|
|
68
73
|
ObjectResult,
|
|
69
74
|
)
|
|
75
|
+
from snowflake.cli.api.secret import SecretType
|
|
76
|
+
from snowflake.cli.api.secure_path import SecurePath
|
|
70
77
|
from snowflake.connector import ProgrammingError
|
|
71
78
|
from snowflake.connector.constants import CONNECTIONS_FILE
|
|
72
79
|
|
|
@@ -282,6 +289,13 @@ def add(
|
|
|
282
289
|
if connection_exists(connection_name):
|
|
283
290
|
raise UsageError(f"Connection {connection_name} already exists")
|
|
284
291
|
|
|
292
|
+
if not no_interactive:
|
|
293
|
+
connection_options, keypair_error = _extend_add_with_key_pair(
|
|
294
|
+
connection_name, connection_options
|
|
295
|
+
)
|
|
296
|
+
else:
|
|
297
|
+
keypair_error = ""
|
|
298
|
+
|
|
285
299
|
connections_file = add_connection_to_proper_file(
|
|
286
300
|
connection_name,
|
|
287
301
|
ConnectionConfig(**connection_options),
|
|
@@ -289,6 +303,12 @@ def add(
|
|
|
289
303
|
if set_as_default:
|
|
290
304
|
set_config_value(path=["default_connection_name"], value=connection_name)
|
|
291
305
|
|
|
306
|
+
if keypair_error:
|
|
307
|
+
return MessageResult(
|
|
308
|
+
f"Wrote new password-based connection {connection_name} to {connections_file}, "
|
|
309
|
+
f"however there were some issues during key pair setup. Review the following error and check 'snow auth keypair' "
|
|
310
|
+
f"commands to setup key pair authentication:\n * {keypair_error}"
|
|
311
|
+
)
|
|
292
312
|
return MessageResult(
|
|
293
313
|
f"Wrote new connection {connection_name} to {connections_file}"
|
|
294
314
|
)
|
|
@@ -402,3 +422,59 @@ def generate_jwt(
|
|
|
402
422
|
return MessageResult(token)
|
|
403
423
|
except (ValueError, TypeError) as err:
|
|
404
424
|
raise ClickException(str(err))
|
|
425
|
+
|
|
426
|
+
|
|
427
|
+
def _extend_add_with_key_pair(
|
|
428
|
+
connection_name: str, connection_options: Dict
|
|
429
|
+
) -> Tuple[Dict, str]:
|
|
430
|
+
if not _should_extend_with_key_pair(connection_options):
|
|
431
|
+
return connection_options, ""
|
|
432
|
+
|
|
433
|
+
configure_key_pair = typer.confirm(
|
|
434
|
+
"Do you want to configure key pair authentication?",
|
|
435
|
+
default=False,
|
|
436
|
+
)
|
|
437
|
+
if not configure_key_pair:
|
|
438
|
+
return connection_options, ""
|
|
439
|
+
|
|
440
|
+
key_length = typer.prompt(
|
|
441
|
+
"Key length",
|
|
442
|
+
default=2048,
|
|
443
|
+
show_default=True,
|
|
444
|
+
)
|
|
445
|
+
|
|
446
|
+
output_path = typer.prompt(
|
|
447
|
+
"Output path",
|
|
448
|
+
default=KEY_PAIR_DEFAULT_PATH,
|
|
449
|
+
show_default=True,
|
|
450
|
+
value_proc=lambda value: SecurePath(value),
|
|
451
|
+
)
|
|
452
|
+
private_key_passphrase = typer.prompt(
|
|
453
|
+
"Private key passphrase",
|
|
454
|
+
default="",
|
|
455
|
+
hide_input=True,
|
|
456
|
+
show_default=False,
|
|
457
|
+
value_proc=lambda value: SecretType(value),
|
|
458
|
+
)
|
|
459
|
+
connection = connect_to_snowflake(temporary_connection=True, **connection_options)
|
|
460
|
+
try:
|
|
461
|
+
connection_options = AuthManager(connection=connection).extend_connection_add(
|
|
462
|
+
connection_name=connection_name,
|
|
463
|
+
connection_options=deepcopy(connection_options),
|
|
464
|
+
key_length=key_length,
|
|
465
|
+
output_path=output_path,
|
|
466
|
+
private_key_passphrase=private_key_passphrase,
|
|
467
|
+
)
|
|
468
|
+
except exceptions.CouldNotSetKeyPairError:
|
|
469
|
+
return connection_options, "The public key is set already."
|
|
470
|
+
except Exception as e:
|
|
471
|
+
return connection_options, str(e)
|
|
472
|
+
return connection_options, ""
|
|
473
|
+
|
|
474
|
+
|
|
475
|
+
def _should_extend_with_key_pair(connection_options: Dict) -> bool:
|
|
476
|
+
return (
|
|
477
|
+
connection_options.get("password") is not None
|
|
478
|
+
and connection_options.get("private_key_file") is None
|
|
479
|
+
and connection_options.get("private_key_path") is None
|
|
480
|
+
)
|
|
@@ -669,7 +669,7 @@ class ApplicationEntity(EntityBase[ApplicationEntityModel]):
|
|
|
669
669
|
role_to_use=package.role,
|
|
670
670
|
)
|
|
671
671
|
|
|
672
|
-
|
|
672
|
+
create_app_result, warnings = get_snowflake_facade().create_application(
|
|
673
673
|
name=self.name,
|
|
674
674
|
package_name=package.name,
|
|
675
675
|
install_method=install_method,
|
|
@@ -680,6 +680,9 @@ class ApplicationEntity(EntityBase[ApplicationEntityModel]):
|
|
|
680
680
|
warehouse=self.warehouse,
|
|
681
681
|
release_channel=release_channel,
|
|
682
682
|
)
|
|
683
|
+
for warning in warnings:
|
|
684
|
+
self.console.warning(warning)
|
|
685
|
+
return create_app_result
|
|
683
686
|
|
|
684
687
|
@span("update_app_object")
|
|
685
688
|
def create_or_upgrade_app(
|
|
@@ -60,6 +60,7 @@ from snowflake.cli.api.errno import (
|
|
|
60
60
|
CANNOT_DISABLE_MANDATORY_TELEMETRY,
|
|
61
61
|
CANNOT_DISABLE_RELEASE_CHANNELS,
|
|
62
62
|
CANNOT_MODIFY_RELEASE_CHANNEL_ACCOUNTS,
|
|
63
|
+
CANNOT_SET_DEBUG_MODE_WITH_MANIFEST_VERSION,
|
|
63
64
|
DOES_NOT_EXIST_OR_CANNOT_BE_PERFORMED,
|
|
64
65
|
DOES_NOT_EXIST_OR_NOT_AUTHORIZED,
|
|
65
66
|
INSUFFICIENT_PRIVILEGES,
|
|
@@ -854,7 +855,7 @@ class SnowflakeSQLFacade:
|
|
|
854
855
|
debug_mode: bool | None,
|
|
855
856
|
should_authorize_event_sharing: bool | None,
|
|
856
857
|
release_channel: str | None = None,
|
|
857
|
-
) -> list[tuple[str]]:
|
|
858
|
+
) -> tuple[list[tuple[str]], list[str]]:
|
|
858
859
|
"""
|
|
859
860
|
Creates a new application object using an application package,
|
|
860
861
|
running the setup script of the application package
|
|
@@ -868,6 +869,7 @@ class SnowflakeSQLFacade:
|
|
|
868
869
|
@param debug_mode: Whether to enable debug mode; None means not explicitly enabled or disabled
|
|
869
870
|
@param should_authorize_event_sharing: Whether to enable event sharing; None means not explicitly enabled or disabled
|
|
870
871
|
@param release_channel [Optional]: Release channel to use when creating the application
|
|
872
|
+
@return: a tuple containing the result of the create application query and possible warning messages
|
|
871
873
|
"""
|
|
872
874
|
package_name = to_identifier(package_name)
|
|
873
875
|
name = to_identifier(name)
|
|
@@ -875,11 +877,9 @@ class SnowflakeSQLFacade:
|
|
|
875
877
|
|
|
876
878
|
# by default, applications are created in debug mode when possible;
|
|
877
879
|
# this can be overridden in the project definition
|
|
878
|
-
|
|
880
|
+
initial_debug_mode = False
|
|
879
881
|
if install_method.is_dev_mode:
|
|
880
882
|
initial_debug_mode = debug_mode if debug_mode is not None else True
|
|
881
|
-
debug_mode_clause = f"debug_mode = {initial_debug_mode}"
|
|
882
|
-
|
|
883
883
|
authorize_telemetry_clause = ""
|
|
884
884
|
if should_authorize_event_sharing is not None:
|
|
885
885
|
self._log.info(
|
|
@@ -903,13 +903,13 @@ class SnowflakeSQLFacade:
|
|
|
903
903
|
from application package {package_name}
|
|
904
904
|
{using_clause}
|
|
905
905
|
{release_channel_clause}
|
|
906
|
-
{debug_mode_clause}
|
|
907
906
|
{authorize_telemetry_clause}
|
|
908
907
|
comment = {SPECIAL_COMMENT}
|
|
909
908
|
"""
|
|
910
909
|
)
|
|
911
910
|
),
|
|
912
911
|
)
|
|
912
|
+
|
|
913
913
|
except Exception as err:
|
|
914
914
|
if isinstance(err, ProgrammingError):
|
|
915
915
|
if err.errno == APPLICATION_REQUIRES_TELEMETRY_SHARING:
|
|
@@ -927,9 +927,36 @@ class SnowflakeSQLFacade:
|
|
|
927
927
|
f"Failed to create application {name} with the following error message:\n"
|
|
928
928
|
f"{err.msg}"
|
|
929
929
|
) from err
|
|
930
|
+
|
|
930
931
|
handle_unclassified_error(err, f"Failed to create application {name}.")
|
|
931
932
|
|
|
932
|
-
|
|
933
|
+
warnings = []
|
|
934
|
+
try:
|
|
935
|
+
if initial_debug_mode:
|
|
936
|
+
self._sql_executor.execute_query(
|
|
937
|
+
dedent(
|
|
938
|
+
_strip_empty_lines(
|
|
939
|
+
f"""\
|
|
940
|
+
alter application {name}
|
|
941
|
+
set debug_mode = {initial_debug_mode}
|
|
942
|
+
"""
|
|
943
|
+
)
|
|
944
|
+
)
|
|
945
|
+
)
|
|
946
|
+
except Exception as err:
|
|
947
|
+
if (
|
|
948
|
+
isinstance(err, ProgrammingError)
|
|
949
|
+
and err.errno == CANNOT_SET_DEBUG_MODE_WITH_MANIFEST_VERSION
|
|
950
|
+
):
|
|
951
|
+
warnings.append(
|
|
952
|
+
"Did not apply debug mode to application because the manifest version is set to 2 or higher. Please use session debugging instead."
|
|
953
|
+
)
|
|
954
|
+
else:
|
|
955
|
+
warnings.append(
|
|
956
|
+
f"Failed to set debug mode for application {name}. {str(err)}"
|
|
957
|
+
)
|
|
958
|
+
|
|
959
|
+
return create_cursor.fetchall(), warnings
|
|
933
960
|
|
|
934
961
|
def create_application_package(
|
|
935
962
|
self,
|
|
@@ -36,8 +36,10 @@ def add_object_command_aliases(
|
|
|
36
36
|
name_argument: typer.Argument,
|
|
37
37
|
like_option: Optional[typer.Option],
|
|
38
38
|
scope_option: Optional[typer.Option],
|
|
39
|
-
ommit_commands: List[str] =
|
|
39
|
+
ommit_commands: Optional[List[str]] = None,
|
|
40
40
|
):
|
|
41
|
+
if ommit_commands is None:
|
|
42
|
+
ommit_commands = list()
|
|
41
43
|
if "list" not in ommit_commands:
|
|
42
44
|
if not like_option:
|
|
43
45
|
raise ClickException('[like_option] have to be defined for "list" command')
|
|
@@ -58,14 +58,16 @@ class ObjectManager(SqlExecutionMixin):
|
|
|
58
58
|
object_name = _get_object_names(object_type).sf_name
|
|
59
59
|
return self.execute_query(f"drop {object_name} {fqn.sql_identifier}")
|
|
60
60
|
|
|
61
|
-
def describe(self, *, object_type: str, fqn: FQN):
|
|
61
|
+
def describe(self, *, object_type: str, fqn: FQN, **kwargs):
|
|
62
62
|
# Image repository is the only supported object that does not have a DESCRIBE command.
|
|
63
63
|
if object_type == "image-repository":
|
|
64
64
|
raise ClickException(
|
|
65
65
|
f"Describe is currently not supported for object of type image-repository"
|
|
66
66
|
)
|
|
67
67
|
object_name = _get_object_names(object_type).sf_name
|
|
68
|
-
return self.execute_query(
|
|
68
|
+
return self.execute_query(
|
|
69
|
+
f"describe {object_name} {fqn.sql_identifier}", **kwargs
|
|
70
|
+
)
|
|
69
71
|
|
|
70
72
|
def object_exists(self, *, object_type: str, fqn: FQN):
|
|
71
73
|
try:
|
|
@@ -15,6 +15,8 @@
|
|
|
15
15
|
from typing import List, Optional
|
|
16
16
|
|
|
17
17
|
import typer
|
|
18
|
+
from snowflake.cli._plugins.object.command_aliases import add_object_command_aliases
|
|
19
|
+
from snowflake.cli._plugins.object.commands import scope_option
|
|
18
20
|
from snowflake.cli._plugins.project.feature_flags import FeatureFlag
|
|
19
21
|
from snowflake.cli._plugins.project.manager import ProjectManager
|
|
20
22
|
from snowflake.cli._plugins.project.project_entity_model import (
|
|
@@ -27,11 +29,13 @@ from snowflake.cli.api.commands.decorators import with_project_definition
|
|
|
27
29
|
from snowflake.cli.api.commands.flags import (
|
|
28
30
|
entity_argument,
|
|
29
31
|
identifier_argument,
|
|
32
|
+
like_option,
|
|
30
33
|
variables_option,
|
|
31
34
|
)
|
|
32
35
|
from snowflake.cli.api.commands.snow_typer import SnowTyperFactory
|
|
33
36
|
from snowflake.cli.api.commands.utils import get_entity_for_operation
|
|
34
37
|
from snowflake.cli.api.console.console import cli_console
|
|
38
|
+
from snowflake.cli.api.constants import ObjectType
|
|
35
39
|
from snowflake.cli.api.identifiers import FQN
|
|
36
40
|
from snowflake.cli.api.output.types import MessageResult, SingleQueryResult
|
|
37
41
|
from snowflake.cli.api.project.project_paths import ProjectPaths
|
|
@@ -51,6 +55,18 @@ variables_flag = variables_option(
|
|
|
51
55
|
)
|
|
52
56
|
|
|
53
57
|
|
|
58
|
+
add_object_command_aliases(
|
|
59
|
+
app=app,
|
|
60
|
+
object_type=ObjectType.PROJECT,
|
|
61
|
+
name_argument=project_identifier,
|
|
62
|
+
like_option=like_option(
|
|
63
|
+
help_example='`list --like "my%"` lists all projects that begin with “my”'
|
|
64
|
+
),
|
|
65
|
+
scope_option=scope_option(help_example="`list --in database my_db`"),
|
|
66
|
+
ommit_commands=["drop", "create", "describe"],
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
|
|
54
70
|
@app.command(requires_connection=True)
|
|
55
71
|
def execute(
|
|
56
72
|
identifier: FQN = project_identifier,
|
|
@@ -88,9 +88,17 @@ MaxNodesOption = OverrideableOption(
|
|
|
88
88
|
_AUTO_RESUME_HELP = "The compute pool will automatically resume when a service or job is submitted to it."
|
|
89
89
|
|
|
90
90
|
AutoResumeOption = OverrideableOption(
|
|
91
|
-
|
|
92
|
-
"--auto-resume
|
|
91
|
+
False,
|
|
92
|
+
"--auto-resume",
|
|
93
93
|
help=_AUTO_RESUME_HELP,
|
|
94
|
+
mutually_exclusive=["no_auto_resume"],
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
NoAutoResumeOption = OverrideableOption(
|
|
98
|
+
False,
|
|
99
|
+
"--no-auto-resume",
|
|
100
|
+
help=_AUTO_RESUME_HELP,
|
|
101
|
+
mutually_exclusive=["auto_resume"],
|
|
94
102
|
)
|
|
95
103
|
|
|
96
104
|
_AUTO_SUSPEND_SECS_HELP = "Number of seconds of inactivity after which you want Snowflake to automatically suspend the compute pool."
|
|
@@ -126,6 +134,7 @@ def create(
|
|
|
126
134
|
min_nodes: int = MinNodesOption(),
|
|
127
135
|
max_nodes: Optional[int] = MaxNodesOption(),
|
|
128
136
|
auto_resume: bool = AutoResumeOption(),
|
|
137
|
+
no_auto_resume: bool = NoAutoResumeOption(),
|
|
129
138
|
initially_suspended: bool = typer.Option(
|
|
130
139
|
False,
|
|
131
140
|
"--init-suspend/--no-init-suspend",
|
|
@@ -140,13 +149,14 @@ def create(
|
|
|
140
149
|
"""
|
|
141
150
|
Creates a new compute pool.
|
|
142
151
|
"""
|
|
152
|
+
resume_option = True if auto_resume else False if no_auto_resume else True
|
|
143
153
|
max_nodes = validate_and_set_instances(min_nodes, max_nodes, "nodes")
|
|
144
154
|
cursor = ComputePoolManager().create(
|
|
145
155
|
pool_name=name.identifier,
|
|
146
156
|
min_nodes=min_nodes,
|
|
147
157
|
max_nodes=max_nodes,
|
|
148
158
|
instance_family=instance_family,
|
|
149
|
-
auto_resume=
|
|
159
|
+
auto_resume=resume_option,
|
|
150
160
|
initially_suspended=initially_suspended,
|
|
151
161
|
auto_suspend_secs=auto_suspend_secs,
|
|
152
162
|
tags=tags,
|
|
@@ -223,7 +233,8 @@ def set_property(
|
|
|
223
233
|
name: FQN = ComputePoolNameArgument,
|
|
224
234
|
min_nodes: Optional[int] = MinNodesOption(default=None, show_default=False),
|
|
225
235
|
max_nodes: Optional[int] = MaxNodesOption(show_default=False),
|
|
226
|
-
auto_resume:
|
|
236
|
+
auto_resume: bool = AutoResumeOption(default=None, show_default=False),
|
|
237
|
+
no_auto_resume: bool = NoAutoResumeOption(default=None, show_default=False),
|
|
227
238
|
auto_suspend_secs: Optional[int] = AutoSuspendSecsOption(
|
|
228
239
|
default=None, show_default=False
|
|
229
240
|
),
|
|
@@ -235,11 +246,12 @@ def set_property(
|
|
|
235
246
|
"""
|
|
236
247
|
Sets one or more properties for the compute pool.
|
|
237
248
|
"""
|
|
249
|
+
resume_option = True if auto_resume else False if no_auto_resume else None
|
|
238
250
|
cursor = ComputePoolManager().set_property(
|
|
239
251
|
pool_name=name.identifier,
|
|
240
252
|
min_nodes=min_nodes,
|
|
241
253
|
max_nodes=max_nodes,
|
|
242
|
-
auto_resume=
|
|
254
|
+
auto_resume=resume_option,
|
|
243
255
|
auto_suspend_secs=auto_suspend_secs,
|
|
244
256
|
comment=comment,
|
|
245
257
|
)
|
|
@@ -14,23 +14,29 @@
|
|
|
14
14
|
|
|
15
15
|
from __future__ import annotations
|
|
16
16
|
|
|
17
|
+
import logging
|
|
17
18
|
import sys
|
|
18
|
-
from
|
|
19
|
-
from itertools import chain
|
|
19
|
+
from functools import partial
|
|
20
20
|
from pathlib import Path
|
|
21
21
|
from typing import Dict, Iterable, List, Tuple
|
|
22
22
|
|
|
23
23
|
from click import ClickException, UsageError
|
|
24
|
-
from jinja2 import UndefinedError
|
|
25
24
|
from snowflake.cli._plugins.sql.snowsql_templating import transpile_snowsql_templates
|
|
25
|
+
from snowflake.cli._plugins.sql.source_reader import (
|
|
26
|
+
compile_statements,
|
|
27
|
+
files_reader,
|
|
28
|
+
query_reader,
|
|
29
|
+
)
|
|
30
|
+
from snowflake.cli.api.console import cli_console
|
|
26
31
|
from snowflake.cli.api.rendering.sql_templates import snowflake_sql_jinja_render
|
|
27
|
-
from snowflake.cli.api.secure_path import
|
|
32
|
+
from snowflake.cli.api.secure_path import SecurePath
|
|
28
33
|
from snowflake.cli.api.sql_execution import SqlExecutionMixin, VerboseCursor
|
|
29
34
|
from snowflake.connector.cursor import SnowflakeCursor
|
|
30
|
-
from snowflake.connector.util_text import split_statements
|
|
31
35
|
|
|
32
36
|
IsSingleStatement = bool
|
|
33
37
|
|
|
38
|
+
logger = logging.getLogger(__name__)
|
|
39
|
+
|
|
34
40
|
|
|
35
41
|
class SqlManager(SqlExecutionMixin):
|
|
36
42
|
def execute(
|
|
@@ -41,57 +47,42 @@ class SqlManager(SqlExecutionMixin):
|
|
|
41
47
|
data: Dict | None = None,
|
|
42
48
|
retain_comments: bool = False,
|
|
43
49
|
) -> Tuple[IsSingleStatement, Iterable[SnowflakeCursor]]:
|
|
44
|
-
|
|
45
|
-
# Check if any two inputs were provided simultaneously
|
|
46
|
-
if len([i for i in inputs if i]) > 1:
|
|
47
|
-
raise UsageError(
|
|
48
|
-
"Multiple input sources specified. Please specify only one."
|
|
49
|
-
)
|
|
50
|
+
"""Reads, transforms and execute statements from input.
|
|
50
51
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
return self._execute_single_query(
|
|
55
|
-
query=query, data=data, retain_comments=retain_comments
|
|
56
|
-
)
|
|
52
|
+
Only one input can be consumed at a time.
|
|
53
|
+
When no compilation errors are detected, the sequence on queries
|
|
54
|
+
in executed and returned as tuple.
|
|
57
55
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
single_statement = False
|
|
62
|
-
for file in files:
|
|
63
|
-
query_from_file = SecurePath(file).read_text(
|
|
64
|
-
file_size_limit_mb=UNLIMITED
|
|
65
|
-
)
|
|
66
|
-
single_statement, result = self._execute_single_query(
|
|
67
|
-
query=query_from_file, data=data, retain_comments=retain_comments
|
|
68
|
-
)
|
|
69
|
-
results.append(result)
|
|
56
|
+
Throws an exception ff multiple inputs are provided.
|
|
57
|
+
"""
|
|
58
|
+
query = sys.stdin.read() if std_in else query
|
|
70
59
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
60
|
+
stmt_operators = (
|
|
61
|
+
transpile_snowsql_templates,
|
|
62
|
+
partial(snowflake_sql_jinja_render, data=data),
|
|
63
|
+
)
|
|
64
|
+
remove_comments = not retain_comments
|
|
74
65
|
|
|
75
|
-
|
|
76
|
-
|
|
66
|
+
if query:
|
|
67
|
+
stmt_reader = query_reader(query, stmt_operators, remove_comments)
|
|
68
|
+
elif files:
|
|
69
|
+
secured_files = [SecurePath(f) for f in files]
|
|
70
|
+
stmt_reader = files_reader(secured_files, stmt_operators, remove_comments)
|
|
71
|
+
else:
|
|
72
|
+
raise UsageError("Use either query, filename or input option.")
|
|
77
73
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
try:
|
|
82
|
-
query = transpile_snowsql_templates(query)
|
|
83
|
-
query = snowflake_sql_jinja_render(content=query, data=data)
|
|
84
|
-
except UndefinedError as err:
|
|
85
|
-
raise ClickException(f"SQL template rendering error: {err}")
|
|
74
|
+
errors, stmt_count, compiled_statements = compile_statements(stmt_reader)
|
|
75
|
+
if not any((errors, stmt_count, compiled_statements)):
|
|
76
|
+
raise UsageError("Use either query, filename or input option.")
|
|
86
77
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
)
|
|
92
|
-
)
|
|
93
|
-
single_statement = len(statements) == 1
|
|
78
|
+
if errors:
|
|
79
|
+
for error in errors:
|
|
80
|
+
logger.info("Statement compilation error: %s", error)
|
|
81
|
+
cli_console.warning(error)
|
|
82
|
+
raise ClickException("SQL rendering error")
|
|
94
83
|
|
|
95
|
-
|
|
96
|
-
|
|
84
|
+
is_single_statement = not (stmt_count > 1)
|
|
85
|
+
return is_single_statement, self.execute_string(
|
|
86
|
+
"\n".join(compiled_statements),
|
|
87
|
+
cursor_class=VerboseCursor,
|
|
97
88
|
)
|