execsql2 2.7.1__py3-none-any.whl → 2.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.
- execsql/cli/__init__.py +6 -0
- execsql/cli/run.py +95 -7
- execsql/metacommands/__init__.py +2 -0
- execsql/metacommands/control.py +33 -0
- execsql/metacommands/dispatch.py +31 -0
- execsql/script/engine.py +16 -0
- execsql/state.py +10 -0
- {execsql2-2.7.1.dist-info → execsql2-2.8.0.dist-info}/METADATA +2 -1
- {execsql2-2.7.1.dist-info → execsql2-2.8.0.dist-info}/RECORD +28 -28
- {execsql2-2.7.1.data → execsql2-2.8.0.data}/data/execsql2_extras/README.md +0 -0
- {execsql2-2.7.1.data → execsql2-2.8.0.data}/data/execsql2_extras/config_settings.sqlite +0 -0
- {execsql2-2.7.1.data → execsql2-2.8.0.data}/data/execsql2_extras/example_config_prompt.sql +0 -0
- {execsql2-2.7.1.data → execsql2-2.8.0.data}/data/execsql2_extras/execsql.conf +0 -0
- {execsql2-2.7.1.data → execsql2-2.8.0.data}/data/execsql2_extras/make_config_db.sql +0 -0
- {execsql2-2.7.1.data → execsql2-2.8.0.data}/data/execsql2_extras/md_compare.sql +0 -0
- {execsql2-2.7.1.data → execsql2-2.8.0.data}/data/execsql2_extras/md_glossary.sql +0 -0
- {execsql2-2.7.1.data → execsql2-2.8.0.data}/data/execsql2_extras/md_upsert.sql +0 -0
- {execsql2-2.7.1.data → execsql2-2.8.0.data}/data/execsql2_extras/pg_compare.sql +0 -0
- {execsql2-2.7.1.data → execsql2-2.8.0.data}/data/execsql2_extras/pg_glossary.sql +0 -0
- {execsql2-2.7.1.data → execsql2-2.8.0.data}/data/execsql2_extras/pg_upsert.sql +0 -0
- {execsql2-2.7.1.data → execsql2-2.8.0.data}/data/execsql2_extras/script_template.sql +0 -0
- {execsql2-2.7.1.data → execsql2-2.8.0.data}/data/execsql2_extras/ss_compare.sql +0 -0
- {execsql2-2.7.1.data → execsql2-2.8.0.data}/data/execsql2_extras/ss_glossary.sql +0 -0
- {execsql2-2.7.1.data → execsql2-2.8.0.data}/data/execsql2_extras/ss_upsert.sql +0 -0
- {execsql2-2.7.1.dist-info → execsql2-2.8.0.dist-info}/WHEEL +0 -0
- {execsql2-2.7.1.dist-info → execsql2-2.8.0.dist-info}/entry_points.txt +0 -0
- {execsql2-2.7.1.dist-info → execsql2-2.8.0.dist-info}/licenses/LICENSE.txt +0 -0
- {execsql2-2.7.1.dist-info → execsql2-2.8.0.dist-info}/licenses/NOTICE +0 -0
execsql/cli/__init__.py
CHANGED
|
@@ -254,6 +254,11 @@ def main(
|
|
|
254
254
|
"--dump-keywords",
|
|
255
255
|
help="Dump all metacommand keywords as JSON and exit.",
|
|
256
256
|
),
|
|
257
|
+
profile: bool = typer.Option(
|
|
258
|
+
False,
|
|
259
|
+
"--profile",
|
|
260
|
+
help="Record per-statement execution times and print a timing summary after the script completes.",
|
|
261
|
+
),
|
|
257
262
|
version: bool | None = typer.Option(
|
|
258
263
|
None,
|
|
259
264
|
"--version",
|
|
@@ -415,6 +420,7 @@ def main(
|
|
|
415
420
|
dsn=dsn,
|
|
416
421
|
output_dir=output_dir,
|
|
417
422
|
progress=progress,
|
|
423
|
+
profile=profile,
|
|
418
424
|
)
|
|
419
425
|
|
|
420
426
|
|
execsql/cli/run.py
CHANGED
|
@@ -20,11 +20,11 @@ from execsql.cli.dsn import _parse_connection_string
|
|
|
20
20
|
from execsql.cli.help import _console, _err_console
|
|
21
21
|
from execsql.config import ConfigData, StatObj
|
|
22
22
|
from execsql.exceptions import ConfigError, ErrInfo
|
|
23
|
-
from execsql.script import SubVarSet, current_script_line, read_sqlfile, read_sqlstring, runscripts
|
|
23
|
+
from execsql.script import SubVarSet, current_script_line, read_sqlfile, read_sqlstring, runscripts, substitute_vars
|
|
24
24
|
from execsql.utils.fileio import FileWriter, Logger, filewriter_end
|
|
25
25
|
from execsql.utils.gui import gui_connect, gui_console_isrunning, gui_console_off, gui_console_on, gui_console_wait_user
|
|
26
26
|
|
|
27
|
-
__all__ = ["_connect_initial_db", "_print_dry_run", "_run"]
|
|
27
|
+
__all__ = ["_connect_initial_db", "_print_dry_run", "_print_profile", "_run"]
|
|
28
28
|
|
|
29
29
|
|
|
30
30
|
# ---------------------------------------------------------------------------
|
|
@@ -33,7 +33,16 @@ __all__ = ["_connect_initial_db", "_print_dry_run", "_run"]
|
|
|
33
33
|
|
|
34
34
|
|
|
35
35
|
def _print_dry_run(cmdlist: object) -> None:
|
|
36
|
-
"""Print the parsed command list for --dry-run mode.
|
|
36
|
+
"""Print the parsed command list for --dry-run mode.
|
|
37
|
+
|
|
38
|
+
Substitution variables (``$VAR``, ``&ENV``, ``@COUNTER``) that are already
|
|
39
|
+
populated — from environment variables, ``--assign-arg`` values, or config —
|
|
40
|
+
are expanded in the displayed text. System variables that are set at
|
|
41
|
+
execution time (e.g. ``$CURRENT_TIME``, ``$DB_NAME``, ``$TIMER``) will
|
|
42
|
+
appear unexpanded because ``set_system_vars()`` has not yet been called.
|
|
43
|
+
Local ``~``-prefixed script-scope variables are also not expanded (no script
|
|
44
|
+
execution context exists in dry-run mode).
|
|
45
|
+
"""
|
|
37
46
|
if cmdlist is None or not cmdlist.cmdlist:
|
|
38
47
|
_console.print("[yellow]No commands found in script.[/yellow]")
|
|
39
48
|
return
|
|
@@ -43,7 +52,71 @@ def _print_dry_run(cmdlist: object) -> None:
|
|
|
43
52
|
for i, cmd in enumerate(cmdlist.cmdlist, 1):
|
|
44
53
|
ctype = "SQL " if cmd.command_type == "sql" else "METACMD"
|
|
45
54
|
source_info = f"[dim]{cmd.source}:{cmd.line_no}[/dim]"
|
|
46
|
-
|
|
55
|
+
raw = cmd.commandline()
|
|
56
|
+
try:
|
|
57
|
+
expanded = substitute_vars(raw)
|
|
58
|
+
except Exception:
|
|
59
|
+
# Cycle detection or other expansion errors — fall back to raw text.
|
|
60
|
+
expanded = raw
|
|
61
|
+
_console.print(f" [dim]{i:>4}[/dim] [bold green]{ctype}[/bold green] {source_info} {expanded}")
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
# ---------------------------------------------------------------------------
|
|
65
|
+
# Profile report helper
|
|
66
|
+
# ---------------------------------------------------------------------------
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _print_profile(profile_data: list[tuple]) -> None:
|
|
70
|
+
"""Print a per-statement timing summary to stdout.
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
profile_data: List of ``(source, line_no, command_type, elapsed_secs,
|
|
74
|
+
command_text_preview)`` tuples collected during execution.
|
|
75
|
+
"""
|
|
76
|
+
if not profile_data:
|
|
77
|
+
_console.print("[dim]Profile: no statements recorded.[/dim]")
|
|
78
|
+
return
|
|
79
|
+
|
|
80
|
+
total_secs = sum(row[3] for row in profile_data)
|
|
81
|
+
n = len(profile_data)
|
|
82
|
+
|
|
83
|
+
# Sort descending by elapsed time; show top 20 (or all if <= 20).
|
|
84
|
+
sorted_data = sorted(profile_data, key=lambda r: r[3], reverse=True)
|
|
85
|
+
display = sorted_data[:20]
|
|
86
|
+
|
|
87
|
+
_console.print()
|
|
88
|
+
_console.print(f"[bold cyan]Profile:[/bold cyan] {n} statement{'s' if n != 1 else ''} in {total_secs:.3f}s")
|
|
89
|
+
_console.print()
|
|
90
|
+
|
|
91
|
+
header = f" {'Time (s)':<10} {'Pct':<7} {'Source:Line':<20} {'Type':<7} Command"
|
|
92
|
+
sep = f" {'-' * 10} {'-' * 7} {'-' * 20} {'-' * 7} {'-' * 40}"
|
|
93
|
+
_console.print(f"[dim]{header}[/dim]")
|
|
94
|
+
_console.print(f"[dim]{sep}[/dim]")
|
|
95
|
+
|
|
96
|
+
for source, line_no, command_type, elapsed, preview in display:
|
|
97
|
+
pct = (elapsed / total_secs * 100) if total_secs > 0 else 0.0
|
|
98
|
+
source_col = f"{source}:{line_no}"
|
|
99
|
+
if len(source_col) > 20:
|
|
100
|
+
source_col = "..." + source_col[-17:]
|
|
101
|
+
ctype_label = "SQL " if command_type == "sql" else "METACMD"
|
|
102
|
+
preview_short = preview[:50].replace("\n", " ").strip()
|
|
103
|
+
if len(preview) > 50:
|
|
104
|
+
preview_short += "..."
|
|
105
|
+
_console.print(
|
|
106
|
+
f" [yellow]{elapsed:<10.3f}[/yellow] "
|
|
107
|
+
f"[dim]{pct:<6.1f}%[/dim] "
|
|
108
|
+
f"[cyan]{source_col:<20}[/cyan] "
|
|
109
|
+
f"[green]{ctype_label:<7}[/green] "
|
|
110
|
+
f"{preview_short}",
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
if len(sorted_data) > 20:
|
|
114
|
+
omitted = len(sorted_data) - 20
|
|
115
|
+
_console.print(
|
|
116
|
+
f"[dim] ... {omitted} more statement{'s' if omitted != 1 else ''} not shown (top 20 by time)[/dim]",
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
_console.print()
|
|
47
120
|
|
|
48
121
|
|
|
49
122
|
# ---------------------------------------------------------------------------
|
|
@@ -76,6 +149,7 @@ def _run(
|
|
|
76
149
|
dsn: str | None = None,
|
|
77
150
|
output_dir: str | None = None,
|
|
78
151
|
progress: bool = False,
|
|
152
|
+
profile: bool = False,
|
|
79
153
|
) -> None:
|
|
80
154
|
"""Initialise state, connect to the database, load the script, and run it.
|
|
81
155
|
|
|
@@ -378,7 +452,10 @@ def _run(
|
|
|
378
452
|
atexit.register(_state.dbs.closeall)
|
|
379
453
|
_state.dbs.do_rollback = True
|
|
380
454
|
|
|
381
|
-
|
|
455
|
+
if profile:
|
|
456
|
+
_state.profile_data = []
|
|
457
|
+
|
|
458
|
+
_execute_script_direct(conf, profile=profile)
|
|
382
459
|
|
|
383
460
|
|
|
384
461
|
# ---------------------------------------------------------------------------
|
|
@@ -437,8 +514,15 @@ def _execute_script_textual_console(conf: ConfigData) -> None:
|
|
|
437
514
|
_state.exec_log.log_exit_end()
|
|
438
515
|
|
|
439
516
|
|
|
440
|
-
def _execute_script_direct(conf: ConfigData) -> None:
|
|
441
|
-
"""Run runscripts() in the current (main) thread — used when Textual is not active.
|
|
517
|
+
def _execute_script_direct(conf: ConfigData, *, profile: bool = False) -> None:
|
|
518
|
+
"""Run runscripts() in the current (main) thread — used when Textual is not active.
|
|
519
|
+
|
|
520
|
+
Args:
|
|
521
|
+
conf: The active configuration object.
|
|
522
|
+
profile: When ``True``, print a per-statement timing summary after the
|
|
523
|
+
script completes. Timing data must already have been activated on
|
|
524
|
+
``_state.profile_data`` before this function is called.
|
|
525
|
+
"""
|
|
442
526
|
import execsql.state as _state
|
|
443
527
|
import execsql.utils.gui as _gui
|
|
444
528
|
|
|
@@ -463,6 +547,8 @@ def _execute_script_direct(conf: ConfigData) -> None:
|
|
|
463
547
|
if gui_console_isrunning():
|
|
464
548
|
gui_console_off()
|
|
465
549
|
_state.exec_log.log_status_info(f"{_state.cmds_run} commands run")
|
|
550
|
+
if profile and _state.profile_data is not None:
|
|
551
|
+
_print_profile(_state.profile_data)
|
|
466
552
|
sys.exit(exc.code)
|
|
467
553
|
except ConfigError:
|
|
468
554
|
raise
|
|
@@ -489,6 +575,8 @@ def _execute_script_direct(conf: ConfigData) -> None:
|
|
|
489
575
|
if gui_console_isrunning():
|
|
490
576
|
gui_console_off()
|
|
491
577
|
_state.exec_log.log_status_info(f"{_state.cmds_run} commands run")
|
|
578
|
+
if profile and _state.profile_data is not None:
|
|
579
|
+
_print_profile(_state.profile_data)
|
|
492
580
|
_state.exec_log.log_exit_end()
|
|
493
581
|
|
|
494
582
|
|
execsql/metacommands/__init__.py
CHANGED
|
@@ -36,6 +36,7 @@ from execsql.metacommands.connect import (
|
|
|
36
36
|
x_daoflushdelay,
|
|
37
37
|
)
|
|
38
38
|
from execsql.metacommands.control import (
|
|
39
|
+
x_assert,
|
|
39
40
|
x_if,
|
|
40
41
|
x_if_orif,
|
|
41
42
|
x_if_andif,
|
|
@@ -238,6 +239,7 @@ __all__ = [
|
|
|
238
239
|
"x_pg_vacuum",
|
|
239
240
|
"x_daoflushdelay",
|
|
240
241
|
# control handlers
|
|
242
|
+
"x_assert",
|
|
241
243
|
"x_if",
|
|
242
244
|
"x_if_orif",
|
|
243
245
|
"x_if_andif",
|
execsql/metacommands/control.py
CHANGED
|
@@ -34,6 +34,39 @@ from execsql.utils.fileio import EncodedFile, check_dir
|
|
|
34
34
|
from execsql.utils.gui import GUI_HALT, GuiSpec, enable_gui, gui_console_isrunning
|
|
35
35
|
|
|
36
36
|
|
|
37
|
+
def x_assert(**kwargs: Any) -> None:
|
|
38
|
+
"""Evaluate a condition and raise ErrInfo if it is false.
|
|
39
|
+
|
|
40
|
+
Syntax::
|
|
41
|
+
|
|
42
|
+
-- !x! ASSERT <condition> ["message"]
|
|
43
|
+
-- !x! ASSERT <condition> ['message']
|
|
44
|
+
-- !x! ASSERT <condition>
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
**kwargs: Keyword arguments injected by the dispatch table.
|
|
48
|
+
``condtest`` — the condition expression string.
|
|
49
|
+
``message`` — optional user-supplied failure message; may be None.
|
|
50
|
+
|
|
51
|
+
Raises:
|
|
52
|
+
ErrInfo: When the condition evaluates to False (or raises internally
|
|
53
|
+
for an unrecognized condition).
|
|
54
|
+
"""
|
|
55
|
+
condition: str = kwargs["condtest"].strip()
|
|
56
|
+
raw_message: str | None = kwargs.get("message")
|
|
57
|
+
if raw_message:
|
|
58
|
+
# Strip surrounding quotes that the regex captured
|
|
59
|
+
message: str = raw_message.strip("'\"")
|
|
60
|
+
else:
|
|
61
|
+
message = f"Assertion failed: {condition}"
|
|
62
|
+
|
|
63
|
+
result = _state.xcmd_test(condition)
|
|
64
|
+
if result:
|
|
65
|
+
_state.exec_log.log_user_msg(f"ASSERT passed: {condition}")
|
|
66
|
+
else:
|
|
67
|
+
raise ErrInfo(type="cmd", other_msg=message)
|
|
68
|
+
|
|
69
|
+
|
|
37
70
|
def x_if(**kwargs: Any) -> None:
|
|
38
71
|
tf_value = _state.xcmd_test(kwargs["condtest"])
|
|
39
72
|
if tf_value:
|
execsql/metacommands/dispatch.py
CHANGED
|
@@ -36,6 +36,7 @@ from execsql.metacommands.connect import (
|
|
|
36
36
|
x_use,
|
|
37
37
|
)
|
|
38
38
|
from execsql.metacommands.control import (
|
|
39
|
+
x_assert,
|
|
39
40
|
x_begin_batch,
|
|
40
41
|
x_break,
|
|
41
42
|
x_end_batch,
|
|
@@ -1659,6 +1660,36 @@ def build_dispatch_table() -> MetaCommandList:
|
|
|
1659
1660
|
category="action",
|
|
1660
1661
|
)
|
|
1661
1662
|
|
|
1663
|
+
# ------------------------------------------------------------------
|
|
1664
|
+
# ASSERT
|
|
1665
|
+
# ------------------------------------------------------------------
|
|
1666
|
+
# Two registrations; MetaCommandList.add() prepends, so register the
|
|
1667
|
+
# broader (no-message) pattern first and the more specific (with-message)
|
|
1668
|
+
# pattern second — the second registration wins because it is prepended
|
|
1669
|
+
# last and therefore tried first during dispatch.
|
|
1670
|
+
#
|
|
1671
|
+
# with-message: the trailing quoted token is captured as `message`;
|
|
1672
|
+
# everything between ASSERT and the message becomes `condtest`.
|
|
1673
|
+
# This handles conditions that themselves contain quoted strings, e.g.:
|
|
1674
|
+
# ASSERT $VAR = 'expected' 'wrong value'
|
|
1675
|
+
# The non-greedy (.+?) stops before the LAST quoted token on the line.
|
|
1676
|
+
#
|
|
1677
|
+
# no-message: full remainder after ASSERT goes into `condtest`.
|
|
1678
|
+
mcl.add(
|
|
1679
|
+
r"^\s*ASSERT\s+(?P<condtest>.+?)\s*$",
|
|
1680
|
+
x_assert,
|
|
1681
|
+
description="ASSERT",
|
|
1682
|
+
category="action",
|
|
1683
|
+
run_when_false=False,
|
|
1684
|
+
)
|
|
1685
|
+
mcl.add(
|
|
1686
|
+
r"^\s*ASSERT\s+(?P<condtest>.+?)\s+(?P<message>(?:\"[^\"]*\"|'[^']*'))\s*$",
|
|
1687
|
+
x_assert,
|
|
1688
|
+
description="ASSERT",
|
|
1689
|
+
category="action",
|
|
1690
|
+
run_when_false=False,
|
|
1691
|
+
)
|
|
1692
|
+
|
|
1662
1693
|
# ------------------------------------------------------------------
|
|
1663
1694
|
# IF / ORIF / ANDIF / ELSEIF / ELSE / ENDIF
|
|
1664
1695
|
# ------------------------------------------------------------------
|
execsql/script/engine.py
CHANGED
|
@@ -489,7 +489,23 @@ class CommandList:
|
|
|
489
489
|
_state.subvars.add_substitution("$CURRENT_SCRIPT_NAME", Path(cmditem.source).name)
|
|
490
490
|
_state.subvars.add_substitution("$CURRENT_SCRIPT_LINE", str(cmditem.line_no))
|
|
491
491
|
_state.subvars.add_substitution("$SCRIPT_LINE", str(cmditem.line_no))
|
|
492
|
+
_profiling = _state.profile_data is not None
|
|
493
|
+
if _profiling:
|
|
494
|
+
import time as _time
|
|
495
|
+
|
|
496
|
+
_t0 = _time.perf_counter()
|
|
492
497
|
cmditem.command.run(self.localvars.merge(self.paramvals), not _state.status.batch.in_batch())
|
|
498
|
+
if _profiling:
|
|
499
|
+
_elapsed = _time.perf_counter() - _t0
|
|
500
|
+
_state.profile_data.append(
|
|
501
|
+
(
|
|
502
|
+
cmditem.source,
|
|
503
|
+
cmditem.line_no,
|
|
504
|
+
cmditem.command_type,
|
|
505
|
+
_elapsed,
|
|
506
|
+
cmditem.command.commandline()[:100],
|
|
507
|
+
),
|
|
508
|
+
)
|
|
493
509
|
self.cmdptr += 1
|
|
494
510
|
|
|
495
511
|
def run_next(self) -> None:
|
execsql/state.py
CHANGED
|
@@ -94,6 +94,8 @@ __all__ = [
|
|
|
94
94
|
"gui_console",
|
|
95
95
|
"gui_manager_queue",
|
|
96
96
|
"gui_manager_thread",
|
|
97
|
+
# Profiling
|
|
98
|
+
"profile_data",
|
|
97
99
|
# Version
|
|
98
100
|
"primary_vno",
|
|
99
101
|
"secondary_vno",
|
|
@@ -191,6 +193,8 @@ _CONTEXT_ATTRS: frozenset[str] = frozenset(
|
|
|
191
193
|
"gui_console",
|
|
192
194
|
"gui_manager_queue",
|
|
193
195
|
"gui_manager_thread",
|
|
196
|
+
# Profiling
|
|
197
|
+
"profile_data",
|
|
194
198
|
},
|
|
195
199
|
)
|
|
196
200
|
|
|
@@ -242,6 +246,8 @@ class RuntimeContext:
|
|
|
242
246
|
"gui_console",
|
|
243
247
|
"gui_manager_queue",
|
|
244
248
|
"gui_manager_thread",
|
|
249
|
+
# Profiling
|
|
250
|
+
"profile_data",
|
|
245
251
|
)
|
|
246
252
|
|
|
247
253
|
def __init__(self) -> None:
|
|
@@ -289,6 +295,10 @@ class RuntimeContext:
|
|
|
289
295
|
self.gui_manager_queue: _mp.Queue | None = None
|
|
290
296
|
self.gui_manager_thread: _threading.Thread | None = None
|
|
291
297
|
|
|
298
|
+
# Profiling — None means profiling is disabled; a list means it is enabled.
|
|
299
|
+
# Each entry: (source, line_no, command_type, elapsed_secs, command_text_preview)
|
|
300
|
+
self.profile_data: list[tuple] | None = None
|
|
301
|
+
|
|
292
302
|
|
|
293
303
|
# ---------------------------------------------------------------------------
|
|
294
304
|
# 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.8.0
|
|
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
|
|
@@ -233,6 +233,7 @@ Run `execsql --help` for the full option list, or `execsql -m` to list all metac
|
|
|
233
233
|
- Export query results in 20+ formats including CSV, TSV, JSON, YAML, XML, HTML, Markdown, LaTeX, XLSX, OpenDocument, Feather, Parquet, HDF5, DuckDB, SQLite, plain text, and Jinja2 templates.
|
|
234
234
|
- Copy data between databases, including across different DBMS types.
|
|
235
235
|
- Conditionally execute SQL and metacommands using `IF`/`ELSE`/`ENDIF` based on data values, DBMS type, or user input.
|
|
236
|
+
- Validate data with `ASSERT` — halt the script with a clear error message if a condition is false (ideal for CI pipelines).
|
|
236
237
|
- Loop over blocks of SQL and metacommands using `LOOP`/`ENDLOOP`.
|
|
237
238
|
- Use substitution variables (`SUB`, `$ARG_x`, built-in variables like `$date_tag`) to parameterize scripts.
|
|
238
239
|
- Include or chain scripts with `INCLUDE` and `SCRIPT`.
|
|
@@ -7,12 +7,12 @@ 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=BodGWiLD7I3s7LFd8Mb6SHMp3I1BhVE4rYcR0UZWAoM,14799
|
|
11
11
|
execsql/types.py,sha256=HVWb4umIB9lpxCGgqk3xy1hoGYPfN39xci5mHF0Izq4,31882
|
|
12
|
-
execsql/cli/__init__.py,sha256=
|
|
12
|
+
execsql/cli/__init__.py,sha256=KewdhCBL8iRa_iPZZ7RKeRl90pGCXg3_lsMaVZph-kY,15118
|
|
13
13
|
execsql/cli/dsn.py,sha256=svaZtrUXFRL2W5G6FRRiKtR6kehOp7urrVhIx_642Z8,2820
|
|
14
14
|
execsql/cli/help.py,sha256=Sn_TgSJiQeBx-xZH0fuP5OvR_wasSTumjWF9UHfIX5k,5414
|
|
15
|
-
execsql/cli/run.py,sha256=
|
|
15
|
+
execsql/cli/run.py,sha256=pkUIcWtzDSYfoIlM418cZdLix7NI45HKRIBxgZfFtHg,26405
|
|
16
16
|
execsql/db/__init__.py,sha256=jTbuafuKOqYtXFR1wvCOoKK5Lr3l1uErfaIbIr6UywI,1063
|
|
17
17
|
execsql/db/access.py,sha256=L79gUnAnnM9EJ_f4k42jr7DI0qGcKtLOnJTlBC7uPm0,17879
|
|
18
18
|
execsql/db/base.py,sha256=hfMFj8fXY0T1aXLvWJHqb0aU4EQUDFOc-YrS29HH8U4,30405
|
|
@@ -58,13 +58,13 @@ execsql/importers/csv.py,sha256=Mu848WNzuhVO1ade-WurPyxqGOuVNRO8UwRF3-bav_I,4845
|
|
|
58
58
|
execsql/importers/feather.py,sha256=g2B69d2uv9vmnXcmjFyTVsMP40LYEzFYkhk3gD26mGw,1900
|
|
59
59
|
execsql/importers/ods.py,sha256=MJsdsjropzCvxAA3DDZfAL_AnmZ4yij7DnrjGyDJqHQ,2843
|
|
60
60
|
execsql/importers/xls.py,sha256=e0Zfe47ZiCpA1Ae3XDJ1ko3sCiH3-8U6XLKi6NvD0jQ,3683
|
|
61
|
-
execsql/metacommands/__init__.py,sha256=
|
|
61
|
+
execsql/metacommands/__init__.py,sha256=ejuY2GFHxNh5f_Yp_GOV0EBe2vuUcly0-zBrKiR3qes,11112
|
|
62
62
|
execsql/metacommands/conditions.py,sha256=u-XdeIWj9QMht9hRGhvH0XlB9V09AliAPKDBHRXc02s,24540
|
|
63
63
|
execsql/metacommands/connect.py,sha256=Nsm0D91i3RX-R2rzQQ-Br-gULaI6Uvdn9fqb7DOAVfE,14804
|
|
64
|
-
execsql/metacommands/control.py,sha256=
|
|
64
|
+
execsql/metacommands/control.py,sha256=CBCg0ZKSR-BGejBW5cXwk6aJ9VrYBzCg9C40ofi8qi8,8776
|
|
65
65
|
execsql/metacommands/data.py,sha256=tRQBGTAuW-eJ2tBNWaoZI9OjTyNNyHJISo7gOdL-sm8,11370
|
|
66
66
|
execsql/metacommands/debug.py,sha256=nmfQ2ijUbTQO3drnyV9EzFueGSTfMl-CddP_NlQyI14,8178
|
|
67
|
-
execsql/metacommands/dispatch.py,sha256=
|
|
67
|
+
execsql/metacommands/dispatch.py,sha256=I6HoBKMofRalL1Cmdsnj1jQFZSFXCgntTofFaIZWgWQ,83670
|
|
68
68
|
execsql/metacommands/io.py,sha256=Duh60caM4go9JczbGYNMKKYpcMimwPzF6EQ_tshKxdE,2971
|
|
69
69
|
execsql/metacommands/io_export.py,sha256=7lkCSnPhXy9FVau9_hT1u68NOVdG2DsWmvUh9hM1QWI,18359
|
|
70
70
|
execsql/metacommands/io_fileops.py,sha256=RKqbWPTYiwiqCZYG-lpih0w1JVOY4RBFdWr3BJb_pnY,9669
|
|
@@ -75,7 +75,7 @@ execsql/metacommands/script_ext.py,sha256=TUgAldB2LSJAwZrCvDDi804hQ1d9BDQD2GDqHN
|
|
|
75
75
|
execsql/metacommands/system.py,sha256=sUR5kLL7idTVg8WXIMdd-Kv7nkERIiaeL0beWsz8NyY,7293
|
|
76
76
|
execsql/script/__init__.py,sha256=pIo0EJ7-vg67rSMbOvbri_BOUgLoGoSEUfJgxUN7ZS0,3380
|
|
77
77
|
execsql/script/control.py,sha256=s-1eZdGARM6H1FwZ6VDdO_f50j7bvvRtTHesfUm9tbc,6144
|
|
78
|
-
execsql/script/engine.py,sha256=
|
|
78
|
+
execsql/script/engine.py,sha256=d3iUGF_r4OQAlqKpd8pIuWGAjDlYvzYiKqi-2Ew1-Yo,40213
|
|
79
79
|
execsql/script/variables.py,sha256=MOT9XEHucpuuuHQZM5bklxGMBQcwHzwTBxd0q3aO0XY,11641
|
|
80
80
|
execsql/utils/__init__.py,sha256=0uR6JwVJQRX3vceByNBduCAf5dd5assKjeqJUWvpZoA,278
|
|
81
81
|
execsql/utils/auth.py,sha256=onXzNkNZQZxGC5w7eey06sjvAIAX_Lf9g7nUJtcsel0,7009
|
|
@@ -89,24 +89,24 @@ execsql/utils/numeric.py,sha256=xh02ANSRk3nUpQ-rtm66ILoMqoi7HtzCoRMIOT9U8QI,1570
|
|
|
89
89
|
execsql/utils/regex.py,sha256=diEzTZqU_HHwVMadPAvN1Vgzhl7I03eVaEFGCXyGGL8,3770
|
|
90
90
|
execsql/utils/strings.py,sha256=5Dvzrk-9SIw2lpxXZQkiJbNyo1sy7iXXAtSULlZ0KG8,8488
|
|
91
91
|
execsql/utils/timer.py,sha256=eDYf5VzCNFk7oo90InJucUm3XcBdhYMogjZMqeg9xzc,1899
|
|
92
|
-
execsql2-2.
|
|
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.
|
|
92
|
+
execsql2-2.8.0.data/data/execsql2_extras/README.md,sha256=sxwVyU0ZahCfANv56LahkyuM505kFjrMhe-1SvWE69E,4845
|
|
93
|
+
execsql2-2.8.0.data/data/execsql2_extras/config_settings.sqlite,sha256=aY5cxR7Q7J6zJ4bC9lu5mHUrhy211Cq3MNKPQVCt02E,20480
|
|
94
|
+
execsql2-2.8.0.data/data/execsql2_extras/example_config_prompt.sql,sha256=SY3Jxn1qcVm4kPW9xmmTfknHfvURXmeEYTbRjYrjGSw,7487
|
|
95
|
+
execsql2-2.8.0.data/data/execsql2_extras/execsql.conf,sha256=_45iJ-KWZnB8uMW_gEg067MM5pmGJ-dVl7VbAZMunAE,9530
|
|
96
|
+
execsql2-2.8.0.data/data/execsql2_extras/make_config_db.sql,sha256=WwyC6dK-Eh5CAVppiBCDHqiI1_wEI9U95Ytpr4lsZkg,8726
|
|
97
|
+
execsql2-2.8.0.data/data/execsql2_extras/md_compare.sql,sha256=B8Wd7LZ0vnMY2qvA139JIEBkPObgRH2i5xj6PejTQt8,24092
|
|
98
|
+
execsql2-2.8.0.data/data/execsql2_extras/md_glossary.sql,sha256=DJRHcU5NbFpxTTX-IwH3yRlsboj1q6BBGrUAHKn4Cuo,10796
|
|
99
|
+
execsql2-2.8.0.data/data/execsql2_extras/md_upsert.sql,sha256=v_7GbWh_N1mBTmw3gvTrkagOVp2q0KmXvM8hE-DlFxY,112524
|
|
100
|
+
execsql2-2.8.0.data/data/execsql2_extras/pg_compare.sql,sha256=9dWa8hnfy5dVJI-z2iGpd9JzQmI4j2ziMlEdpnr66ro,24352
|
|
101
|
+
execsql2-2.8.0.data/data/execsql2_extras/pg_glossary.sql,sha256=pKjIIDsROAgJq2H-1qNEcRMAWManivcZ_AEVHzUUlic,9908
|
|
102
|
+
execsql2-2.8.0.data/data/execsql2_extras/pg_upsert.sql,sha256=k7AFiGTLBy3nf-qO5QIaZrEYTAKvdxxU3JDLx9jqkzs,108315
|
|
103
|
+
execsql2-2.8.0.data/data/execsql2_extras/script_template.sql,sha256=1Estacb_vm1FgK41k_G9nuduP1yiA-fQ1Kn4Z4mv5Ao,11153
|
|
104
|
+
execsql2-2.8.0.data/data/execsql2_extras/ss_compare.sql,sha256=TsVxWm3cEpR5-EiMYXNhtaY0arSNeKZhsJdHdLA7xeI,24833
|
|
105
|
+
execsql2-2.8.0.data/data/execsql2_extras/ss_glossary.sql,sha256=cLM7nN8JOIu9ZVP9oY9qdSK3hrnWJiDcX6nZmQQbQWI,13065
|
|
106
|
+
execsql2-2.8.0.data/data/execsql2_extras/ss_upsert.sql,sha256=BCqmBykXBF-BpCgOFeG1qhf2XfScKsxPD17wd1hYfHw,120647
|
|
107
|
+
execsql2-2.8.0.dist-info/METADATA,sha256=fGxvlbidjAgVeGg51ZWXNPcblzQrR3UWbgfF60yGpuA,16849
|
|
108
|
+
execsql2-2.8.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
109
|
+
execsql2-2.8.0.dist-info/entry_points.txt,sha256=sUOxkM-dN1eBGGpSpDLsAaE0yNXYQKWZAfxPOlMkQyk,90
|
|
110
|
+
execsql2-2.8.0.dist-info/licenses/LICENSE.txt,sha256=LBdhuxejF8_bLCHZ2kWfmDXpDGUu914Gbd6_3JjCRe0,676
|
|
111
|
+
execsql2-2.8.0.dist-info/licenses/NOTICE,sha256=sqVrM73Ys9zfvWC_P797lHfTnoPW_ETeBSrUTFaob0A,339
|
|
112
|
+
execsql2-2.8.0.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
|