execsql2 2.9.0__py3-none-any.whl → 2.10.1__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/metacommands/__init__.py +3 -0
- execsql/metacommands/conditions.py +148 -0
- execsql/metacommands/debug_repl.py +227 -0
- execsql/metacommands/dispatch.py +12 -0
- execsql/script/engine.py +5 -0
- execsql/state.py +9 -0
- {execsql2-2.9.0.dist-info → execsql2-2.10.1.dist-info}/METADATA +1 -1
- {execsql2-2.9.0.dist-info → execsql2-2.10.1.dist-info}/RECORD +27 -26
- {execsql2-2.9.0.data → execsql2-2.10.1.data}/data/execsql2_extras/README.md +0 -0
- {execsql2-2.9.0.data → execsql2-2.10.1.data}/data/execsql2_extras/config_settings.sqlite +0 -0
- {execsql2-2.9.0.data → execsql2-2.10.1.data}/data/execsql2_extras/example_config_prompt.sql +0 -0
- {execsql2-2.9.0.data → execsql2-2.10.1.data}/data/execsql2_extras/execsql.conf +0 -0
- {execsql2-2.9.0.data → execsql2-2.10.1.data}/data/execsql2_extras/make_config_db.sql +0 -0
- {execsql2-2.9.0.data → execsql2-2.10.1.data}/data/execsql2_extras/md_compare.sql +0 -0
- {execsql2-2.9.0.data → execsql2-2.10.1.data}/data/execsql2_extras/md_glossary.sql +0 -0
- {execsql2-2.9.0.data → execsql2-2.10.1.data}/data/execsql2_extras/md_upsert.sql +0 -0
- {execsql2-2.9.0.data → execsql2-2.10.1.data}/data/execsql2_extras/pg_compare.sql +0 -0
- {execsql2-2.9.0.data → execsql2-2.10.1.data}/data/execsql2_extras/pg_glossary.sql +0 -0
- {execsql2-2.9.0.data → execsql2-2.10.1.data}/data/execsql2_extras/pg_upsert.sql +0 -0
- {execsql2-2.9.0.data → execsql2-2.10.1.data}/data/execsql2_extras/script_template.sql +0 -0
- {execsql2-2.9.0.data → execsql2-2.10.1.data}/data/execsql2_extras/ss_compare.sql +0 -0
- {execsql2-2.9.0.data → execsql2-2.10.1.data}/data/execsql2_extras/ss_glossary.sql +0 -0
- {execsql2-2.9.0.data → execsql2-2.10.1.data}/data/execsql2_extras/ss_upsert.sql +0 -0
- {execsql2-2.9.0.dist-info → execsql2-2.10.1.dist-info}/WHEEL +0 -0
- {execsql2-2.9.0.dist-info → execsql2-2.10.1.dist-info}/entry_points.txt +0 -0
- {execsql2-2.9.0.dist-info → execsql2-2.10.1.dist-info}/licenses/LICENSE.txt +0 -0
- {execsql2-2.9.0.dist-info → execsql2-2.10.1.dist-info}/licenses/NOTICE +0 -0
execsql/metacommands/__init__.py
CHANGED
|
@@ -100,6 +100,7 @@ from execsql.metacommands.debug import (
|
|
|
100
100
|
x_debug_write_subvars,
|
|
101
101
|
x_debug_write_config,
|
|
102
102
|
)
|
|
103
|
+
from execsql.metacommands.debug_repl import x_breakpoint
|
|
103
104
|
from execsql.metacommands.io import (
|
|
104
105
|
x_export,
|
|
105
106
|
x_export_query,
|
|
@@ -300,6 +301,8 @@ __all__ = [
|
|
|
300
301
|
"x_debug_log_config",
|
|
301
302
|
"x_debug_write_subvars",
|
|
302
303
|
"x_debug_write_config",
|
|
304
|
+
# debug repl handlers
|
|
305
|
+
"x_breakpoint",
|
|
303
306
|
# io handlers
|
|
304
307
|
"x_export",
|
|
305
308
|
"x_export_query",
|
|
@@ -71,6 +71,124 @@ def xf_hasrows(**kwargs: Any) -> bool:
|
|
|
71
71
|
return nrows > 0
|
|
72
72
|
|
|
73
73
|
|
|
74
|
+
def _row_count(queryname: str, sql_context: str, metacommandline: str) -> int:
|
|
75
|
+
"""Return the number of rows in *queryname*, raising ErrInfo on failure.
|
|
76
|
+
|
|
77
|
+
Args:
|
|
78
|
+
queryname: Table or view name to count rows in.
|
|
79
|
+
sql_context: The SQL string to include in error messages.
|
|
80
|
+
metacommandline: The full metacommand line for error context.
|
|
81
|
+
|
|
82
|
+
Returns:
|
|
83
|
+
Integer row count.
|
|
84
|
+
|
|
85
|
+
Raises:
|
|
86
|
+
ErrInfo: If the query fails or the result is not numeric.
|
|
87
|
+
"""
|
|
88
|
+
sql = f"select count(*) from {queryname};"
|
|
89
|
+
try:
|
|
90
|
+
_hdrs, rec = _state.dbs.current().select_data(sql)
|
|
91
|
+
except ErrInfo:
|
|
92
|
+
raise
|
|
93
|
+
except Exception as e:
|
|
94
|
+
raise ErrInfo("db", sql, exception_msg=exception_desc()) from e
|
|
95
|
+
try:
|
|
96
|
+
return int(rec[0][0])
|
|
97
|
+
except (IndexError, TypeError, ValueError) as e:
|
|
98
|
+
raise ErrInfo(
|
|
99
|
+
type="cmd",
|
|
100
|
+
command_text=metacommandline,
|
|
101
|
+
other_msg=f"Could not read row count for {queryname}.",
|
|
102
|
+
) from e
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def _parse_row_count_n(raw: str, metacommandline: str) -> int:
|
|
106
|
+
"""Parse and return the numeric threshold N from the matched group.
|
|
107
|
+
|
|
108
|
+
Args:
|
|
109
|
+
raw: The raw string captured by the regex group (``n``).
|
|
110
|
+
metacommandline: The full metacommand line for error context.
|
|
111
|
+
|
|
112
|
+
Returns:
|
|
113
|
+
Integer value of *raw*.
|
|
114
|
+
|
|
115
|
+
Raises:
|
|
116
|
+
ErrInfo: If *raw* cannot be parsed as an integer.
|
|
117
|
+
"""
|
|
118
|
+
try:
|
|
119
|
+
return int(raw.strip())
|
|
120
|
+
except (ValueError, TypeError) as e:
|
|
121
|
+
raise ErrInfo(
|
|
122
|
+
type="cmd",
|
|
123
|
+
command_text=metacommandline,
|
|
124
|
+
other_msg=f"ROW_COUNT threshold must be an integer; got {raw!r}.",
|
|
125
|
+
) from e
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def xf_row_count_gt(**kwargs: Any) -> bool:
|
|
129
|
+
"""Return True if the row count of *queryname* is strictly greater than N.
|
|
130
|
+
|
|
131
|
+
Args:
|
|
132
|
+
**kwargs: Named groups from the regex match, plus ``metacommandline``.
|
|
133
|
+
Required keys: ``queryname``, ``n``.
|
|
134
|
+
|
|
135
|
+
Returns:
|
|
136
|
+
True if ``count(*) > N``.
|
|
137
|
+
"""
|
|
138
|
+
queryname = kwargs["queryname"]
|
|
139
|
+
mcl = kwargs["metacommandline"]
|
|
140
|
+
n = _parse_row_count_n(kwargs["n"], mcl)
|
|
141
|
+
return _row_count(queryname, f"select count(*) from {queryname};", mcl) > n
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def xf_row_count_gte(**kwargs: Any) -> bool:
|
|
145
|
+
"""Return True if the row count of *queryname* is greater than or equal to N.
|
|
146
|
+
|
|
147
|
+
Args:
|
|
148
|
+
**kwargs: Named groups from the regex match, plus ``metacommandline``.
|
|
149
|
+
Required keys: ``queryname``, ``n``.
|
|
150
|
+
|
|
151
|
+
Returns:
|
|
152
|
+
True if ``count(*) >= N``.
|
|
153
|
+
"""
|
|
154
|
+
queryname = kwargs["queryname"]
|
|
155
|
+
mcl = kwargs["metacommandline"]
|
|
156
|
+
n = _parse_row_count_n(kwargs["n"], mcl)
|
|
157
|
+
return _row_count(queryname, f"select count(*) from {queryname};", mcl) >= n
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def xf_row_count_eq(**kwargs: Any) -> bool:
|
|
161
|
+
"""Return True if the row count of *queryname* equals N exactly.
|
|
162
|
+
|
|
163
|
+
Args:
|
|
164
|
+
**kwargs: Named groups from the regex match, plus ``metacommandline``.
|
|
165
|
+
Required keys: ``queryname``, ``n``.
|
|
166
|
+
|
|
167
|
+
Returns:
|
|
168
|
+
True if ``count(*) == N``.
|
|
169
|
+
"""
|
|
170
|
+
queryname = kwargs["queryname"]
|
|
171
|
+
mcl = kwargs["metacommandline"]
|
|
172
|
+
n = _parse_row_count_n(kwargs["n"], mcl)
|
|
173
|
+
return _row_count(queryname, f"select count(*) from {queryname};", mcl) == n
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def xf_row_count_lt(**kwargs: Any) -> bool:
|
|
177
|
+
"""Return True if the row count of *queryname* is strictly less than N.
|
|
178
|
+
|
|
179
|
+
Args:
|
|
180
|
+
**kwargs: Named groups from the regex match, plus ``metacommandline``.
|
|
181
|
+
Required keys: ``queryname``, ``n``.
|
|
182
|
+
|
|
183
|
+
Returns:
|
|
184
|
+
True if ``count(*) < N``.
|
|
185
|
+
"""
|
|
186
|
+
queryname = kwargs["queryname"]
|
|
187
|
+
mcl = kwargs["metacommandline"]
|
|
188
|
+
n = _parse_row_count_n(kwargs["n"], mcl)
|
|
189
|
+
return _row_count(queryname, f"select count(*) from {queryname};", mcl) < n
|
|
190
|
+
|
|
191
|
+
|
|
74
192
|
def xf_sqlerror(**kwargs: Any) -> bool:
|
|
75
193
|
return _state.status.sql_error
|
|
76
194
|
|
|
@@ -495,6 +613,36 @@ def build_conditional_table() -> Any:
|
|
|
495
613
|
mcl.add(r"^\s*HASROWS\((?P<queryname>[^)]+)\)", xf_hasrows, description="HASROWS", category="condition")
|
|
496
614
|
mcl.add(r"^\s*HAS_ROWS\((?P<queryname>[^)]+)\)", xf_hasrows)
|
|
497
615
|
|
|
616
|
+
# ROW_COUNT comparisons — ROW_COUNT_GT/GTE/EQ/LT(table, N)
|
|
617
|
+
# Table name: unquoted, double-quoted, or single-quoted. N: integer literal.
|
|
618
|
+
_rc_table = r"(?P<queryname>[A-Za-z0-9_.\"'\[\]]+)"
|
|
619
|
+
_rc_n = r"(?P<n>\d+)"
|
|
620
|
+
_rc_sep = r"\s*,\s*"
|
|
621
|
+
mcl.add(
|
|
622
|
+
rf"^\s*ROW_COUNT_GT\s*\(\s*{_rc_table}{_rc_sep}{_rc_n}\s*\)",
|
|
623
|
+
xf_row_count_gt,
|
|
624
|
+
description="ROW_COUNT_GT",
|
|
625
|
+
category="condition",
|
|
626
|
+
)
|
|
627
|
+
mcl.add(
|
|
628
|
+
rf"^\s*ROW_COUNT_GTE\s*\(\s*{_rc_table}{_rc_sep}{_rc_n}\s*\)",
|
|
629
|
+
xf_row_count_gte,
|
|
630
|
+
description="ROW_COUNT_GTE",
|
|
631
|
+
category="condition",
|
|
632
|
+
)
|
|
633
|
+
mcl.add(
|
|
634
|
+
rf"^\s*ROW_COUNT_EQ\s*\(\s*{_rc_table}{_rc_sep}{_rc_n}\s*\)",
|
|
635
|
+
xf_row_count_eq,
|
|
636
|
+
description="ROW_COUNT_EQ",
|
|
637
|
+
category="condition",
|
|
638
|
+
)
|
|
639
|
+
mcl.add(
|
|
640
|
+
rf"^\s*ROW_COUNT_LT\s*\(\s*{_rc_table}{_rc_sep}{_rc_n}\s*\)",
|
|
641
|
+
xf_row_count_lt,
|
|
642
|
+
description="ROW_COUNT_LT",
|
|
643
|
+
category="condition",
|
|
644
|
+
)
|
|
645
|
+
|
|
498
646
|
# Status predicates
|
|
499
647
|
mcl.add(r"^\s*sql_error\(\s*\)", xf_sqlerror, description="SQL_ERROR", category="condition")
|
|
500
648
|
mcl.add(r"^\s*dialog_canceled\(\s*\)", xf_dialogcanceled, description="DIALOG_CANCELED", category="condition")
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
Interactive debug REPL metacommand handler for execsql.
|
|
5
|
+
|
|
6
|
+
Implements ``x_breakpoint`` — the ``BREAKPOINT`` metacommand — which pauses
|
|
7
|
+
script execution and drops into an interactive read-eval-print loop.
|
|
8
|
+
|
|
9
|
+
The REPL allows the user to:
|
|
10
|
+
|
|
11
|
+
- Inspect and print substitution variables.
|
|
12
|
+
- Run ad-hoc SQL queries against the current database.
|
|
13
|
+
- Step through the script one statement at a time.
|
|
14
|
+
- Resume or abort execution.
|
|
15
|
+
|
|
16
|
+
In non-interactive environments (CI, piped input, ``sys.stdin.isatty()`` is
|
|
17
|
+
``False``) the metacommand is silently skipped so automated pipelines are not
|
|
18
|
+
blocked.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
import sys
|
|
22
|
+
from typing import Any
|
|
23
|
+
|
|
24
|
+
import execsql.state as _state
|
|
25
|
+
|
|
26
|
+
__all__ = ["x_breakpoint"]
|
|
27
|
+
|
|
28
|
+
# ---------------------------------------------------------------------------
|
|
29
|
+
# Public handler
|
|
30
|
+
# ---------------------------------------------------------------------------
|
|
31
|
+
|
|
32
|
+
_HELP_TEXT = """\
|
|
33
|
+
execsql debug REPL commands:
|
|
34
|
+
continue c Resume script execution
|
|
35
|
+
abort q quit Halt the script (exit 1)
|
|
36
|
+
vars List all substitution variables and their values
|
|
37
|
+
$VARNAME Print a single variable's value (also &VAR, @VAR)
|
|
38
|
+
SELECT ...; Run ad-hoc SQL against the current database
|
|
39
|
+
next n Execute the next statement then pause again (step mode)
|
|
40
|
+
stack Show the command-list stack (script name, line, depth)
|
|
41
|
+
help Show this help text
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def x_breakpoint(**kwargs: Any) -> None:
|
|
46
|
+
"""Pause execution and enter the interactive debug REPL.
|
|
47
|
+
|
|
48
|
+
If ``sys.stdin`` is not a TTY (CI, piped input), the metacommand is
|
|
49
|
+
silently skipped — scripts will not hang in automation.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
**kwargs: Keyword arguments injected by the dispatch table (unused).
|
|
53
|
+
"""
|
|
54
|
+
if not sys.stdin.isatty():
|
|
55
|
+
return
|
|
56
|
+
_debug_repl()
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
# ---------------------------------------------------------------------------
|
|
60
|
+
# REPL core
|
|
61
|
+
# ---------------------------------------------------------------------------
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _debug_repl() -> None:
|
|
65
|
+
"""Interactive read-eval-print loop for script debugging.
|
|
66
|
+
|
|
67
|
+
Reads commands from stdin until the user types ``continue`` or ``abort``,
|
|
68
|
+
or until EOF / KeyboardInterrupt.
|
|
69
|
+
"""
|
|
70
|
+
try:
|
|
71
|
+
import readline as _readline # noqa: F401 — side-effect: enables history/arrow keys
|
|
72
|
+
except ImportError:
|
|
73
|
+
pass # readline not available on Windows; continue without it
|
|
74
|
+
|
|
75
|
+
_write("\n[Breakpoint] Script paused. Type 'help' for commands, 'continue' to resume.\n")
|
|
76
|
+
|
|
77
|
+
while True:
|
|
78
|
+
try:
|
|
79
|
+
line = input("execsql debug> ").strip()
|
|
80
|
+
except EOFError:
|
|
81
|
+
_write("\n")
|
|
82
|
+
return # Ctrl-D → continue
|
|
83
|
+
except KeyboardInterrupt:
|
|
84
|
+
_write("\n")
|
|
85
|
+
return # Ctrl-C → continue
|
|
86
|
+
|
|
87
|
+
if not line:
|
|
88
|
+
continue
|
|
89
|
+
|
|
90
|
+
lower = line.lower()
|
|
91
|
+
|
|
92
|
+
if lower in ("continue", "c"):
|
|
93
|
+
return
|
|
94
|
+
elif lower in ("abort", "q", "quit"):
|
|
95
|
+
raise SystemExit(1)
|
|
96
|
+
elif lower == "help":
|
|
97
|
+
_write(_HELP_TEXT)
|
|
98
|
+
elif lower == "vars":
|
|
99
|
+
_print_all_vars()
|
|
100
|
+
elif lower == "stack":
|
|
101
|
+
_print_stack()
|
|
102
|
+
elif lower in ("next", "n"):
|
|
103
|
+
_enable_step_mode()
|
|
104
|
+
return
|
|
105
|
+
elif line[0] in ("$", "&", "@"):
|
|
106
|
+
_print_var(line)
|
|
107
|
+
elif line.rstrip().endswith(";"):
|
|
108
|
+
_run_sql(line)
|
|
109
|
+
else:
|
|
110
|
+
_write(f"Unknown command: {line!r}. Type 'help' for available commands.\n")
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
# ---------------------------------------------------------------------------
|
|
114
|
+
# REPL command implementations
|
|
115
|
+
# ---------------------------------------------------------------------------
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def _write(text: str) -> None:
|
|
119
|
+
"""Write *text* to the execsql output stream (falls back to stdout)."""
|
|
120
|
+
output = _state.output
|
|
121
|
+
if output is not None:
|
|
122
|
+
output.write(text)
|
|
123
|
+
else:
|
|
124
|
+
sys.stdout.write(text)
|
|
125
|
+
sys.stdout.flush()
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def _print_all_vars() -> None:
|
|
129
|
+
"""Print all substitution variables and their current values."""
|
|
130
|
+
subvars = _state.subvars
|
|
131
|
+
if subvars is None:
|
|
132
|
+
_write(" (no substitution variables defined)\n")
|
|
133
|
+
return
|
|
134
|
+
items = subvars.substitutions # list of (name, value) tuples
|
|
135
|
+
if not items:
|
|
136
|
+
_write(" (no substitution variables defined)\n")
|
|
137
|
+
return
|
|
138
|
+
# Compute column width for aligned output.
|
|
139
|
+
max_name = max((len(name) for name, _ in items), default=0)
|
|
140
|
+
for name, value in sorted(items):
|
|
141
|
+
_write(f" {name:<{max_name}} = {value!r}\n")
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def _print_var(varname: str) -> None:
|
|
145
|
+
"""Print the value of a single substitution variable.
|
|
146
|
+
|
|
147
|
+
Args:
|
|
148
|
+
varname: The variable reference as typed by the user, e.g. ``$FOO``.
|
|
149
|
+
"""
|
|
150
|
+
subvars = _state.subvars
|
|
151
|
+
if subvars is None:
|
|
152
|
+
_write(f" {varname}: (substitution variables not initialised)\n")
|
|
153
|
+
return
|
|
154
|
+
# Try the name as typed first, then without the sigil prefix ($, &, @, #, ~).
|
|
155
|
+
# SUB creates variables without a prefix (e.g., "logfile"), but users
|
|
156
|
+
# naturally type "$logfile" at the prompt.
|
|
157
|
+
value = subvars.varvalue(varname)
|
|
158
|
+
if value is None and len(varname) > 1 and varname[0] in "$&@#~":
|
|
159
|
+
value = subvars.varvalue(varname[1:])
|
|
160
|
+
if value is None:
|
|
161
|
+
_write(f" {varname}: (undefined)\n")
|
|
162
|
+
else:
|
|
163
|
+
_write(f" {varname} = {value!r}\n")
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def _print_stack() -> None:
|
|
167
|
+
"""Print the current command-list stack (script name, line number, depth)."""
|
|
168
|
+
stack = _state.commandliststack
|
|
169
|
+
if not stack:
|
|
170
|
+
_write(" (command list stack is empty)\n")
|
|
171
|
+
return
|
|
172
|
+
_write(f" Stack depth: {len(stack)}\n")
|
|
173
|
+
for depth, cmdlist in enumerate(stack):
|
|
174
|
+
listname = getattr(cmdlist, "listname", "<unknown>")
|
|
175
|
+
cmdptr = getattr(cmdlist, "cmdptr", 0)
|
|
176
|
+
_write(f" [{depth}] {listname} (cursor at index {cmdptr})\n")
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def _run_sql(sql: str) -> None:
|
|
180
|
+
"""Execute ad-hoc SQL against the current database and pretty-print the results.
|
|
181
|
+
|
|
182
|
+
Args:
|
|
183
|
+
sql: A complete SQL statement ending with a semicolon.
|
|
184
|
+
"""
|
|
185
|
+
dbs = _state.dbs
|
|
186
|
+
if dbs is None:
|
|
187
|
+
_write(" (no database connection is active)\n")
|
|
188
|
+
return
|
|
189
|
+
db = dbs.current()
|
|
190
|
+
if db is None:
|
|
191
|
+
_write(" (no database connection is active)\n")
|
|
192
|
+
return
|
|
193
|
+
try:
|
|
194
|
+
colnames, rows = db.select_data(sql)
|
|
195
|
+
except Exception as exc:
|
|
196
|
+
_write(f" SQL error: {exc}\n")
|
|
197
|
+
return
|
|
198
|
+
|
|
199
|
+
if not colnames:
|
|
200
|
+
_write(" (query returned no columns)\n")
|
|
201
|
+
return
|
|
202
|
+
|
|
203
|
+
# Build a simple text table.
|
|
204
|
+
col_widths = [len(c) for c in colnames]
|
|
205
|
+
str_rows: list[list[str]] = []
|
|
206
|
+
for row in rows:
|
|
207
|
+
str_row = [str(v) if v is not None else "NULL" for v in row]
|
|
208
|
+
str_rows.append(str_row)
|
|
209
|
+
for i, cell in enumerate(str_row):
|
|
210
|
+
col_widths[i] = max(col_widths[i], len(cell))
|
|
211
|
+
|
|
212
|
+
sep = "+-" + "-+-".join("-" * w for w in col_widths) + "-+"
|
|
213
|
+
header = "| " + " | ".join(c.ljust(col_widths[i]) for i, c in enumerate(colnames)) + " |"
|
|
214
|
+
_write(sep + "\n")
|
|
215
|
+
_write(header + "\n")
|
|
216
|
+
_write(sep + "\n")
|
|
217
|
+
for str_row in str_rows:
|
|
218
|
+
data_line = "| " + " | ".join(cell.ljust(col_widths[i]) for i, cell in enumerate(str_row)) + " |"
|
|
219
|
+
_write(data_line + "\n")
|
|
220
|
+
_write(sep + "\n")
|
|
221
|
+
row_word = "row" if len(str_rows) == 1 else "rows"
|
|
222
|
+
_write(f" ({len(str_rows)} {row_word})\n")
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def _enable_step_mode() -> None:
|
|
226
|
+
"""Activate step mode so the engine re-enters the REPL after the next statement."""
|
|
227
|
+
_state.step_mode = True
|
execsql/metacommands/dispatch.py
CHANGED
|
@@ -100,6 +100,7 @@ from execsql.metacommands.debug import (
|
|
|
100
100
|
x_debug_write_odbc_drivers,
|
|
101
101
|
x_debug_write_subvars,
|
|
102
102
|
)
|
|
103
|
+
from execsql.metacommands.debug_repl import x_breakpoint
|
|
103
104
|
from execsql.metacommands.io import (
|
|
104
105
|
x_cd,
|
|
105
106
|
x_copy,
|
|
@@ -1690,6 +1691,17 @@ def build_dispatch_table() -> MetaCommandList:
|
|
|
1690
1691
|
run_when_false=False,
|
|
1691
1692
|
)
|
|
1692
1693
|
|
|
1694
|
+
# ------------------------------------------------------------------
|
|
1695
|
+
# BREAKPOINT
|
|
1696
|
+
# ------------------------------------------------------------------
|
|
1697
|
+
mcl.add(
|
|
1698
|
+
r"^\s*BREAKPOINT\s*$",
|
|
1699
|
+
x_breakpoint,
|
|
1700
|
+
description="BREAKPOINT",
|
|
1701
|
+
category="action",
|
|
1702
|
+
run_when_false=False,
|
|
1703
|
+
)
|
|
1704
|
+
|
|
1693
1705
|
# ------------------------------------------------------------------
|
|
1694
1706
|
# IF / ORIF / ANDIF / ELSEIF / ELSE / ENDIF
|
|
1695
1707
|
# ------------------------------------------------------------------
|
execsql/script/engine.py
CHANGED
|
@@ -506,6 +506,11 @@ class CommandList:
|
|
|
506
506
|
cmditem.command.commandline()[:100],
|
|
507
507
|
),
|
|
508
508
|
)
|
|
509
|
+
if _state.step_mode:
|
|
510
|
+
_state.step_mode = False
|
|
511
|
+
from execsql.metacommands.debug_repl import _debug_repl
|
|
512
|
+
|
|
513
|
+
_debug_repl()
|
|
509
514
|
self.cmdptr += 1
|
|
510
515
|
|
|
511
516
|
def run_next(self) -> None:
|
execsql/state.py
CHANGED
|
@@ -96,6 +96,8 @@ __all__ = [
|
|
|
96
96
|
"gui_manager_thread",
|
|
97
97
|
# Profiling
|
|
98
98
|
"profile_data",
|
|
99
|
+
# Debug REPL
|
|
100
|
+
"step_mode",
|
|
99
101
|
# Version
|
|
100
102
|
"primary_vno",
|
|
101
103
|
"secondary_vno",
|
|
@@ -195,6 +197,8 @@ _CONTEXT_ATTRS: frozenset[str] = frozenset(
|
|
|
195
197
|
"gui_manager_thread",
|
|
196
198
|
# Profiling
|
|
197
199
|
"profile_data",
|
|
200
|
+
# Debug REPL
|
|
201
|
+
"step_mode",
|
|
198
202
|
},
|
|
199
203
|
)
|
|
200
204
|
|
|
@@ -248,6 +252,8 @@ class RuntimeContext:
|
|
|
248
252
|
"gui_manager_thread",
|
|
249
253
|
# Profiling
|
|
250
254
|
"profile_data",
|
|
255
|
+
# Debug REPL
|
|
256
|
+
"step_mode",
|
|
251
257
|
)
|
|
252
258
|
|
|
253
259
|
def __init__(self) -> None:
|
|
@@ -299,6 +305,9 @@ class RuntimeContext:
|
|
|
299
305
|
# Each entry: (source, line_no, command_type, elapsed_secs, command_text_preview)
|
|
300
306
|
self.profile_data: list[tuple] | None = None
|
|
301
307
|
|
|
308
|
+
# Debug REPL — True after a ``next`` command; engine re-enters REPL after next statement.
|
|
309
|
+
self.step_mode: bool = False
|
|
310
|
+
|
|
302
311
|
|
|
303
312
|
# ---------------------------------------------------------------------------
|
|
304
313
|
# Module proxy — transparently delegates context attr access to _ctx
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: execsql2
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.10.1
|
|
4
4
|
Summary: Runs a SQL script against a PostgreSQL, SQLite, MariaDB/MySQL, DuckDB, Firebird, MS-Access, MS-SQL-Server, or Oracle database, or an ODBC DSN. Provides metacommands to import and export data, copy data between databases, conditionally execute SQL and metacommands, and dynamically alter SQL and metacommands with substitution variables.
|
|
5
5
|
Project-URL: Repository, https://github.com/geocoug/execsql
|
|
6
6
|
Project-URL: Issues, https://github.com/geocoug/execsql/issues
|
|
@@ -7,7 +7,7 @@ execsql/format.py,sha256=-6iknDddqbkapMo4NKmT5LAynDLqMW5kHgDWRg0KSws,11990
|
|
|
7
7
|
execsql/models.py,sha256=DxkGp9iWbuZDWPGmnxZp9mvEeyOwxEJNx94fxQQiLfQ,13538
|
|
8
8
|
execsql/parser.py,sha256=mbNSMiAMR1NvNvFtQAZq6nxBOupMGJZXSimLWLtZeNs,15537
|
|
9
9
|
execsql/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
10
|
-
execsql/state.py,sha256=
|
|
10
|
+
execsql/state.py,sha256=ovUQOr78R3LzsDUzBLL9Bq9ZdHiiFVlBvCNzilQ-K-s,15055
|
|
11
11
|
execsql/types.py,sha256=HVWb4umIB9lpxCGgqk3xy1hoGYPfN39xci5mHF0Izq4,31882
|
|
12
12
|
execsql/cli/__init__.py,sha256=Y4lFKvKWyjtMgSLsmACHYG33DFpeAQxddIVrlKURwpg,16081
|
|
13
13
|
execsql/cli/dsn.py,sha256=svaZtrUXFRL2W5G6FRRiKtR6kehOp7urrVhIx_642Z8,2820
|
|
@@ -59,13 +59,14 @@ execsql/importers/csv.py,sha256=Mu848WNzuhVO1ade-WurPyxqGOuVNRO8UwRF3-bav_I,4845
|
|
|
59
59
|
execsql/importers/feather.py,sha256=g2B69d2uv9vmnXcmjFyTVsMP40LYEzFYkhk3gD26mGw,1900
|
|
60
60
|
execsql/importers/ods.py,sha256=MJsdsjropzCvxAA3DDZfAL_AnmZ4yij7DnrjGyDJqHQ,2843
|
|
61
61
|
execsql/importers/xls.py,sha256=e0Zfe47ZiCpA1Ae3XDJ1ko3sCiH3-8U6XLKi6NvD0jQ,3683
|
|
62
|
-
execsql/metacommands/__init__.py,sha256=
|
|
63
|
-
execsql/metacommands/conditions.py,sha256=
|
|
62
|
+
execsql/metacommands/__init__.py,sha256=TT1ARHgHltHqZ7qx4Y62o1h_GOPvUztZKCem-wAE560,11215
|
|
63
|
+
execsql/metacommands/conditions.py,sha256=QUpevCHce9kbKpt6XpHkc73q3bYAIfaBin0b6eaJnYQ,29259
|
|
64
64
|
execsql/metacommands/connect.py,sha256=Nsm0D91i3RX-R2rzQQ-Br-gULaI6Uvdn9fqb7DOAVfE,14804
|
|
65
65
|
execsql/metacommands/control.py,sha256=CBCg0ZKSR-BGejBW5cXwk6aJ9VrYBzCg9C40ofi8qi8,8776
|
|
66
66
|
execsql/metacommands/data.py,sha256=tRQBGTAuW-eJ2tBNWaoZI9OjTyNNyHJISo7gOdL-sm8,11370
|
|
67
67
|
execsql/metacommands/debug.py,sha256=nmfQ2ijUbTQO3drnyV9EzFueGSTfMl-CddP_NlQyI14,8178
|
|
68
|
-
execsql/metacommands/
|
|
68
|
+
execsql/metacommands/debug_repl.py,sha256=UsTup7oYsJ0bNsKKynxQz-4wplFKnIcKSNrkz6gl0VM,7530
|
|
69
|
+
execsql/metacommands/dispatch.py,sha256=1Mae6yqrea6wViFLBsvVt33Zgx4xP8tnhOuB_aQC89c,84054
|
|
69
70
|
execsql/metacommands/io.py,sha256=Duh60caM4go9JczbGYNMKKYpcMimwPzF6EQ_tshKxdE,2971
|
|
70
71
|
execsql/metacommands/io_export.py,sha256=7lkCSnPhXy9FVau9_hT1u68NOVdG2DsWmvUh9hM1QWI,18359
|
|
71
72
|
execsql/metacommands/io_fileops.py,sha256=RKqbWPTYiwiqCZYG-lpih0w1JVOY4RBFdWr3BJb_pnY,9669
|
|
@@ -76,7 +77,7 @@ execsql/metacommands/script_ext.py,sha256=TUgAldB2LSJAwZrCvDDi804hQ1d9BDQD2GDqHN
|
|
|
76
77
|
execsql/metacommands/system.py,sha256=sUR5kLL7idTVg8WXIMdd-Kv7nkERIiaeL0beWsz8NyY,7293
|
|
77
78
|
execsql/script/__init__.py,sha256=pIo0EJ7-vg67rSMbOvbri_BOUgLoGoSEUfJgxUN7ZS0,3380
|
|
78
79
|
execsql/script/control.py,sha256=s-1eZdGARM6H1FwZ6VDdO_f50j7bvvRtTHesfUm9tbc,6144
|
|
79
|
-
execsql/script/engine.py,sha256=
|
|
80
|
+
execsql/script/engine.py,sha256=2WcOfYEOwO7L_NQAd3vk_c2wk1VZKJYSakSl07FBUts,40390
|
|
80
81
|
execsql/script/variables.py,sha256=MOT9XEHucpuuuHQZM5bklxGMBQcwHzwTBxd0q3aO0XY,11641
|
|
81
82
|
execsql/utils/__init__.py,sha256=0uR6JwVJQRX3vceByNBduCAf5dd5assKjeqJUWvpZoA,278
|
|
82
83
|
execsql/utils/auth.py,sha256=onXzNkNZQZxGC5w7eey06sjvAIAX_Lf9g7nUJtcsel0,7009
|
|
@@ -90,24 +91,24 @@ execsql/utils/numeric.py,sha256=xh02ANSRk3nUpQ-rtm66ILoMqoi7HtzCoRMIOT9U8QI,1570
|
|
|
90
91
|
execsql/utils/regex.py,sha256=diEzTZqU_HHwVMadPAvN1Vgzhl7I03eVaEFGCXyGGL8,3770
|
|
91
92
|
execsql/utils/strings.py,sha256=5Dvzrk-9SIw2lpxXZQkiJbNyo1sy7iXXAtSULlZ0KG8,8488
|
|
92
93
|
execsql/utils/timer.py,sha256=eDYf5VzCNFk7oo90InJucUm3XcBdhYMogjZMqeg9xzc,1899
|
|
93
|
-
execsql2-2.
|
|
94
|
-
execsql2-2.
|
|
95
|
-
execsql2-2.
|
|
96
|
-
execsql2-2.
|
|
97
|
-
execsql2-2.
|
|
98
|
-
execsql2-2.
|
|
99
|
-
execsql2-2.
|
|
100
|
-
execsql2-2.
|
|
101
|
-
execsql2-2.
|
|
102
|
-
execsql2-2.
|
|
103
|
-
execsql2-2.
|
|
104
|
-
execsql2-2.
|
|
105
|
-
execsql2-2.
|
|
106
|
-
execsql2-2.
|
|
107
|
-
execsql2-2.
|
|
108
|
-
execsql2-2.
|
|
109
|
-
execsql2-2.
|
|
110
|
-
execsql2-2.
|
|
111
|
-
execsql2-2.
|
|
112
|
-
execsql2-2.
|
|
113
|
-
execsql2-2.
|
|
94
|
+
execsql2-2.10.1.data/data/execsql2_extras/README.md,sha256=sxwVyU0ZahCfANv56LahkyuM505kFjrMhe-1SvWE69E,4845
|
|
95
|
+
execsql2-2.10.1.data/data/execsql2_extras/config_settings.sqlite,sha256=aY5cxR7Q7J6zJ4bC9lu5mHUrhy211Cq3MNKPQVCt02E,20480
|
|
96
|
+
execsql2-2.10.1.data/data/execsql2_extras/example_config_prompt.sql,sha256=SY3Jxn1qcVm4kPW9xmmTfknHfvURXmeEYTbRjYrjGSw,7487
|
|
97
|
+
execsql2-2.10.1.data/data/execsql2_extras/execsql.conf,sha256=_45iJ-KWZnB8uMW_gEg067MM5pmGJ-dVl7VbAZMunAE,9530
|
|
98
|
+
execsql2-2.10.1.data/data/execsql2_extras/make_config_db.sql,sha256=WwyC6dK-Eh5CAVppiBCDHqiI1_wEI9U95Ytpr4lsZkg,8726
|
|
99
|
+
execsql2-2.10.1.data/data/execsql2_extras/md_compare.sql,sha256=B8Wd7LZ0vnMY2qvA139JIEBkPObgRH2i5xj6PejTQt8,24092
|
|
100
|
+
execsql2-2.10.1.data/data/execsql2_extras/md_glossary.sql,sha256=DJRHcU5NbFpxTTX-IwH3yRlsboj1q6BBGrUAHKn4Cuo,10796
|
|
101
|
+
execsql2-2.10.1.data/data/execsql2_extras/md_upsert.sql,sha256=v_7GbWh_N1mBTmw3gvTrkagOVp2q0KmXvM8hE-DlFxY,112524
|
|
102
|
+
execsql2-2.10.1.data/data/execsql2_extras/pg_compare.sql,sha256=9dWa8hnfy5dVJI-z2iGpd9JzQmI4j2ziMlEdpnr66ro,24352
|
|
103
|
+
execsql2-2.10.1.data/data/execsql2_extras/pg_glossary.sql,sha256=pKjIIDsROAgJq2H-1qNEcRMAWManivcZ_AEVHzUUlic,9908
|
|
104
|
+
execsql2-2.10.1.data/data/execsql2_extras/pg_upsert.sql,sha256=k7AFiGTLBy3nf-qO5QIaZrEYTAKvdxxU3JDLx9jqkzs,108315
|
|
105
|
+
execsql2-2.10.1.data/data/execsql2_extras/script_template.sql,sha256=1Estacb_vm1FgK41k_G9nuduP1yiA-fQ1Kn4Z4mv5Ao,11153
|
|
106
|
+
execsql2-2.10.1.data/data/execsql2_extras/ss_compare.sql,sha256=TsVxWm3cEpR5-EiMYXNhtaY0arSNeKZhsJdHdLA7xeI,24833
|
|
107
|
+
execsql2-2.10.1.data/data/execsql2_extras/ss_glossary.sql,sha256=cLM7nN8JOIu9ZVP9oY9qdSK3hrnWJiDcX6nZmQQbQWI,13065
|
|
108
|
+
execsql2-2.10.1.data/data/execsql2_extras/ss_upsert.sql,sha256=BCqmBykXBF-BpCgOFeG1qhf2XfScKsxPD17wd1hYfHw,120647
|
|
109
|
+
execsql2-2.10.1.dist-info/METADATA,sha256=uRqE2iIiqGuy00GvroEk5nPDMg8PXCozmOtfi1DJr4Y,16956
|
|
110
|
+
execsql2-2.10.1.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
111
|
+
execsql2-2.10.1.dist-info/entry_points.txt,sha256=sUOxkM-dN1eBGGpSpDLsAaE0yNXYQKWZAfxPOlMkQyk,90
|
|
112
|
+
execsql2-2.10.1.dist-info/licenses/LICENSE.txt,sha256=LBdhuxejF8_bLCHZ2kWfmDXpDGUu914Gbd6_3JjCRe0,676
|
|
113
|
+
execsql2-2.10.1.dist-info/licenses/NOTICE,sha256=sqVrM73Ys9zfvWC_P797lHfTnoPW_ETeBSrUTFaob0A,339
|
|
114
|
+
execsql2-2.10.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|