execsql2 2.23.1__py3-none-any.whl → 2.25.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.
- execsql/api.py +169 -160
- execsql/cli/__init__.py +19 -586
- execsql/cli/application.py +309 -0
- execsql/cli/commands/__init__.py +13 -0
- execsql/cli/commands/config.py +205 -0
- execsql/cli/commands/format.py +100 -0
- execsql/cli/commands/init.py +135 -0
- execsql/cli/commands/lint.py +225 -0
- execsql/cli/commands/listing.py +81 -0
- execsql/cli/commands/run.py +459 -0
- execsql/cli/dispatch.py +66 -0
- execsql/cli/dsn.py +11 -4
- execsql/cli/help.py +175 -24
- execsql/cli/lint.py +780 -100
- execsql/cli/options.py +108 -0
- execsql/cli/run.py +96 -32
- execsql/config.py +256 -173
- execsql/config_validation.py +282 -0
- execsql/data/execsql.conf.template +38 -5
- execsql/data/script_header.sql +23 -0
- execsql/db/access.py +29 -8
- execsql/db/base.py +40 -8
- execsql/db/dsn.py +13 -4
- execsql/db/duckdb.py +3 -1
- execsql/db/factory.py +13 -4
- execsql/db/firebird.py +21 -7
- execsql/db/mysql.py +37 -16
- execsql/db/oracle.py +49 -16
- execsql/db/postgres.py +30 -6
- execsql/db/sqlite.py +7 -9
- execsql/db/sqlserver.py +25 -8
- execsql/debug/repl.py +51 -193
- execsql/exceptions.py +9 -0
- execsql/exporters/delimited.py +87 -28
- execsql/exporters/xlsx.py +24 -9
- execsql/exporters/zip.py +1 -2
- execsql/format.py +115 -76
- execsql/gui/desktop.py +1 -3
- execsql/gui/tui.py +2 -2
- execsql/importers/base.py +35 -1
- execsql/importers/csv.py +3 -1
- execsql/interactive.py +232 -0
- execsql/manifest.py +200 -0
- execsql/metacommands/conditions.py +7 -8
- execsql/metacommands/control.py +2 -3
- execsql/metacommands/data.py +4 -1
- execsql/metacommands/debug.py +2 -4
- execsql/metacommands/effects.py +124 -0
- execsql/metacommands/io_fileops.py +62 -37
- execsql/metacommands/io_import.py +0 -1
- execsql/metacommands/prompt.py +2 -0
- execsql/metacommands/upsert.py +5 -2
- execsql/models.py +3 -2
- execsql/parser.py +14 -5
- execsql/plugins.py +24 -0
- execsql/script/__init__.py +0 -9
- execsql/script/ast.py +1 -1
- execsql/script/engine.py +46 -104
- execsql/script/executor.py +162 -39
- execsql/script/parser.py +167 -118
- execsql/state.py +34 -11
- execsql/state.pyi +11 -0
- execsql/types.py +22 -13
- execsql/utils/auth.py +169 -24
- execsql/utils/color.py +18 -0
- execsql/utils/datetime.py +55 -17
- execsql/utils/errors.py +82 -10
- execsql/utils/fileio.py +404 -117
- execsql/utils/gui.py +3 -0
- execsql/utils/mail.py +37 -12
- {execsql2-2.23.1.data → execsql2-2.25.0.data}/data/execsql2_extras/README.md +1 -1
- {execsql2-2.23.1.dist-info → execsql2-2.25.0.dist-info}/METADATA +122 -63
- execsql2-2.25.0.dist-info/RECORD +140 -0
- execsql2-2.25.0.dist-info/entry_points.txt +2 -0
- execsql2-2.23.1.dist-info/RECORD +0 -124
- execsql2-2.23.1.dist-info/entry_points.txt +0 -3
- {execsql2-2.23.1.data → execsql2-2.25.0.data}/data/execsql2_extras/config_settings.sqlite +0 -0
- {execsql2-2.23.1.data → execsql2-2.25.0.data}/data/execsql2_extras/example_config_prompt.sql +0 -0
- {execsql2-2.23.1.data → execsql2-2.25.0.data}/data/execsql2_extras/make_config_db.sql +0 -0
- {execsql2-2.23.1.data → execsql2-2.25.0.data}/data/execsql2_extras/md_compare.sql +0 -0
- {execsql2-2.23.1.data → execsql2-2.25.0.data}/data/execsql2_extras/md_glossary.sql +0 -0
- {execsql2-2.23.1.data → execsql2-2.25.0.data}/data/execsql2_extras/md_upsert.sql +0 -0
- {execsql2-2.23.1.data → execsql2-2.25.0.data}/data/execsql2_extras/pg_compare.sql +0 -0
- {execsql2-2.23.1.data → execsql2-2.25.0.data}/data/execsql2_extras/pg_glossary.sql +0 -0
- {execsql2-2.23.1.data → execsql2-2.25.0.data}/data/execsql2_extras/pg_upsert.sql +0 -0
- {execsql2-2.23.1.data → execsql2-2.25.0.data}/data/execsql2_extras/script_template.sql +0 -0
- {execsql2-2.23.1.data → execsql2-2.25.0.data}/data/execsql2_extras/ss_compare.sql +0 -0
- {execsql2-2.23.1.data → execsql2-2.25.0.data}/data/execsql2_extras/ss_glossary.sql +0 -0
- {execsql2-2.23.1.data → execsql2-2.25.0.data}/data/execsql2_extras/ss_upsert.sql +0 -0
- {execsql2-2.23.1.dist-info → execsql2-2.25.0.dist-info}/WHEEL +0 -0
- {execsql2-2.23.1.dist-info → execsql2-2.25.0.dist-info}/licenses/LICENSE.txt +0 -0
- {execsql2-2.23.1.dist-info → execsql2-2.25.0.dist-info}/licenses/NOTICE +0 -0
execsql/models.py
CHANGED
|
@@ -268,7 +268,6 @@ class DataTable:
|
|
|
268
268
|
|
|
269
269
|
self.inputrows = 0 # Total number of rows in the row source.
|
|
270
270
|
self.datarows = 0 # Number of non-empty rows (with data values).
|
|
271
|
-
self.shortrows = 0 # Number of rows without as many data values as column names.
|
|
272
271
|
self.cols: list = [] # List of Column objects.
|
|
273
272
|
for n in column_names:
|
|
274
273
|
self.cols.append(Column(n, infer_strings=infer_strings))
|
|
@@ -281,7 +280,6 @@ class DataTable:
|
|
|
281
280
|
self.datarows += 1
|
|
282
281
|
chkcols = len(self.cols)
|
|
283
282
|
if dataitems < chkcols:
|
|
284
|
-
self.shortrows += 1
|
|
285
283
|
chkcols = len(datarow)
|
|
286
284
|
else:
|
|
287
285
|
if dataitems > chkcols:
|
|
@@ -307,6 +305,9 @@ class DataTable:
|
|
|
307
305
|
raise DataTableError(errmsg)
|
|
308
306
|
for i in range(chkcols):
|
|
309
307
|
self.cols[i].eval_types(datarow[i])
|
|
308
|
+
# Trailing values left off a short row are imported as NULL.
|
|
309
|
+
for i in range(chkcols, len(self.cols)):
|
|
310
|
+
self.cols[i].eval_types(None)
|
|
310
311
|
for col in self.cols:
|
|
311
312
|
col.column_type()
|
|
312
313
|
|
execsql/parser.py
CHANGED
|
@@ -215,10 +215,17 @@ class CondParser(CondTokens):
|
|
|
215
215
|
"""Recursive-descent parser for boolean conditional expressions."""
|
|
216
216
|
|
|
217
217
|
# Takes a conditional expression string.
|
|
218
|
-
def __init__(self, condexpr: str) -> None:
|
|
219
|
-
"""Initialise the parser with the conditional expression string.
|
|
218
|
+
def __init__(self, condexpr: str, conditionallist: Any = None) -> None:
|
|
219
|
+
"""Initialise the parser with the conditional expression string.
|
|
220
|
+
|
|
221
|
+
*conditionallist* is the table of conditional tests to match against;
|
|
222
|
+
``None`` means the current run's ``conditionallist``. Passing one lets
|
|
223
|
+
a caller with no run in progress, such as ``execsql lint``, parse
|
|
224
|
+
(without evaluating) a condition.
|
|
225
|
+
"""
|
|
220
226
|
self.condexpr = condexpr
|
|
221
227
|
self.cond_expr = SourceString(condexpr)
|
|
228
|
+
self.conditionallist = conditionallist
|
|
222
229
|
|
|
223
230
|
def match_not(self) -> int | None:
|
|
224
231
|
"""Match a NOT operator and return its token type, or None."""
|
|
@@ -251,10 +258,12 @@ class CondParser(CondTokens):
|
|
|
251
258
|
m1 = self.factor()
|
|
252
259
|
return CondAstNode(self.NOT, m1, None)
|
|
253
260
|
# Find the matching metacommand -- get a tuple consisting of (metacommand, groupdict)
|
|
254
|
-
|
|
255
|
-
|
|
261
|
+
conditionallist = self.conditionallist
|
|
262
|
+
if conditionallist is None:
|
|
263
|
+
import execsql.state as _state
|
|
256
264
|
|
|
257
|
-
|
|
265
|
+
conditionallist = _state.conditionallist
|
|
266
|
+
matched_metacommand = self.cond_expr.match_metacommand(conditionallist)
|
|
258
267
|
if matched_metacommand is not None:
|
|
259
268
|
matched_metacommand[1]["metacommandline"] = self.condexpr
|
|
260
269
|
return CondAstNode(self.CONDITIONAL, matched_metacommand, None)
|
execsql/plugins.py
CHANGED
|
@@ -61,6 +61,8 @@ adds entries::
|
|
|
61
61
|
from __future__ import annotations
|
|
62
62
|
|
|
63
63
|
import logging
|
|
64
|
+
import threading
|
|
65
|
+
import weakref
|
|
64
66
|
from collections.abc import Callable
|
|
65
67
|
from importlib.metadata import entry_points
|
|
66
68
|
from typing import Any
|
|
@@ -71,6 +73,7 @@ __all__ = [
|
|
|
71
73
|
"ImporterEntry",
|
|
72
74
|
"ImporterRegistry",
|
|
73
75
|
"discover_metacommand_plugins",
|
|
76
|
+
"register_metacommand_plugins_once",
|
|
74
77
|
"discover_exporter_plugins",
|
|
75
78
|
"discover_importer_plugins",
|
|
76
79
|
"discover_all_plugins",
|
|
@@ -344,6 +347,27 @@ def discover_metacommand_plugins(mcl: Any) -> int:
|
|
|
344
347
|
return loaded
|
|
345
348
|
|
|
346
349
|
|
|
350
|
+
_registered_tables: weakref.WeakSet[Any] = weakref.WeakSet()
|
|
351
|
+
_registration_lock = threading.Lock()
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
def register_metacommand_plugins_once(mcl: Any) -> int:
|
|
355
|
+
"""Register metacommand plugins into *mcl* unless that was already done.
|
|
356
|
+
|
|
357
|
+
Every run in a process shares one dispatch table, so registering on each
|
|
358
|
+
run would prepend another copy of every plugin command each time, while
|
|
359
|
+
other threads may be dispatching from the table.
|
|
360
|
+
|
|
361
|
+
Returns:
|
|
362
|
+
Number of plugins loaded by this call (0 if *mcl* already had them).
|
|
363
|
+
"""
|
|
364
|
+
with _registration_lock:
|
|
365
|
+
if mcl in _registered_tables:
|
|
366
|
+
return 0
|
|
367
|
+
_registered_tables.add(mcl)
|
|
368
|
+
return discover_metacommand_plugins(mcl)
|
|
369
|
+
|
|
370
|
+
|
|
347
371
|
def discover_exporter_plugins(registry: ExporterRegistry | None = None) -> int:
|
|
348
372
|
"""Discover and register exporter plugins.
|
|
349
373
|
|
execsql/script/__init__.py
CHANGED
|
@@ -23,9 +23,6 @@ Key classes:
|
|
|
23
23
|
handler function + flags).
|
|
24
24
|
- :class:`MetaCommandList` — ordered list of :class:`MetaCommand` entries
|
|
25
25
|
with a keyword index for fast dispatch.
|
|
26
|
-
- :class:`SqlStmt` / :class:`MetacommandStmt` / :class:`ScriptCmd` —
|
|
27
|
-
statement wrappers carried in the AST and used by ``ctx.last_command``
|
|
28
|
-
for source-location tracking.
|
|
29
26
|
- :class:`ScriptExecSpec` — specification for deferred script execution
|
|
30
27
|
(used by ``ON ERROR_HALT`` / ``ON CANCEL_HALT EXECUTE SCRIPT``).
|
|
31
28
|
|
|
@@ -46,10 +43,7 @@ from execsql.script.control import BatchLevels
|
|
|
46
43
|
from execsql.script.engine import (
|
|
47
44
|
MetaCommand,
|
|
48
45
|
MetaCommandList,
|
|
49
|
-
MetacommandStmt,
|
|
50
|
-
ScriptCmd,
|
|
51
46
|
ScriptExecSpec,
|
|
52
|
-
SqlStmt,
|
|
53
47
|
current_script_line,
|
|
54
48
|
set_dynamic_system_vars,
|
|
55
49
|
set_static_system_vars,
|
|
@@ -84,9 +78,6 @@ __all__ = [
|
|
|
84
78
|
"ScriptArgSubVarSet",
|
|
85
79
|
"MetaCommand",
|
|
86
80
|
"MetaCommandList",
|
|
87
|
-
"SqlStmt",
|
|
88
|
-
"MetacommandStmt",
|
|
89
|
-
"ScriptCmd",
|
|
90
81
|
"ScriptExecSpec",
|
|
91
82
|
"set_dynamic_system_vars",
|
|
92
83
|
"set_static_system_vars",
|
execsql/script/ast.py
CHANGED
|
@@ -6,7 +6,7 @@ Defines the node types that make up the execsql AST.
|
|
|
6
6
|
|
|
7
7
|
Design principles:
|
|
8
8
|
- Every node carries a :class:`SourceSpan` so that error messages,
|
|
9
|
-
|
|
9
|
+
``execsql lint``, and the formatter can report precise source locations.
|
|
10
10
|
- Block structures (IF, LOOP, BATCH, SCRIPT) are represented as nodes
|
|
11
11
|
whose ``body`` (and optional ``else_body``, ``elseif_clauses``) contain
|
|
12
12
|
child nodes, forming the tree structure.
|
execsql/script/engine.py
CHANGED
|
@@ -11,9 +11,6 @@ live in :mod:`execsql.script.parser` and
|
|
|
11
11
|
Classes:
|
|
12
12
|
- :class:`MetaCommand` — one entry in the metacommand dispatch table.
|
|
13
13
|
- :class:`MetaCommandList` — ordered list of :class:`MetaCommand` entries with a keyword index.
|
|
14
|
-
- :class:`SqlStmt` — wraps a single SQL string for execution.
|
|
15
|
-
- :class:`MetacommandStmt` — wraps a metacommand line for dispatch.
|
|
16
|
-
- :class:`ScriptCmd` — pairs a statement with its source-file location.
|
|
17
14
|
- :class:`ScriptExecSpec` — specification for deferred script execution.
|
|
18
15
|
|
|
19
16
|
Functions:
|
|
@@ -28,7 +25,7 @@ import os
|
|
|
28
25
|
import re
|
|
29
26
|
import uuid
|
|
30
27
|
from pathlib import Path
|
|
31
|
-
from typing import Any
|
|
28
|
+
from typing import Any
|
|
32
29
|
|
|
33
30
|
import execsql.state as _state
|
|
34
31
|
from execsql.exceptions import ErrInfo
|
|
@@ -38,9 +35,6 @@ from execsql.utils.errors import exception_desc
|
|
|
38
35
|
__all__ = [
|
|
39
36
|
"MetaCommand",
|
|
40
37
|
"MetaCommandList",
|
|
41
|
-
"SqlStmt",
|
|
42
|
-
"MetacommandStmt",
|
|
43
|
-
"ScriptCmd",
|
|
44
38
|
"ScriptExecSpec",
|
|
45
39
|
"set_system_vars",
|
|
46
40
|
"substitute_vars",
|
|
@@ -56,12 +50,16 @@ __all__ = [
|
|
|
56
50
|
class MetaCommand:
|
|
57
51
|
"""A single entry in the metacommand dispatch table.
|
|
58
52
|
|
|
59
|
-
Holds a
|
|
60
|
-
|
|
61
|
-
|
|
53
|
+
Holds a regex, a handler function, and execution-control flags. Call
|
|
54
|
+
:meth:`run` with a raw command string to attempt a match and invoke the
|
|
55
|
+
handler.
|
|
56
|
+
|
|
57
|
+
*rx* is a compiled pattern, or a pattern string that is compiled
|
|
58
|
+
(case-insensitively) the first time :attr:`rx` is read. The dispatch
|
|
59
|
+
table registers some 1,700 patterns and a script uses a few dozen, so
|
|
60
|
+
compiling them all up front cost most of a second on every run.
|
|
62
61
|
"""
|
|
63
62
|
|
|
64
|
-
# A compiled metacommand that can be run if it matches a metacommand command string.
|
|
65
63
|
def __init__(
|
|
66
64
|
self,
|
|
67
65
|
rx: Any,
|
|
@@ -70,6 +68,8 @@ class MetaCommand:
|
|
|
70
68
|
set_error_flag: bool = True,
|
|
71
69
|
category: str | None = None,
|
|
72
70
|
) -> None:
|
|
71
|
+
self._pattern: str
|
|
72
|
+
self._rx: Any = None
|
|
73
73
|
self.rx = rx
|
|
74
74
|
self.exec_fn = exec_func
|
|
75
75
|
self.description = description
|
|
@@ -77,8 +77,32 @@ class MetaCommand:
|
|
|
77
77
|
self.category = category
|
|
78
78
|
self.hitcount = 0
|
|
79
79
|
|
|
80
|
+
@property
|
|
81
|
+
def rx(self) -> Any:
|
|
82
|
+
"""The compiled pattern, compiled on first use."""
|
|
83
|
+
if self._rx is None:
|
|
84
|
+
self._rx = re.compile(self._pattern, re.I)
|
|
85
|
+
return self._rx
|
|
86
|
+
|
|
87
|
+
@rx.setter
|
|
88
|
+
def rx(self, value: Any) -> None:
|
|
89
|
+
if isinstance(value, str):
|
|
90
|
+
self._pattern, self._rx = value, None
|
|
91
|
+
else:
|
|
92
|
+
self._pattern, self._rx = value.pattern, value
|
|
93
|
+
|
|
94
|
+
@property
|
|
95
|
+
def pattern(self) -> str:
|
|
96
|
+
"""The pattern string, without compiling it."""
|
|
97
|
+
return self._pattern
|
|
98
|
+
|
|
99
|
+
@property
|
|
100
|
+
def compiled(self) -> bool:
|
|
101
|
+
"""Whether :attr:`rx` has been compiled yet."""
|
|
102
|
+
return self._rx is not None
|
|
103
|
+
|
|
80
104
|
def __repr__(self) -> str:
|
|
81
|
-
return f"MetaCommand({self.
|
|
105
|
+
return f"MetaCommand({self.pattern!r}, {self.exec_fn!r}, {self.description!r})"
|
|
82
106
|
|
|
83
107
|
def run(self, cmd_str: str) -> tuple:
|
|
84
108
|
"""Match *cmd_str* against this entry's regex and, if it matches, invoke the handler.
|
|
@@ -97,8 +121,9 @@ class MetaCommand:
|
|
|
97
121
|
raise
|
|
98
122
|
except ErrInfo as errinf:
|
|
99
123
|
er = errinf
|
|
100
|
-
except Exception:
|
|
124
|
+
except Exception as exc:
|
|
101
125
|
er = ErrInfo("cmd", command_text=cmd_str, exception_msg=exception_desc())
|
|
126
|
+
er.__cause__ = exc
|
|
102
127
|
if er:
|
|
103
128
|
if _state.status.halt_on_metacommand_err:
|
|
104
129
|
from execsql.utils.errors import exit_now
|
|
@@ -170,18 +195,17 @@ class MetaCommandList:
|
|
|
170
195
|
"""Register one or more regex patterns as a new :class:`MetaCommand` entry.
|
|
171
196
|
|
|
172
197
|
*matching_regexes* may be a single pattern string or a list/tuple of
|
|
173
|
-
patterns; each
|
|
174
|
-
the dispatch list so that later registrations take priority.
|
|
198
|
+
patterns; each becomes a separate :class:`MetaCommand` prepended to
|
|
199
|
+
the dispatch list so that later registrations take priority. The
|
|
200
|
+
patterns are compiled when first matched, not here.
|
|
175
201
|
"""
|
|
176
202
|
if isinstance(matching_regexes, (tuple, list)):
|
|
177
203
|
raw_patterns = list(matching_regexes)
|
|
178
|
-
regexes = [re.compile(rx, re.I) for rx in raw_patterns]
|
|
179
204
|
else:
|
|
180
205
|
raw_patterns = [matching_regexes]
|
|
181
|
-
|
|
182
|
-
for rx, raw in zip(regexes, raw_patterns):
|
|
206
|
+
for raw in raw_patterns:
|
|
183
207
|
mc = MetaCommand(
|
|
184
|
-
|
|
208
|
+
raw,
|
|
185
209
|
exec_func,
|
|
186
210
|
description,
|
|
187
211
|
set_error_flag,
|
|
@@ -205,7 +229,7 @@ class MetaCommandList:
|
|
|
205
229
|
def keywords_by_category(self) -> dict[str, list[str]]:
|
|
206
230
|
"""Return ``{category: [keyword, ...]}`` from entries that have both.
|
|
207
231
|
|
|
208
|
-
Used by
|
|
232
|
+
Used by ``execsql list keywords`` to introspect the dispatch table.
|
|
209
233
|
"""
|
|
210
234
|
result: dict[str, list[str]] = {}
|
|
211
235
|
for mc in self._commands:
|
|
@@ -239,89 +263,6 @@ class MetaCommandList:
|
|
|
239
263
|
return None
|
|
240
264
|
|
|
241
265
|
|
|
242
|
-
# ---------------------------------------------------------------------------
|
|
243
|
-
# SqlStmt / MetacommandStmt
|
|
244
|
-
# ---------------------------------------------------------------------------
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
class SqlStmt:
|
|
248
|
-
"""A single SQL statement ready to be executed against the active database.
|
|
249
|
-
|
|
250
|
-
Data class only — the legacy ``.run()`` method was removed when the AST
|
|
251
|
-
executor became the sole engine. SQL execution now goes through
|
|
252
|
-
:func:`execsql.script.executor._exec_sql`.
|
|
253
|
-
"""
|
|
254
|
-
|
|
255
|
-
def __init__(self, sql_statement: str) -> None:
|
|
256
|
-
self.statement = re.sub(r"\s*;(\s*;\s*)+$", ";", sql_statement)
|
|
257
|
-
|
|
258
|
-
def __repr__(self) -> str:
|
|
259
|
-
return f"SqlStmt({self.statement})"
|
|
260
|
-
|
|
261
|
-
def commandline(self) -> str:
|
|
262
|
-
"""Return the raw SQL statement text."""
|
|
263
|
-
return self.statement
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
class MetacommandStmt:
|
|
267
|
-
"""A single execsql metacommand line.
|
|
268
|
-
|
|
269
|
-
Data class only — the legacy ``.run()`` method was removed when the AST
|
|
270
|
-
executor became the sole engine. Metacommand dispatch now goes through
|
|
271
|
-
:func:`execsql.script.executor._exec_metacommand`.
|
|
272
|
-
"""
|
|
273
|
-
|
|
274
|
-
def __init__(self, metacommand_statement: str) -> None:
|
|
275
|
-
self.statement = metacommand_statement
|
|
276
|
-
|
|
277
|
-
def __repr__(self) -> str:
|
|
278
|
-
return f"MetacommandStmt({self.statement})"
|
|
279
|
-
|
|
280
|
-
def commandline(self) -> str:
|
|
281
|
-
"""Return the metacommand line in its canonical ``-- !x! ...`` form."""
|
|
282
|
-
return "-- !x! " + self.statement
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
# ---------------------------------------------------------------------------
|
|
286
|
-
# ScriptCmd
|
|
287
|
-
# ---------------------------------------------------------------------------
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
class ScriptCmd:
|
|
291
|
-
"""A parsed script item: either a :class:`SqlStmt` or a :class:`MetacommandStmt`, with source location."""
|
|
292
|
-
|
|
293
|
-
# A SQL script object that is either a SQL statement or a metacommand.
|
|
294
|
-
def __init__(
|
|
295
|
-
self,
|
|
296
|
-
command_source_name: str,
|
|
297
|
-
command_line_no: int,
|
|
298
|
-
command_type: str,
|
|
299
|
-
script_command: Any,
|
|
300
|
-
) -> None:
|
|
301
|
-
self.source = command_source_name
|
|
302
|
-
self.line_no = command_line_no
|
|
303
|
-
self.command_type = command_type
|
|
304
|
-
self.command = script_command
|
|
305
|
-
# MIGRATION NOTE: differs from monolith (execsql.py) — source_dir and source_name are
|
|
306
|
-
# resolved once at construction rather than on every statement execution. For absolute
|
|
307
|
-
# paths (the common case) the result is identical. For relative paths the value is
|
|
308
|
-
# anchored to the CWD at script-load time rather than at each statement's execution time;
|
|
309
|
-
# the original per-statement resolve could yield inconsistent values across statements of
|
|
310
|
-
# the same script if a CD metacommand ran between them.
|
|
311
|
-
_p = Path(command_source_name)
|
|
312
|
-
self.source_dir: str = str(_p.resolve().parent) + os.sep
|
|
313
|
-
self.source_name: str = _p.name
|
|
314
|
-
|
|
315
|
-
def __repr__(self) -> str:
|
|
316
|
-
return f"ScriptCmd({self.source!r}, {self.line_no!r}, {self.command_type!r}, {repr(self.command)!r})"
|
|
317
|
-
|
|
318
|
-
def current_script_line(self) -> tuple[str, int]:
|
|
319
|
-
return (self.source, self.line_no)
|
|
320
|
-
|
|
321
|
-
def commandline(self) -> str:
|
|
322
|
-
return cast(str, self.command.statement if self.command_type == "sql" else "-- !x! " + self.command.statement)
|
|
323
|
-
|
|
324
|
-
|
|
325
266
|
# ---------------------------------------------------------------------------
|
|
326
267
|
# ScriptExecSpec
|
|
327
268
|
# ---------------------------------------------------------------------------
|
|
@@ -509,7 +450,8 @@ def current_script_line() -> tuple[str, int]:
|
|
|
509
450
|
"""Return ``(source_name, line_number)`` for the command currently executing.
|
|
510
451
|
|
|
511
452
|
Reads from ``_state.last_command``, which the AST executor updates on
|
|
512
|
-
every statement via
|
|
453
|
+
every statement via :class:`~execsql.script.executor.ExecutingStatement`.
|
|
454
|
+
Returns ``("", 0)``
|
|
513
455
|
when nothing has executed yet (e.g. during early initialization errors).
|
|
514
456
|
"""
|
|
515
457
|
last = getattr(_state, "last_command", None)
|