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.
Files changed (28) hide show
  1. execsql/cli/__init__.py +6 -0
  2. execsql/cli/run.py +95 -7
  3. execsql/metacommands/__init__.py +2 -0
  4. execsql/metacommands/control.py +33 -0
  5. execsql/metacommands/dispatch.py +31 -0
  6. execsql/script/engine.py +16 -0
  7. execsql/state.py +10 -0
  8. {execsql2-2.7.1.dist-info → execsql2-2.8.0.dist-info}/METADATA +2 -1
  9. {execsql2-2.7.1.dist-info → execsql2-2.8.0.dist-info}/RECORD +28 -28
  10. {execsql2-2.7.1.data → execsql2-2.8.0.data}/data/execsql2_extras/README.md +0 -0
  11. {execsql2-2.7.1.data → execsql2-2.8.0.data}/data/execsql2_extras/config_settings.sqlite +0 -0
  12. {execsql2-2.7.1.data → execsql2-2.8.0.data}/data/execsql2_extras/example_config_prompt.sql +0 -0
  13. {execsql2-2.7.1.data → execsql2-2.8.0.data}/data/execsql2_extras/execsql.conf +0 -0
  14. {execsql2-2.7.1.data → execsql2-2.8.0.data}/data/execsql2_extras/make_config_db.sql +0 -0
  15. {execsql2-2.7.1.data → execsql2-2.8.0.data}/data/execsql2_extras/md_compare.sql +0 -0
  16. {execsql2-2.7.1.data → execsql2-2.8.0.data}/data/execsql2_extras/md_glossary.sql +0 -0
  17. {execsql2-2.7.1.data → execsql2-2.8.0.data}/data/execsql2_extras/md_upsert.sql +0 -0
  18. {execsql2-2.7.1.data → execsql2-2.8.0.data}/data/execsql2_extras/pg_compare.sql +0 -0
  19. {execsql2-2.7.1.data → execsql2-2.8.0.data}/data/execsql2_extras/pg_glossary.sql +0 -0
  20. {execsql2-2.7.1.data → execsql2-2.8.0.data}/data/execsql2_extras/pg_upsert.sql +0 -0
  21. {execsql2-2.7.1.data → execsql2-2.8.0.data}/data/execsql2_extras/script_template.sql +0 -0
  22. {execsql2-2.7.1.data → execsql2-2.8.0.data}/data/execsql2_extras/ss_compare.sql +0 -0
  23. {execsql2-2.7.1.data → execsql2-2.8.0.data}/data/execsql2_extras/ss_glossary.sql +0 -0
  24. {execsql2-2.7.1.data → execsql2-2.8.0.data}/data/execsql2_extras/ss_upsert.sql +0 -0
  25. {execsql2-2.7.1.dist-info → execsql2-2.8.0.dist-info}/WHEEL +0 -0
  26. {execsql2-2.7.1.dist-info → execsql2-2.8.0.dist-info}/entry_points.txt +0 -0
  27. {execsql2-2.7.1.dist-info → execsql2-2.8.0.dist-info}/licenses/LICENSE.txt +0 -0
  28. {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
- _console.print(f" [dim]{i:>4}[/dim] [bold green]{ctype}[/bold green] {source_info} {cmd.commandline()}")
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
- _execute_script_direct(conf)
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
 
@@ -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",
@@ -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:
@@ -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.7.1
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=Kg_cMr0DDEjFkEQ02BKO2xxeH7N03aBRj8UjvzQK-C0,14445
10
+ execsql/state.py,sha256=BodGWiLD7I3s7LFd8Mb6SHMp3I1BhVE4rYcR0UZWAoM,14799
11
11
  execsql/types.py,sha256=HVWb4umIB9lpxCGgqk3xy1hoGYPfN39xci5mHF0Izq4,31882
12
- execsql/cli/__init__.py,sha256=s4283f04Z-SS5CdwsJj6t_oHuyL5sQELsUGTh6x45NU,14908
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=seThMuXV0BmpG94lzOteoxlSjez-p-Ht4__5jjsJZAI,22829
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=zYRUuCnU-e-HbPdp_zCgQIriR_YgRqHkais45Eo3FNk,11082
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=FCIWD-ZivHRZDqMS-2k37iR05HKHsv_7UPh5zJAg4I4,7693
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=aZq4QFY3AjqbplCr2iSoD13ayvT34wWns63a15a_JRM,82407
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=5WOuSbQR1vrp_SawylshzLmdHco2oEjqZBSoxRg0Ggo,39638
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.7.1.data/data/execsql2_extras/README.md,sha256=sxwVyU0ZahCfANv56LahkyuM505kFjrMhe-1SvWE69E,4845
93
- execsql2-2.7.1.data/data/execsql2_extras/config_settings.sqlite,sha256=aY5cxR7Q7J6zJ4bC9lu5mHUrhy211Cq3MNKPQVCt02E,20480
94
- execsql2-2.7.1.data/data/execsql2_extras/example_config_prompt.sql,sha256=SY3Jxn1qcVm4kPW9xmmTfknHfvURXmeEYTbRjYrjGSw,7487
95
- execsql2-2.7.1.data/data/execsql2_extras/execsql.conf,sha256=_45iJ-KWZnB8uMW_gEg067MM5pmGJ-dVl7VbAZMunAE,9530
96
- execsql2-2.7.1.data/data/execsql2_extras/make_config_db.sql,sha256=WwyC6dK-Eh5CAVppiBCDHqiI1_wEI9U95Ytpr4lsZkg,8726
97
- execsql2-2.7.1.data/data/execsql2_extras/md_compare.sql,sha256=B8Wd7LZ0vnMY2qvA139JIEBkPObgRH2i5xj6PejTQt8,24092
98
- execsql2-2.7.1.data/data/execsql2_extras/md_glossary.sql,sha256=DJRHcU5NbFpxTTX-IwH3yRlsboj1q6BBGrUAHKn4Cuo,10796
99
- execsql2-2.7.1.data/data/execsql2_extras/md_upsert.sql,sha256=v_7GbWh_N1mBTmw3gvTrkagOVp2q0KmXvM8hE-DlFxY,112524
100
- execsql2-2.7.1.data/data/execsql2_extras/pg_compare.sql,sha256=9dWa8hnfy5dVJI-z2iGpd9JzQmI4j2ziMlEdpnr66ro,24352
101
- execsql2-2.7.1.data/data/execsql2_extras/pg_glossary.sql,sha256=pKjIIDsROAgJq2H-1qNEcRMAWManivcZ_AEVHzUUlic,9908
102
- execsql2-2.7.1.data/data/execsql2_extras/pg_upsert.sql,sha256=k7AFiGTLBy3nf-qO5QIaZrEYTAKvdxxU3JDLx9jqkzs,108315
103
- execsql2-2.7.1.data/data/execsql2_extras/script_template.sql,sha256=1Estacb_vm1FgK41k_G9nuduP1yiA-fQ1Kn4Z4mv5Ao,11153
104
- execsql2-2.7.1.data/data/execsql2_extras/ss_compare.sql,sha256=TsVxWm3cEpR5-EiMYXNhtaY0arSNeKZhsJdHdLA7xeI,24833
105
- execsql2-2.7.1.data/data/execsql2_extras/ss_glossary.sql,sha256=cLM7nN8JOIu9ZVP9oY9qdSK3hrnWJiDcX6nZmQQbQWI,13065
106
- execsql2-2.7.1.data/data/execsql2_extras/ss_upsert.sql,sha256=BCqmBykXBF-BpCgOFeG1qhf2XfScKsxPD17wd1hYfHw,120647
107
- execsql2-2.7.1.dist-info/METADATA,sha256=UEW6Lpayq5sSHyOOrXXz0SvdAw0MzuoEbRQQ1Chr2VI,16722
108
- execsql2-2.7.1.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
109
- execsql2-2.7.1.dist-info/entry_points.txt,sha256=sUOxkM-dN1eBGGpSpDLsAaE0yNXYQKWZAfxPOlMkQyk,90
110
- execsql2-2.7.1.dist-info/licenses/LICENSE.txt,sha256=LBdhuxejF8_bLCHZ2kWfmDXpDGUu914Gbd6_3JjCRe0,676
111
- execsql2-2.7.1.dist-info/licenses/NOTICE,sha256=sqVrM73Ys9zfvWC_P797lHfTnoPW_ETeBSrUTFaob0A,339
112
- execsql2-2.7.1.dist-info/RECORD,,
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,,