snowflake-cli 3.7.1__py3-none-any.whl → 3.8.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/snow_connector.py +14 -0
- snowflake/cli/_app/telemetry.py +11 -0
- snowflake/cli/_plugins/connection/commands.py +4 -2
- snowflake/cli/_plugins/nativeapp/codegen/setup/native_app_setup_processor.py +1 -1
- snowflake/cli/_plugins/nativeapp/entities/application_package.py +20 -7
- snowflake/cli/_plugins/nativeapp/sf_sql_facade.py +5 -3
- snowflake/cli/_plugins/project/commands.py +16 -6
- snowflake/cli/_plugins/snowpark/common.py +31 -0
- snowflake/cli/_plugins/snowpark/package/anaconda_packages.py +3 -0
- snowflake/cli/_plugins/snowpark/snowpark_entity.py +21 -1
- snowflake/cli/_plugins/snowpark/snowpark_entity_model.py +23 -1
- snowflake/cli/_plugins/spcs/common.py +7 -0
- snowflake/cli/_plugins/spcs/image_repository/commands.py +7 -2
- snowflake/cli/_plugins/spcs/image_repository/manager.py +6 -2
- snowflake/cli/_plugins/spcs/services/commands.py +2 -2
- snowflake/cli/_plugins/spcs/services/manager.py +36 -1
- snowflake/cli/_plugins/sql/commands.py +57 -6
- snowflake/cli/_plugins/sql/lexer/__init__.py +7 -0
- snowflake/cli/_plugins/sql/lexer/completer.py +12 -0
- snowflake/cli/_plugins/sql/lexer/functions.py +421 -0
- snowflake/cli/_plugins/sql/lexer/keywords.py +529 -0
- snowflake/cli/_plugins/sql/lexer/lexer.py +56 -0
- snowflake/cli/_plugins/sql/lexer/types.py +37 -0
- snowflake/cli/_plugins/sql/manager.py +43 -9
- snowflake/cli/_plugins/sql/repl.py +221 -0
- snowflake/cli/_plugins/sql/snowsql_commands.py +331 -0
- snowflake/cli/_plugins/sql/statement_reader.py +296 -0
- snowflake/cli/_plugins/streamlit/commands.py +30 -15
- snowflake/cli/_plugins/streamlit/manager.py +0 -183
- snowflake/cli/_plugins/streamlit/streamlit_entity.py +163 -23
- snowflake/cli/api/artifacts/upload.py +5 -0
- snowflake/cli/api/artifacts/utils.py +0 -2
- snowflake/cli/api/cli_global_context.py +7 -3
- snowflake/cli/api/commands/decorators.py +70 -0
- snowflake/cli/api/commands/flags.py +95 -3
- snowflake/cli/api/config.py +10 -0
- snowflake/cli/api/connections.py +10 -0
- snowflake/cli/api/console/abc.py +8 -2
- snowflake/cli/api/console/console.py +16 -0
- snowflake/cli/api/console/enum.py +1 -1
- snowflake/cli/api/entities/common.py +99 -10
- snowflake/cli/api/entities/utils.py +1 -0
- snowflake/cli/api/feature_flags.py +6 -0
- snowflake/cli/api/project/project_paths.py +5 -0
- snowflake/cli/api/rendering/sql_templates.py +2 -1
- snowflake/cli/api/sql_execution.py +16 -4
- snowflake/cli/api/utils/path_utils.py +15 -0
- snowflake/cli/api/utils/python_api_utils.py +12 -0
- {snowflake_cli-3.7.1.dist-info → snowflake_cli-3.8.0.dist-info}/METADATA +12 -8
- {snowflake_cli-3.7.1.dist-info → snowflake_cli-3.8.0.dist-info}/RECORD +54 -46
- snowflake/cli/_plugins/nativeapp/feature_flags.py +0 -28
- snowflake/cli/_plugins/sql/source_reader.py +0 -230
- {snowflake_cli-3.7.1.dist-info → snowflake_cli-3.8.0.dist-info}/WHEEL +0 -0
- {snowflake_cli-3.7.1.dist-info → snowflake_cli-3.8.0.dist-info}/entry_points.txt +0 -0
- {snowflake_cli-3.7.1.dist-info → snowflake_cli-3.8.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
from logging import getLogger
|
|
2
|
+
from typing import Iterable
|
|
3
|
+
|
|
4
|
+
from prompt_toolkit import PromptSession
|
|
5
|
+
from prompt_toolkit.filters import Condition, is_done, is_searching
|
|
6
|
+
from prompt_toolkit.history import FileHistory
|
|
7
|
+
from prompt_toolkit.key_binding.key_bindings import KeyBindings
|
|
8
|
+
from prompt_toolkit.keys import Keys
|
|
9
|
+
from prompt_toolkit.lexers import PygmentsLexer
|
|
10
|
+
from snowflake.cli._app.printing import print_result
|
|
11
|
+
from snowflake.cli._plugins.sql.lexer import CliLexer, cli_completer
|
|
12
|
+
from snowflake.cli._plugins.sql.manager import SqlManager
|
|
13
|
+
from snowflake.cli.api.cli_global_context import get_cli_context_manager
|
|
14
|
+
from snowflake.cli.api.console import cli_console
|
|
15
|
+
from snowflake.cli.api.output.types import MultipleResults, QueryResult
|
|
16
|
+
from snowflake.cli.api.secure_path import SecurePath
|
|
17
|
+
from snowflake.connector.config_manager import CONFIG_MANAGER
|
|
18
|
+
from snowflake.connector.cursor import SnowflakeCursor
|
|
19
|
+
|
|
20
|
+
log = getLogger(__name__)
|
|
21
|
+
|
|
22
|
+
HISTORY_FILE = SecurePath(
|
|
23
|
+
CONFIG_MANAGER.file_path.parent / "repl_history"
|
|
24
|
+
).path.expanduser()
|
|
25
|
+
EXIT_KEYWORDS = ("exit", "quit")
|
|
26
|
+
|
|
27
|
+
log.debug("setting history file to: %s", HISTORY_FILE.as_posix())
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class Repl:
|
|
31
|
+
"""Basic REPL implementation for the Snowflake CLI."""
|
|
32
|
+
|
|
33
|
+
def __init__(
|
|
34
|
+
self,
|
|
35
|
+
sql_manager: SqlManager,
|
|
36
|
+
data: dict | None = None,
|
|
37
|
+
retain_comments: bool = False,
|
|
38
|
+
):
|
|
39
|
+
"""Requires a `SqlManager` instance to execute queries.
|
|
40
|
+
|
|
41
|
+
'pass through' variables for SqlManager.execute method:
|
|
42
|
+
`data` should contain the variables used for template processing,
|
|
43
|
+
`retain_comments` how to handle comments in queries
|
|
44
|
+
"""
|
|
45
|
+
super().__init__()
|
|
46
|
+
setattr(get_cli_context_manager(), "is_repl", True)
|
|
47
|
+
self._data = data or {}
|
|
48
|
+
self._retain_comments = retain_comments
|
|
49
|
+
self._history = FileHistory(HISTORY_FILE)
|
|
50
|
+
self._lexer = PygmentsLexer(CliLexer)
|
|
51
|
+
self._completer = cli_completer
|
|
52
|
+
self._repl_key_bindings = self._setup_key_bindings()
|
|
53
|
+
self._yes_no_keybindings = self._setup_yn_key_bindings()
|
|
54
|
+
self._sql_manager = sql_manager
|
|
55
|
+
self.session = PromptSession(history=self._history)
|
|
56
|
+
|
|
57
|
+
def _setup_key_bindings(self) -> KeyBindings:
|
|
58
|
+
"""Key bindings for repl. Helps detecting ; at end of buffer."""
|
|
59
|
+
kb = KeyBindings()
|
|
60
|
+
|
|
61
|
+
@Condition
|
|
62
|
+
def not_searching():
|
|
63
|
+
return not is_searching()
|
|
64
|
+
|
|
65
|
+
@kb.add(Keys.Enter, filter=not_searching)
|
|
66
|
+
def _(event):
|
|
67
|
+
"""Handle Enter key press."""
|
|
68
|
+
buffer = event.app.current_buffer
|
|
69
|
+
stripped_buffer = buffer.text.strip()
|
|
70
|
+
|
|
71
|
+
if stripped_buffer:
|
|
72
|
+
log.debug("evaluating repl input")
|
|
73
|
+
cursor_position = buffer.cursor_position
|
|
74
|
+
ends_with_semicolon = buffer.text.endswith(";")
|
|
75
|
+
|
|
76
|
+
if stripped_buffer.lower() in EXIT_KEYWORDS:
|
|
77
|
+
log.debug("exit keyword detected %r", stripped_buffer)
|
|
78
|
+
buffer.validate_and_handle()
|
|
79
|
+
|
|
80
|
+
elif ends_with_semicolon and cursor_position >= len(stripped_buffer):
|
|
81
|
+
log.debug("semicolon detected, submitting input")
|
|
82
|
+
buffer.validate_and_handle()
|
|
83
|
+
|
|
84
|
+
else:
|
|
85
|
+
log.debug("adding new line")
|
|
86
|
+
buffer.insert_text("\n")
|
|
87
|
+
else:
|
|
88
|
+
log.debug("empty input")
|
|
89
|
+
|
|
90
|
+
@kb.add(Keys.ControlJ)
|
|
91
|
+
def _(event):
|
|
92
|
+
"""Control+J (and alias for Control + Enter) always inserts a new line."""
|
|
93
|
+
event.app.current_buffer.insert_text("\n")
|
|
94
|
+
|
|
95
|
+
return kb
|
|
96
|
+
|
|
97
|
+
def _setup_yn_key_bindings(self) -> KeyBindings:
|
|
98
|
+
"""Key bindings for easy handling yes/no prompt."""
|
|
99
|
+
kb = KeyBindings()
|
|
100
|
+
|
|
101
|
+
@kb.add(Keys.Enter, filter=~is_done)
|
|
102
|
+
@kb.add("c-d", filter=~is_done)
|
|
103
|
+
@kb.add("y", filter=~is_done)
|
|
104
|
+
def _(event):
|
|
105
|
+
event.app.exit(result="y")
|
|
106
|
+
|
|
107
|
+
@kb.add("n", filter=~is_done)
|
|
108
|
+
@kb.add("c-c", filter=~is_done)
|
|
109
|
+
def _(event):
|
|
110
|
+
event.app.exit(result="n")
|
|
111
|
+
|
|
112
|
+
@kb.add(Keys.Any)
|
|
113
|
+
def _(event):
|
|
114
|
+
pass
|
|
115
|
+
|
|
116
|
+
return kb
|
|
117
|
+
|
|
118
|
+
def repl_propmpt(self, msg: str = " > ") -> str:
|
|
119
|
+
"""Regular repl prompt."""
|
|
120
|
+
return self.session.prompt(
|
|
121
|
+
msg,
|
|
122
|
+
lexer=self._lexer,
|
|
123
|
+
completer=self._completer,
|
|
124
|
+
multiline=True,
|
|
125
|
+
wrap_lines=True,
|
|
126
|
+
key_bindings=self._repl_key_bindings,
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
def yn_prompt(self, msg: str) -> str:
|
|
130
|
+
"""Yes/No prompt."""
|
|
131
|
+
return self.session.prompt(
|
|
132
|
+
msg,
|
|
133
|
+
lexer=None,
|
|
134
|
+
completer=None,
|
|
135
|
+
multiline=False,
|
|
136
|
+
wrap_lines=False,
|
|
137
|
+
key_bindings=self._yes_no_keybindings,
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
@property
|
|
141
|
+
def _welcome_banner(self) -> str:
|
|
142
|
+
return f"Welcome to Snowflake-CLI REPL\nType 'exit' or 'quit' to leave"
|
|
143
|
+
|
|
144
|
+
def _initialize_connection(self):
|
|
145
|
+
"""Early connection for possible fast fail."""
|
|
146
|
+
cursor = self._execute("select current_version();")
|
|
147
|
+
res = next(iter(cursor))
|
|
148
|
+
log.debug("REPL: Snowflake version: %s", res.fetchall()[0][0])
|
|
149
|
+
|
|
150
|
+
def _execute(self, user_input: str) -> Iterable[SnowflakeCursor]:
|
|
151
|
+
"""Executes a query and returns a list of cursors."""
|
|
152
|
+
_, cursors = self._sql_manager.execute(
|
|
153
|
+
query=user_input,
|
|
154
|
+
files=None,
|
|
155
|
+
std_in=False,
|
|
156
|
+
data=self._data,
|
|
157
|
+
retain_comments=self._retain_comments,
|
|
158
|
+
)
|
|
159
|
+
return cursors
|
|
160
|
+
|
|
161
|
+
def run(self):
|
|
162
|
+
try:
|
|
163
|
+
cli_console.panel(self._welcome_banner)
|
|
164
|
+
self._initialize_connection()
|
|
165
|
+
self._repl_loop()
|
|
166
|
+
except (KeyboardInterrupt, EOFError):
|
|
167
|
+
cli_console.message("\n[bold orange_red1]Leaving REPL, bye ...")
|
|
168
|
+
|
|
169
|
+
def _repl_loop(self):
|
|
170
|
+
"""Main REPL loop. Handles input and query execution.
|
|
171
|
+
|
|
172
|
+
Sets up prompt session with history and key bindings.
|
|
173
|
+
Honors Ctrl-C and Ctrl-D in REPL loop.
|
|
174
|
+
"""
|
|
175
|
+
while True:
|
|
176
|
+
try:
|
|
177
|
+
user_input = self.repl_propmpt().strip()
|
|
178
|
+
|
|
179
|
+
if not user_input:
|
|
180
|
+
continue
|
|
181
|
+
|
|
182
|
+
if user_input.lower() in EXIT_KEYWORDS:
|
|
183
|
+
raise EOFError
|
|
184
|
+
|
|
185
|
+
try:
|
|
186
|
+
log.debug("executing query")
|
|
187
|
+
cursors = self._execute(user_input)
|
|
188
|
+
print_result(MultipleResults(QueryResult(c) for c in cursors))
|
|
189
|
+
|
|
190
|
+
except Exception as e:
|
|
191
|
+
log.debug("error occurred: %s", e)
|
|
192
|
+
cli_console.warning(f"\nError occurred: {e}")
|
|
193
|
+
|
|
194
|
+
except KeyboardInterrupt: # a.k.a Ctrl-C
|
|
195
|
+
log.debug("user interrupted with Ctrl-C")
|
|
196
|
+
continue
|
|
197
|
+
|
|
198
|
+
except EOFError: # a.k.a Ctrl-D
|
|
199
|
+
log.debug("user interrupted with Ctrl-D")
|
|
200
|
+
should_exit = self.ask_yn("Do you want to leave?")
|
|
201
|
+
log.debug("user answered: %r", should_exit)
|
|
202
|
+
if should_exit:
|
|
203
|
+
raise EOFError
|
|
204
|
+
continue
|
|
205
|
+
|
|
206
|
+
except Exception as e:
|
|
207
|
+
cli_console.warning(f"\nError occurred: {e}")
|
|
208
|
+
|
|
209
|
+
def ask_yn(self, question: str) -> bool:
|
|
210
|
+
"""Asks user a Yes/No question."""
|
|
211
|
+
try:
|
|
212
|
+
while True:
|
|
213
|
+
log.debug("asking user: %s", question)
|
|
214
|
+
answer = self.yn_prompt(f"{question} (y/n): ")
|
|
215
|
+
log.debug("user answered: %s", answer)
|
|
216
|
+
|
|
217
|
+
return answer == "y"
|
|
218
|
+
|
|
219
|
+
except KeyboardInterrupt:
|
|
220
|
+
log.debug("user interrupted with Ctrl-C returning to REPL")
|
|
221
|
+
return False
|
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
import enum
|
|
2
|
+
import re
|
|
3
|
+
import time
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
from typing import Any, Dict, Generator, Iterable, List, Tuple
|
|
7
|
+
from urllib.parse import urlencode
|
|
8
|
+
|
|
9
|
+
from snowflake.cli._app.printing import print_result
|
|
10
|
+
from snowflake.cli.api.output.types import CollectionResult, QueryResult
|
|
11
|
+
from snowflake.connector import SnowflakeConnection
|
|
12
|
+
|
|
13
|
+
VALID_UUID_RE = re.compile(
|
|
14
|
+
r"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class CommandType(enum.Enum):
|
|
19
|
+
QUERIES = "queries"
|
|
20
|
+
UNKNOWN = "unknown"
|
|
21
|
+
URL = "url"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class SnowSQLCommand:
|
|
25
|
+
def execute(self, connection: SnowflakeConnection):
|
|
26
|
+
"""Executes command and prints the result."""
|
|
27
|
+
raise NotImplementedError
|
|
28
|
+
|
|
29
|
+
@classmethod
|
|
30
|
+
def from_args(cls, args, kwargs) -> "CompileCommandResult":
|
|
31
|
+
"""Validates arguments and creates command ready for execution."""
|
|
32
|
+
raise NotImplementedError
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _print_result_to_stdout(headers: Iterable[str], rows: Iterable[Iterable[Any]]):
|
|
36
|
+
formatted_rows: Generator[Dict[str, Any], None, None] = (
|
|
37
|
+
{key: value for key, value in zip(headers, row)} for row in rows
|
|
38
|
+
)
|
|
39
|
+
print_result(CollectionResult(formatted_rows))
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@dataclass
|
|
43
|
+
class CompileCommandResult:
|
|
44
|
+
command: SnowSQLCommand | None = None
|
|
45
|
+
error_message: str | None = None
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@dataclass
|
|
49
|
+
class QueriesCommand(SnowSQLCommand):
|
|
50
|
+
help_mode: bool = False
|
|
51
|
+
from_current_session: bool = False
|
|
52
|
+
amount: int = 25
|
|
53
|
+
user: str | None = None
|
|
54
|
+
warehouse: str | None = None
|
|
55
|
+
start_timestamp_ms: int | None = None
|
|
56
|
+
end_timestamp_ms: int | None = None
|
|
57
|
+
duration: str | None = None
|
|
58
|
+
stmt_type: str | None = None
|
|
59
|
+
status: str | None = None
|
|
60
|
+
|
|
61
|
+
def execute(self, connection: SnowflakeConnection) -> None:
|
|
62
|
+
if self.help_mode:
|
|
63
|
+
self._execute_help()
|
|
64
|
+
else:
|
|
65
|
+
self._execute_queries(connection)
|
|
66
|
+
|
|
67
|
+
def _execute_help(self):
|
|
68
|
+
headers = ["FILTER", "ARGUMENT", "DEFAULT"]
|
|
69
|
+
filters = [
|
|
70
|
+
["amount", "integer", "25"],
|
|
71
|
+
["status", "string", "any"],
|
|
72
|
+
["warehouse", "string", "any"],
|
|
73
|
+
["user", "string", "any"],
|
|
74
|
+
[
|
|
75
|
+
"start_date",
|
|
76
|
+
"datetime in ISO format (for example YYYY-MM-DDTHH:mm:ss.sss)",
|
|
77
|
+
"any",
|
|
78
|
+
],
|
|
79
|
+
[
|
|
80
|
+
"end_date",
|
|
81
|
+
"datetime in ISO format (for example YYYY-MM-DDTHH:mm:ss.sss)",
|
|
82
|
+
"any",
|
|
83
|
+
],
|
|
84
|
+
["start", "timestamp in milliseconds (integer)", "any"],
|
|
85
|
+
["end", "timestamp in milliseconds (integer)", "any"],
|
|
86
|
+
["type", "string", "any"],
|
|
87
|
+
["duration", "time in milliseconds", "any"],
|
|
88
|
+
["session", "No arguments", "any"],
|
|
89
|
+
]
|
|
90
|
+
_print_result_to_stdout(headers, filters)
|
|
91
|
+
|
|
92
|
+
def _execute_queries(self, connection: SnowflakeConnection) -> None:
|
|
93
|
+
url_parameters = {
|
|
94
|
+
"_dc": f"{time.time()}",
|
|
95
|
+
"includeDDL": "false",
|
|
96
|
+
"max": self.amount,
|
|
97
|
+
}
|
|
98
|
+
if self.user:
|
|
99
|
+
url_parameters["user"] = self.user
|
|
100
|
+
if self.warehouse:
|
|
101
|
+
url_parameters["wh"] = self.warehouse
|
|
102
|
+
if self.start_timestamp_ms:
|
|
103
|
+
url_parameters["start"] = self.start_timestamp_ms
|
|
104
|
+
if self.end_timestamp_ms:
|
|
105
|
+
url_parameters["end"] = self.end_timestamp_ms
|
|
106
|
+
if self.duration:
|
|
107
|
+
url_parameters["min_duration"] = self.duration
|
|
108
|
+
if self.from_current_session:
|
|
109
|
+
url_parameters["session_id"] = connection.session_id
|
|
110
|
+
if self.status:
|
|
111
|
+
url_parameters["subset"] = self.status
|
|
112
|
+
if self.stmt_type:
|
|
113
|
+
url_parameters["stmt_type"] = self.stmt_type
|
|
114
|
+
|
|
115
|
+
url = "/monitoring/queries?" + urlencode(url_parameters)
|
|
116
|
+
ret = connection.rest.request(url=url, method="get", client="rest")
|
|
117
|
+
if ret.get("data") and ret["data"].get("queries"):
|
|
118
|
+
_result: Generator[Tuple[str, str, str, str], None, None] = (
|
|
119
|
+
(
|
|
120
|
+
query["id"],
|
|
121
|
+
query["sqlText"],
|
|
122
|
+
query["state"],
|
|
123
|
+
query["totalDuration"],
|
|
124
|
+
)
|
|
125
|
+
for query in ret["data"]["queries"]
|
|
126
|
+
)
|
|
127
|
+
_print_result_to_stdout(
|
|
128
|
+
["QUERY ID", "SQL TEXT", "STATUS", "DURATION_MS"], _result
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
@classmethod
|
|
132
|
+
def from_args(cls, args: List[str], kwargs: Dict[str, Any]) -> CompileCommandResult:
|
|
133
|
+
if "help" in args:
|
|
134
|
+
return CompileCommandResult(command=cls(help_mode=True))
|
|
135
|
+
|
|
136
|
+
# "session" is set by default if no other arguments are provided
|
|
137
|
+
from_current_session = "session" in args or not kwargs
|
|
138
|
+
amount = kwargs.pop("amount", "25")
|
|
139
|
+
if not amount.isdigit():
|
|
140
|
+
return CompileCommandResult(
|
|
141
|
+
error_message=f"Invalid argument passed to 'amount' filter: {amount}"
|
|
142
|
+
)
|
|
143
|
+
user = kwargs.pop("user", None)
|
|
144
|
+
warehouse = kwargs.pop("warehouse", None)
|
|
145
|
+
duration = kwargs.pop("duration", None)
|
|
146
|
+
|
|
147
|
+
start_timestamp_ms = kwargs.pop("start", None)
|
|
148
|
+
if start_timestamp_ms:
|
|
149
|
+
try:
|
|
150
|
+
start_timestamp_ms = int(start_timestamp_ms)
|
|
151
|
+
except ValueError:
|
|
152
|
+
return CompileCommandResult(
|
|
153
|
+
error_message=f"Invalid argument passed to 'start' filter: {start_timestamp_ms}"
|
|
154
|
+
)
|
|
155
|
+
end_timestamp_ms = kwargs.pop("end", None)
|
|
156
|
+
if end_timestamp_ms:
|
|
157
|
+
try:
|
|
158
|
+
end_timestamp_ms = int(end_timestamp_ms)
|
|
159
|
+
except ValueError:
|
|
160
|
+
return CompileCommandResult(
|
|
161
|
+
error_message=f"Invalid argument passed to 'end' filter: {end_timestamp_ms}"
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
start_date = kwargs.pop("start_date", None)
|
|
165
|
+
if start_date:
|
|
166
|
+
if start_timestamp_ms:
|
|
167
|
+
return CompileCommandResult(
|
|
168
|
+
error_message=f"'start_date' filter cannot be used with 'start' filter"
|
|
169
|
+
)
|
|
170
|
+
try:
|
|
171
|
+
seconds = datetime.fromisoformat(start_date).timestamp()
|
|
172
|
+
start_timestamp_ms = int(seconds * 1000) # convert to milliseconds
|
|
173
|
+
except ValueError:
|
|
174
|
+
return CompileCommandResult(
|
|
175
|
+
error_message=f"Invalid date format passed to 'start_date' filter: {start_date}"
|
|
176
|
+
)
|
|
177
|
+
end_date = kwargs.pop("end_date", None)
|
|
178
|
+
if end_date:
|
|
179
|
+
if end_timestamp_ms:
|
|
180
|
+
return CompileCommandResult(
|
|
181
|
+
error_message=f"'end_date' filter cannot be used with 'end' filter"
|
|
182
|
+
)
|
|
183
|
+
try:
|
|
184
|
+
seconds = datetime.fromisoformat(end_date).timestamp()
|
|
185
|
+
end_timestamp_ms = int(seconds * 1000) # convert to milliseconds
|
|
186
|
+
except ValueError:
|
|
187
|
+
return CompileCommandResult(
|
|
188
|
+
error_message=f"Invalid date format passed to 'end_date' filter: {end_date}"
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
stmt_type = kwargs.pop("type", None)
|
|
192
|
+
if stmt_type:
|
|
193
|
+
stmt_type = stmt_type.upper()
|
|
194
|
+
if stmt_type not in [
|
|
195
|
+
"ANY",
|
|
196
|
+
"SELECT",
|
|
197
|
+
"INSERT",
|
|
198
|
+
"UPDATE",
|
|
199
|
+
"DELETE",
|
|
200
|
+
"MERGE",
|
|
201
|
+
"MULTI_TABLE_INSERT",
|
|
202
|
+
"COPY",
|
|
203
|
+
"COMMIT",
|
|
204
|
+
"ROLLBACK",
|
|
205
|
+
"BEGIN_TRANSACTION",
|
|
206
|
+
"SHOW",
|
|
207
|
+
"GRANT",
|
|
208
|
+
"CREATE",
|
|
209
|
+
"ALTER",
|
|
210
|
+
]:
|
|
211
|
+
return CompileCommandResult(
|
|
212
|
+
error_message=f"Invalid argument passed to 'type' filter: {stmt_type}"
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
status = kwargs.pop("status", None)
|
|
216
|
+
if status:
|
|
217
|
+
status = status.upper()
|
|
218
|
+
if status not in [
|
|
219
|
+
"RUNNING",
|
|
220
|
+
"SUCCEEDED",
|
|
221
|
+
"FAILED",
|
|
222
|
+
"BLOCKED",
|
|
223
|
+
"QUEUED",
|
|
224
|
+
"ABORTED",
|
|
225
|
+
]:
|
|
226
|
+
return CompileCommandResult(
|
|
227
|
+
error_message=f"Invalid argument passed to 'status' filter: {status}"
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
for arg in args:
|
|
231
|
+
if arg.lower() not in ["session", "help"]:
|
|
232
|
+
return CompileCommandResult(
|
|
233
|
+
error_message=f"Invalid argument passed to 'queries' command: {arg}"
|
|
234
|
+
)
|
|
235
|
+
if kwargs:
|
|
236
|
+
key, value = kwargs.popitem()
|
|
237
|
+
return CompileCommandResult(
|
|
238
|
+
error_message=f"Invalid argument passed to 'queries' command: {key}={value}"
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
return CompileCommandResult(
|
|
242
|
+
command=cls(
|
|
243
|
+
help_mode=False,
|
|
244
|
+
from_current_session=from_current_session,
|
|
245
|
+
amount=int(amount),
|
|
246
|
+
user=user,
|
|
247
|
+
warehouse=warehouse,
|
|
248
|
+
start_timestamp_ms=start_timestamp_ms,
|
|
249
|
+
end_timestamp_ms=end_timestamp_ms,
|
|
250
|
+
duration=duration,
|
|
251
|
+
stmt_type=stmt_type,
|
|
252
|
+
status=status,
|
|
253
|
+
)
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def _validate_only_arg_is_query_id(
|
|
258
|
+
command_name: str, args: List[str], kwargs: Dict[str, Any]
|
|
259
|
+
) -> str | None:
|
|
260
|
+
if kwargs:
|
|
261
|
+
key, value = kwargs.popitem()
|
|
262
|
+
return f"Invalid argument passed to '{command_name}' command: {key}={value}"
|
|
263
|
+
if len(args) != 1:
|
|
264
|
+
amount = "Too many" if args else "No"
|
|
265
|
+
return f"{amount} arguments passed to '{command_name}' command. Usage: `!{command_name} <query id>`"
|
|
266
|
+
|
|
267
|
+
qid = args[0]
|
|
268
|
+
if not VALID_UUID_RE.match(qid):
|
|
269
|
+
return f"Invalid query ID passed to '{command_name}' command: {qid}"
|
|
270
|
+
|
|
271
|
+
return None
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
@dataclass
|
|
275
|
+
class ResultCommand(SnowSQLCommand):
|
|
276
|
+
query_id: str
|
|
277
|
+
|
|
278
|
+
def execute(self, connection: SnowflakeConnection):
|
|
279
|
+
cursor = connection.cursor()
|
|
280
|
+
cursor.query_result(self.query_id)
|
|
281
|
+
print_result(QueryResult(cursor=cursor))
|
|
282
|
+
|
|
283
|
+
@classmethod
|
|
284
|
+
def from_args(cls, args, kwargs) -> CompileCommandResult:
|
|
285
|
+
error_msg = _validate_only_arg_is_query_id("result", args, kwargs)
|
|
286
|
+
if error_msg:
|
|
287
|
+
return CompileCommandResult(error_message=error_msg)
|
|
288
|
+
return CompileCommandResult(command=cls(args[0]))
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
@dataclass
|
|
292
|
+
class AbortCommand(SnowSQLCommand):
|
|
293
|
+
query_id: str
|
|
294
|
+
|
|
295
|
+
def execute(self, connection: SnowflakeConnection):
|
|
296
|
+
cursor = connection.cursor()
|
|
297
|
+
cursor.execute(f"SELECT SYSTEM$CANCEL_QUERY('{self.query_id}')")
|
|
298
|
+
print_result(QueryResult(cursor=cursor))
|
|
299
|
+
|
|
300
|
+
@classmethod
|
|
301
|
+
def from_args(cls, args, kwargs) -> CompileCommandResult:
|
|
302
|
+
error_msg = _validate_only_arg_is_query_id("abort", args, kwargs)
|
|
303
|
+
if error_msg:
|
|
304
|
+
return CompileCommandResult(error_message=error_msg)
|
|
305
|
+
return CompileCommandResult(command=cls(args[0]))
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
def compile_snowsql_command(command: str, cmd_args: List[str]):
|
|
309
|
+
"""Parses command into SQL query"""
|
|
310
|
+
args = []
|
|
311
|
+
kwargs = {}
|
|
312
|
+
for cmd_arg in cmd_args:
|
|
313
|
+
if "=" not in cmd_arg:
|
|
314
|
+
args.append(cmd_arg)
|
|
315
|
+
else:
|
|
316
|
+
key, val = cmd_arg.split("=", maxsplit=1)
|
|
317
|
+
if key in kwargs:
|
|
318
|
+
return CompileCommandResult(
|
|
319
|
+
error_message=f"duplicated argument '{key}' for command '{command}'",
|
|
320
|
+
)
|
|
321
|
+
kwargs[key] = val
|
|
322
|
+
|
|
323
|
+
match command.lower():
|
|
324
|
+
case "!queries":
|
|
325
|
+
return QueriesCommand.from_args(args, kwargs)
|
|
326
|
+
case "!result":
|
|
327
|
+
return ResultCommand.from_args(args, kwargs)
|
|
328
|
+
case "!abort":
|
|
329
|
+
return AbortCommand.from_args(args, kwargs)
|
|
330
|
+
case _:
|
|
331
|
+
return CompileCommandResult(error_message=f"Unknown command '{command}'")
|