execsql2 2.18.1__py3-none-any.whl → 2.19.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/run.py +46 -17
- execsql/data/execsql.conf.template +34 -2
- execsql/db/access.py +0 -6
- execsql/db/base.py +0 -13
- execsql/db/mysql.py +0 -6
- execsql/db/oracle.py +0 -6
- execsql/db/sqlserver.py +0 -6
- execsql/debug/repl.py +117 -35
- execsql/exporters/feather.py +10 -9
- execsql/format.py +23 -4
- execsql/importers/base.py +3 -4
- execsql/importers/xls.py +6 -1
- execsql/metacommands/__init__.py +2 -2
- execsql/metacommands/data.py +1 -0
- execsql/script/executor.py +2 -6
- execsql/script/parser.py +49 -12
- {execsql2-2.18.1.data → execsql2-2.19.0.data}/data/execsql2_extras/example_config_prompt.sql +1 -1
- {execsql2-2.18.1.data → execsql2-2.19.0.data}/data/execsql2_extras/execsql.conf +34 -2
- {execsql2-2.18.1.dist-info → execsql2-2.19.0.dist-info}/METADATA +13 -4
- {execsql2-2.18.1.dist-info → execsql2-2.19.0.dist-info}/RECORD +37 -37
- {execsql2-2.18.1.dist-info → execsql2-2.19.0.dist-info}/WHEEL +1 -1
- {execsql2-2.18.1.data → execsql2-2.19.0.data}/data/execsql2_extras/README.md +0 -0
- {execsql2-2.18.1.data → execsql2-2.19.0.data}/data/execsql2_extras/config_settings.sqlite +0 -0
- {execsql2-2.18.1.data → execsql2-2.19.0.data}/data/execsql2_extras/make_config_db.sql +0 -0
- {execsql2-2.18.1.data → execsql2-2.19.0.data}/data/execsql2_extras/md_compare.sql +0 -0
- {execsql2-2.18.1.data → execsql2-2.19.0.data}/data/execsql2_extras/md_glossary.sql +0 -0
- {execsql2-2.18.1.data → execsql2-2.19.0.data}/data/execsql2_extras/md_upsert.sql +0 -0
- {execsql2-2.18.1.data → execsql2-2.19.0.data}/data/execsql2_extras/pg_compare.sql +0 -0
- {execsql2-2.18.1.data → execsql2-2.19.0.data}/data/execsql2_extras/pg_glossary.sql +0 -0
- {execsql2-2.18.1.data → execsql2-2.19.0.data}/data/execsql2_extras/pg_upsert.sql +0 -0
- {execsql2-2.18.1.data → execsql2-2.19.0.data}/data/execsql2_extras/script_template.sql +0 -0
- {execsql2-2.18.1.data → execsql2-2.19.0.data}/data/execsql2_extras/ss_compare.sql +0 -0
- {execsql2-2.18.1.data → execsql2-2.19.0.data}/data/execsql2_extras/ss_glossary.sql +0 -0
- {execsql2-2.18.1.data → execsql2-2.19.0.data}/data/execsql2_extras/ss_upsert.sql +0 -0
- {execsql2-2.18.1.dist-info → execsql2-2.19.0.dist-info}/entry_points.txt +0 -0
- {execsql2-2.18.1.dist-info → execsql2-2.19.0.dist-info}/licenses/LICENSE.txt +0 -0
- {execsql2-2.18.1.dist-info → execsql2-2.19.0.dist-info}/licenses/NOTICE +0 -0
execsql/cli/run.py
CHANGED
|
@@ -65,12 +65,21 @@ def _print_dry_run(tree: object) -> None:
|
|
|
65
65
|
else:
|
|
66
66
|
ctype = "METACMD"
|
|
67
67
|
raw = "-- !x! " + node.command
|
|
68
|
-
|
|
68
|
+
file_loc = f"{node.span.file}:{node.span.start_line}"
|
|
69
69
|
try:
|
|
70
70
|
expanded = substitute_vars(raw)
|
|
71
71
|
except Exception:
|
|
72
72
|
expanded = raw
|
|
73
|
-
|
|
73
|
+
lines = expanded.splitlines() or [""]
|
|
74
|
+
first, *continuations = lines
|
|
75
|
+
_console.print(
|
|
76
|
+
f" [dim]{i:>4}[/dim] [bold green]{ctype}[/bold green] [dim]{file_loc}[/dim] {first}",
|
|
77
|
+
)
|
|
78
|
+
if continuations:
|
|
79
|
+
prefix_width = 2 + 4 + 2 + 7 + 2 + len(file_loc) + 2
|
|
80
|
+
pad = " " * prefix_width
|
|
81
|
+
for cont in continuations:
|
|
82
|
+
_console.print(f"{pad}{cont}")
|
|
74
83
|
|
|
75
84
|
|
|
76
85
|
# ---------------------------------------------------------------------------
|
|
@@ -411,6 +420,12 @@ def _route_positionals(
|
|
|
411
420
|
ping: bool,
|
|
412
421
|
) -> None:
|
|
413
422
|
"""Apply remaining positional CLI arguments to *conf* as server/db/db_file."""
|
|
423
|
+
# When --ping is set, the script-file positional is ignored (ping has no
|
|
424
|
+
# script). Users who add --ping to an existing invocation often leave the
|
|
425
|
+
# script in place — drop it from positional routing so the connection args
|
|
426
|
+
# behind it are interpreted correctly.
|
|
427
|
+
if ping and positional and Path(positional[0]).is_file() and positional[0].lower().endswith(".sql"):
|
|
428
|
+
positional = positional[1:]
|
|
414
429
|
off = 0 if (command is not None or ping) else 1
|
|
415
430
|
if len(positional) == off + 1:
|
|
416
431
|
if conf.db_type in ("a", "l", "k"):
|
|
@@ -648,6 +663,33 @@ def _run(
|
|
|
648
663
|
|
|
649
664
|
_state.output = WriteHooks()
|
|
650
665
|
|
|
666
|
+
# ------------------------------------------------------------------
|
|
667
|
+
# Early exits — modes that touch nothing besides parsing/connecting.
|
|
668
|
+
# Keep these before FileWriter / log / atexit so --dry-run and --ping
|
|
669
|
+
# honor their "no side effects" contracts.
|
|
670
|
+
# ------------------------------------------------------------------
|
|
671
|
+
if dry_run:
|
|
672
|
+
# Seed -a assign-args so substitute_vars in the dry-run print can
|
|
673
|
+
# expand them; the normal path does this inside _setup_logging,
|
|
674
|
+
# which we are skipping.
|
|
675
|
+
if sub_vars:
|
|
676
|
+
for n, repl in enumerate(sub_vars):
|
|
677
|
+
_state.subvars.add_substitution(f"$ARG_{n + 1}", repl)
|
|
678
|
+
_ast_tree = _load_script(command, script_name, conf.script_encoding)
|
|
679
|
+
_print_dry_run(_ast_tree)
|
|
680
|
+
raise SystemExit(0)
|
|
681
|
+
|
|
682
|
+
if ping:
|
|
683
|
+
if conf.server is None and conf.db is None and conf.db_file is None:
|
|
684
|
+
from execsql.utils.errors import fatal_error
|
|
685
|
+
|
|
686
|
+
fatal_error(
|
|
687
|
+
"Database not specified for --ping in configuration files or command-line arguments.",
|
|
688
|
+
)
|
|
689
|
+
db = _connect_initial_db(conf)
|
|
690
|
+
_state.dbs.add("initial", db)
|
|
691
|
+
_ping_db(db) # raises SystemExit
|
|
692
|
+
|
|
651
693
|
import execsql.utils.fileio as _fileio
|
|
652
694
|
|
|
653
695
|
if _state.filewriter is None or not _state.filewriter.is_alive():
|
|
@@ -696,16 +738,9 @@ def _run(
|
|
|
696
738
|
)
|
|
697
739
|
|
|
698
740
|
# ------------------------------------------------------------------
|
|
699
|
-
# Load the SQL script (
|
|
741
|
+
# Load the SQL script (--dry-run / --ping already exited above)
|
|
700
742
|
# ------------------------------------------------------------------
|
|
701
|
-
_ast_tree =
|
|
702
|
-
|
|
703
|
-
# ------------------------------------------------------------------
|
|
704
|
-
# Dry-run: print command list and exit without connecting to DB
|
|
705
|
-
# ------------------------------------------------------------------
|
|
706
|
-
if dry_run:
|
|
707
|
-
_print_dry_run(_ast_tree)
|
|
708
|
-
raise SystemExit(0)
|
|
743
|
+
_ast_tree = _load_script(command, script_name, conf.script_encoding)
|
|
709
744
|
|
|
710
745
|
# ------------------------------------------------------------------
|
|
711
746
|
# NOTE: --lint is handled as an early exit in cli/__init__.py (AST
|
|
@@ -741,12 +776,6 @@ def _run(
|
|
|
741
776
|
_state.subvars.add_substitution("$DB_SERVER", db.server_name)
|
|
742
777
|
_state.subvars.add_substitution("$SYSTEM_CMD_EXIT_STATUS", "0")
|
|
743
778
|
|
|
744
|
-
# ------------------------------------------------------------------
|
|
745
|
-
# --ping: report connection details and exit (no script executed)
|
|
746
|
-
# ------------------------------------------------------------------
|
|
747
|
-
if ping:
|
|
748
|
-
_ping_db(db) # raises SystemExit(0) on success
|
|
749
|
-
|
|
750
779
|
# ------------------------------------------------------------------
|
|
751
780
|
# Execute the script
|
|
752
781
|
# ------------------------------------------------------------------
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# execsql.conf — Configuration file for execsql
|
|
2
2
|
#
|
|
3
|
-
# Documentation: https://execsql2.readthedocs.io/reference/configuration/
|
|
3
|
+
# Documentation: https://execsql2.readthedocs.io/en/latest/reference/configuration/
|
|
4
4
|
#
|
|
5
5
|
# This file uses INI format. Section names are case-sensitive (lowercase).
|
|
6
6
|
# Property names are not case-sensitive. Lines starting with # are comments.
|
|
@@ -263,7 +263,7 @@
|
|
|
263
263
|
#message_css=
|
|
264
264
|
|
|
265
265
|
# Obfuscated password (XOR, not cryptographically secure — use keyring instead).
|
|
266
|
-
# See: https://execsql2.readthedocs.io/reference/security/#credentials
|
|
266
|
+
# See: https://execsql2.readthedocs.io/en/latest/reference/security/#credentials
|
|
267
267
|
#enc_password=
|
|
268
268
|
|
|
269
269
|
|
|
@@ -279,6 +279,38 @@
|
|
|
279
279
|
# Values: Yes or No. Default: Yes.
|
|
280
280
|
#allow_system_cmd=Yes
|
|
281
281
|
|
|
282
|
+
# Whether to allow the RM_FILE metacommand (which deletes a file).
|
|
283
|
+
# Set to No to prevent scripts from deleting files.
|
|
284
|
+
# Also controllable via --no-rm-file CLI flag.
|
|
285
|
+
# Values: Yes or No. Default: Yes.
|
|
286
|
+
#allow_rm_file=Yes
|
|
287
|
+
|
|
288
|
+
# Whether to allow the SERVE metacommand (which opens an HTTP server on
|
|
289
|
+
# a local port to serve a single file). Set to No to disable.
|
|
290
|
+
# Also controllable via --no-serve CLI flag.
|
|
291
|
+
# Values: Yes or No. Default: Yes.
|
|
292
|
+
#allow_serve=Yes
|
|
293
|
+
|
|
294
|
+
# Root directory under which INCLUDE / EXECUTE SCRIPT targets must
|
|
295
|
+
# resolve. When set, attempts to include files outside this root via
|
|
296
|
+
# ../, absolute paths, drive letters, or UNC paths are rejected with
|
|
297
|
+
# an error. Default: no containment (any readable path is permitted).
|
|
298
|
+
#include_root=
|
|
299
|
+
|
|
300
|
+
# Root directory under which SERVE targets must resolve. Same
|
|
301
|
+
# containment semantics as include_root. Default: no containment.
|
|
302
|
+
#serve_root=
|
|
303
|
+
|
|
304
|
+
# Root directory under which Jinja2 / string.Template loader paths
|
|
305
|
+
# must resolve. Same containment semantics as include_root.
|
|
306
|
+
# Default: no containment.
|
|
307
|
+
#template_root=
|
|
308
|
+
|
|
309
|
+
# Maximum byte size of any single substitution-variable expansion,
|
|
310
|
+
# enforced by the substitute_vars() engine to defeat exponential-
|
|
311
|
+
# expansion bombs. Default: 10 MB (10485760).
|
|
312
|
+
#max_substitution_bytes=10485760
|
|
313
|
+
|
|
282
314
|
# Whether to log all data variable assignments.
|
|
283
315
|
# Values: Yes or No. Default: Yes.
|
|
284
316
|
#log_datavars=Yes
|
execsql/db/access.py
CHANGED
|
@@ -88,12 +88,6 @@ class AccessDatabase(Database):
|
|
|
88
88
|
def __repr__(self) -> str:
|
|
89
89
|
return f"AccessDatabase({self.db_name}, {self.encoding})"
|
|
90
90
|
|
|
91
|
-
def auto_commits_ddl(self) -> bool:
|
|
92
|
-
"""MS Access (via Jet/ACE on pyodbc) implicitly commits DDL —
|
|
93
|
-
``rollback()`` is a silent no-op for any transaction whose
|
|
94
|
-
boundary the DDL crossed."""
|
|
95
|
-
return True
|
|
96
|
-
|
|
97
91
|
def open_db(self) -> None:
|
|
98
92
|
"""Open an ODBC connection to the Access database."""
|
|
99
93
|
# Open an ODBC connection.
|
execsql/db/base.py
CHANGED
|
@@ -253,19 +253,6 @@ class Database(ABC):
|
|
|
253
253
|
"""
|
|
254
254
|
return False
|
|
255
255
|
|
|
256
|
-
def auto_commits_ddl(self) -> bool:
|
|
257
|
-
"""Return True if this adapter's driver implicitly commits DDL.
|
|
258
|
-
|
|
259
|
-
Oracle, MySQL, SQL Server, and MS Access all auto-commit DDL —
|
|
260
|
-
``rollback()`` is a silent no-op for any transaction whose
|
|
261
|
-
boundary the DDL crossed. Callers that wrap DDL inside an
|
|
262
|
-
explicit ``BEGIN BATCH … END BATCH`` block on these adapters
|
|
263
|
-
get weaker rollback guarantees than on PostgreSQL / SQLite, and
|
|
264
|
-
should be aware of the asymmetry. See
|
|
265
|
-
``docs/about/divergence.md`` for the full per-DBMS matrix.
|
|
266
|
-
"""
|
|
267
|
-
return False
|
|
268
|
-
|
|
269
256
|
def schema_qualified_table_name(self, schema_name: str | None, table_name: str) -> str:
|
|
270
257
|
"""Return the quoted, optionally schema-qualified form of *table_name*."""
|
|
271
258
|
table_name = self.type.quoted(table_name)
|
execsql/db/mysql.py
CHANGED
|
@@ -73,12 +73,6 @@ class MySQLDatabase(Database):
|
|
|
73
73
|
f"{self.need_passwd!r}, {self.port!r}, {self.encoding!r})"
|
|
74
74
|
)
|
|
75
75
|
|
|
76
|
-
def auto_commits_ddl(self) -> bool:
|
|
77
|
-
"""MySQL / MariaDB implicitly commit DDL — ``rollback()`` is a
|
|
78
|
-
silent no-op for any transaction whose boundary the DDL
|
|
79
|
-
crossed. See ``docs/about/divergence.md``."""
|
|
80
|
-
return True
|
|
81
|
-
|
|
82
76
|
def quote_identifier(self, identifier: str) -> str:
|
|
83
77
|
"""MySQL / MariaDB native identifier quoting uses backticks.
|
|
84
78
|
|
execsql/db/oracle.py
CHANGED
|
@@ -61,12 +61,6 @@ class OracleDatabase(Database):
|
|
|
61
61
|
f"{self.need_passwd!r}, {self.port!r}, {self.encoding!r})"
|
|
62
62
|
)
|
|
63
63
|
|
|
64
|
-
def auto_commits_ddl(self) -> bool:
|
|
65
|
-
"""Oracle implicitly commits DDL — ``rollback()`` is a silent
|
|
66
|
-
no-op for any transaction whose boundary the DDL crossed.
|
|
67
|
-
See ``docs/about/divergence.md`` for the per-DBMS matrix."""
|
|
68
|
-
return True
|
|
69
|
-
|
|
70
64
|
def open_db(self) -> None:
|
|
71
65
|
"""Open a connection to the Oracle database."""
|
|
72
66
|
import cx_Oracle
|
execsql/db/sqlserver.py
CHANGED
|
@@ -58,12 +58,6 @@ class SqlServerDatabase(Database):
|
|
|
58
58
|
f"{self.need_passwd!r}, {self.port!r}, {self.encoding!r})"
|
|
59
59
|
)
|
|
60
60
|
|
|
61
|
-
def auto_commits_ddl(self) -> bool:
|
|
62
|
-
"""SQL Server implicitly commits DDL on Microsoft's pyodbc
|
|
63
|
-
driver in autocommit mode — ``rollback()`` is a silent no-op
|
|
64
|
-
for any transaction whose boundary the DDL crossed."""
|
|
65
|
-
return True
|
|
66
|
-
|
|
67
61
|
def quote_identifier(self, identifier: str) -> str:
|
|
68
62
|
"""SQL Server native identifier quoting uses square brackets.
|
|
69
63
|
|
execsql/debug/repl.py
CHANGED
|
@@ -12,10 +12,26 @@ The REPL allows the user to:
|
|
|
12
12
|
- Step through the script one statement at a time.
|
|
13
13
|
- Resume or abort execution.
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
15
|
+
Dispatch is two-way (psql-style): input starting with ``.`` is a REPL
|
|
16
|
+
command (``.continue``, ``.vars [VAR]``, ``.next``, etc.), everything
|
|
17
|
+
else is SQL. Multi-line SQL is supported: any non-``.`` input opens a
|
|
18
|
+
buffer whose continuation prompt is `` ... > ``, accumulating
|
|
19
|
+
until a line ends with ``;``. ``.cancel`` (or Ctrl-C / EOF) discards
|
|
20
|
+
the partial buffer.
|
|
21
|
+
|
|
22
|
+
Variable lookup is explicit — ``.vars LOGFILE`` prints one variable;
|
|
23
|
+
``.vars`` lists them all. There is no bare-identifier lookup, so any
|
|
24
|
+
SQL keyword you type starts a buffer the moment you press Enter.
|
|
25
|
+
|
|
26
|
+
The trailing ``;`` is the SQL terminator both within one line and across
|
|
27
|
+
multiple lines — the REPL has no read-only mode and DDL on most adapters
|
|
28
|
+
is irreversible, so requiring ``;`` is a small intent gate against
|
|
29
|
+
accidental DML/DDL on mistyped input. Use SQL ``BEGIN; … ROLLBACK;`` to
|
|
30
|
+
bracket exploratory DML if you need recoverability.
|
|
31
|
+
|
|
32
|
+
Errors raised by any REPL helper — bad SQL, malformed dot-commands, etc. —
|
|
33
|
+
are caught at the loop level so the session re-prompts instead of escaping
|
|
34
|
+
through ``x_breakpoint`` and being stamped as a "Metacommand error".
|
|
19
35
|
|
|
20
36
|
In non-interactive environments (CI, piped input, ``sys.stdin.isatty()`` is
|
|
21
37
|
``False``) the metacommand is silently skipped so automated pipelines are not
|
|
@@ -104,21 +120,21 @@ def _c(code: str, text: str) -> str:
|
|
|
104
120
|
|
|
105
121
|
_HELP_COMMANDS = [
|
|
106
122
|
(".continue", ".c", "Resume script execution"),
|
|
107
|
-
(".
|
|
108
|
-
(".vars", ".v", "List
|
|
109
|
-
(".vars
|
|
123
|
+
(".quit", ".q", "Halt the script (exit 1)"),
|
|
124
|
+
(".vars", ".v", "List all execsql substitution variables"),
|
|
125
|
+
(".vars VAR", ".v VAR", "Print the value of a single variable (e.g. .vars logfile)"),
|
|
110
126
|
(".next", ".n", "Execute the next statement then pause again (step mode)"),
|
|
111
127
|
(".where", ".w", "Show the current script location and upcoming statement"),
|
|
112
128
|
(".stack", "", "Show the command-list stack (script name, line, depth)"),
|
|
113
129
|
(".set VAR VAL", ".s", "Set or update a substitution variable"),
|
|
114
130
|
(".scripts", "", "List all registered SCRIPT definitions"),
|
|
115
131
|
(".scripts NAME", "", "Show detail for a specific SCRIPT"),
|
|
132
|
+
(".cancel", "", "Discard a partial multi-line SQL buffer"),
|
|
116
133
|
(".help", ".h", "Show this help text"),
|
|
117
134
|
]
|
|
118
135
|
|
|
119
136
|
_HELP_OTHER = [
|
|
120
|
-
("
|
|
121
|
-
("SELECT ...;", "Run SQL ending with ';' (expects columns returned, e.g. SELECT)"),
|
|
137
|
+
("SELECT ...;", "Run SQL — multi-line accepted, terminate with ';' to execute"),
|
|
122
138
|
]
|
|
123
139
|
|
|
124
140
|
_HELP_CMD_WIDTH = 13 # width of the command column
|
|
@@ -216,40 +232,71 @@ def _debug_repl(*, step: bool = False) -> None:
|
|
|
216
232
|
_hint_c = _c(_DIM, "'.c'")
|
|
217
233
|
_write(f" Type {_hint_help} for commands, {_hint_c} to resume.\n\n")
|
|
218
234
|
|
|
235
|
+
sql_buffer: list[str] = []
|
|
236
|
+
|
|
219
237
|
while True:
|
|
238
|
+
prompt = " ... > " if sql_buffer else "execsql debug> "
|
|
220
239
|
try:
|
|
221
|
-
line = input(
|
|
240
|
+
line = input(prompt).strip()
|
|
222
241
|
except EOFError:
|
|
242
|
+
if sql_buffer:
|
|
243
|
+
sql_buffer.clear()
|
|
244
|
+
_write("\n (input discarded)\n")
|
|
245
|
+
continue
|
|
223
246
|
_write("\n")
|
|
224
|
-
return
|
|
247
|
+
return
|
|
225
248
|
except KeyboardInterrupt:
|
|
249
|
+
if sql_buffer:
|
|
250
|
+
sql_buffer.clear()
|
|
251
|
+
_write("\n (input discarded)\n")
|
|
252
|
+
continue
|
|
226
253
|
_write("\n")
|
|
227
|
-
return
|
|
254
|
+
return
|
|
228
255
|
|
|
229
256
|
if not line:
|
|
230
257
|
continue
|
|
231
258
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
259
|
+
try:
|
|
260
|
+
if line.startswith("."):
|
|
261
|
+
cmd = line[1:].strip().lower()
|
|
262
|
+
if cmd == "cancel":
|
|
263
|
+
if sql_buffer:
|
|
264
|
+
sql_buffer.clear()
|
|
265
|
+
_write(" (input discarded)\n")
|
|
266
|
+
continue
|
|
267
|
+
_handle_dot_command(line)
|
|
268
|
+
if cmd in ("continue", "c"):
|
|
269
|
+
return
|
|
270
|
+
if cmd in ("abort", "q", "quit"):
|
|
271
|
+
return
|
|
272
|
+
if cmd in ("next", "n"):
|
|
273
|
+
return
|
|
274
|
+
continue
|
|
275
|
+
|
|
276
|
+
if sql_buffer:
|
|
277
|
+
sql_buffer.append(line)
|
|
278
|
+
joined = " ".join(sql_buffer)
|
|
279
|
+
if joined.rstrip().endswith(";"):
|
|
280
|
+
_run_sql(joined)
|
|
281
|
+
sql_buffer.clear()
|
|
282
|
+
continue
|
|
283
|
+
|
|
284
|
+
if line.rstrip().endswith(";"):
|
|
285
|
+
_run_sql(line)
|
|
286
|
+
continue
|
|
287
|
+
|
|
288
|
+
sql_buffer.append(line)
|
|
289
|
+
except SystemExit:
|
|
290
|
+
raise
|
|
291
|
+
except KeyboardInterrupt:
|
|
292
|
+
sql_buffer.clear()
|
|
293
|
+
_write("\n (interrupted)\n")
|
|
243
294
|
continue
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
_run_sql(line)
|
|
295
|
+
except Exception as exc:
|
|
296
|
+
sql_buffer.clear()
|
|
297
|
+
_write(f" {_c(_RED, 'Error:')} {exc}\n")
|
|
248
298
|
continue
|
|
249
299
|
|
|
250
|
-
# Everything else → variable lookup
|
|
251
|
-
_print_var(line)
|
|
252
|
-
|
|
253
300
|
|
|
254
301
|
def _handle_dot_command(line: str) -> None:
|
|
255
302
|
"""Dispatch a dot-prefixed REPL command."""
|
|
@@ -262,10 +309,14 @@ def _handle_dot_command(line: str) -> None:
|
|
|
262
309
|
raise SystemExit(1)
|
|
263
310
|
elif cmd in ("help", "h"):
|
|
264
311
|
_write(_format_help())
|
|
265
|
-
elif cmd in ("vars all", "v all"):
|
|
266
|
-
_print_all_vars(include_env=True)
|
|
267
312
|
elif cmd in ("vars", "v"):
|
|
268
313
|
_print_all_vars()
|
|
314
|
+
elif cmd.startswith("vars ") or cmd.startswith("v "):
|
|
315
|
+
rest = cmd.split(None, 1)[1].strip() if " " in cmd else ""
|
|
316
|
+
if rest:
|
|
317
|
+
_print_var(rest)
|
|
318
|
+
else:
|
|
319
|
+
_print_all_vars()
|
|
269
320
|
elif cmd in ("where", "w"):
|
|
270
321
|
_print_where()
|
|
271
322
|
elif cmd == "stack":
|
|
@@ -383,7 +434,11 @@ def _print_all_vars(*, include_env: bool = False) -> None:
|
|
|
383
434
|
_write(f" {_c(_BOLD, label)}:\n")
|
|
384
435
|
max_name = max(len(n) for n, _ in group)
|
|
385
436
|
for name, value in group:
|
|
386
|
-
|
|
437
|
+
# Pre-compute padding from RAW name length — the format-spec ``:<{max_name}``
|
|
438
|
+
# form measures the colored string, which (when ANSI is on) is ~9 chars
|
|
439
|
+
# longer per wrap and breaks the column alignment.
|
|
440
|
+
pad = " " * (max_name - len(name))
|
|
441
|
+
_write(f" {_c(_CYAN, name)}{pad} {_c(_DIM, '=')} {value}\n")
|
|
387
442
|
|
|
388
443
|
_print_group("User", user_vars)
|
|
389
444
|
_print_group("System ($)", system_vars)
|
|
@@ -477,7 +532,7 @@ def _print_stack() -> None:
|
|
|
477
532
|
|
|
478
533
|
|
|
479
534
|
def _run_sql(sql: str) -> None:
|
|
480
|
-
"""Execute ad-hoc SQL
|
|
535
|
+
"""Execute ad-hoc SQL and pretty-print results or affected rowcount."""
|
|
481
536
|
dbs = _state.dbs
|
|
482
537
|
if dbs is None:
|
|
483
538
|
_write(" (no database connection is active)\n")
|
|
@@ -486,8 +541,35 @@ def _run_sql(sql: str) -> None:
|
|
|
486
541
|
if db is None:
|
|
487
542
|
_write(" (no database connection is active)\n")
|
|
488
543
|
return
|
|
544
|
+
|
|
489
545
|
try:
|
|
490
|
-
|
|
546
|
+
with db._cursor() as curs:
|
|
547
|
+
try:
|
|
548
|
+
curs.execute(sql)
|
|
549
|
+
except Exception as exc:
|
|
550
|
+
try:
|
|
551
|
+
db.rollback()
|
|
552
|
+
except Exception:
|
|
553
|
+
pass
|
|
554
|
+
_write(f" {_c(_RED, 'SQL error:')} {exc}\n")
|
|
555
|
+
return
|
|
556
|
+
|
|
557
|
+
try:
|
|
558
|
+
_state.subvars.add_substitution("$LAST_ROWCOUNT", curs.rowcount)
|
|
559
|
+
except Exception:
|
|
560
|
+
pass
|
|
561
|
+
|
|
562
|
+
if curs.description is None:
|
|
563
|
+
rowcount = curs.rowcount if curs.rowcount is not None else -1
|
|
564
|
+
if rowcount >= 0:
|
|
565
|
+
row_word = "row" if rowcount == 1 else "rows"
|
|
566
|
+
_write(f" {_c(_DIM, f'({rowcount} {row_word} affected)')}\n")
|
|
567
|
+
else:
|
|
568
|
+
_write(f" {_c(_DIM, '(statement executed)')}\n")
|
|
569
|
+
return
|
|
570
|
+
|
|
571
|
+
colnames = [d[0] for d in curs.description]
|
|
572
|
+
rows = curs.fetchall()
|
|
491
573
|
except Exception as exc:
|
|
492
574
|
_write(f" {_c(_RED, 'SQL error:')} {exc}\n")
|
|
493
575
|
return
|
execsql/exporters/feather.py
CHANGED
|
@@ -58,20 +58,26 @@ def write_query_to_hdf5(
|
|
|
58
58
|
other_msg="The tables Python library must be installed to export data to the HDF5 format.",
|
|
59
59
|
) from e
|
|
60
60
|
try:
|
|
61
|
-
hdrs,
|
|
61
|
+
hdrs, row_iter = db.select_rowsource(select_stmt)
|
|
62
62
|
except ErrInfo:
|
|
63
63
|
raise
|
|
64
64
|
except Exception as e:
|
|
65
65
|
raise ErrInfo("db", select_stmt, exception_msg=exception_desc()) from e
|
|
66
|
+
# Materialize once so DataTable's type inference (below) and the write
|
|
67
|
+
# loop (further below) share the same rows without a second query.
|
|
68
|
+
rows = list(row_iter)
|
|
66
69
|
|
|
67
70
|
def h5type(datatype, size):
|
|
68
|
-
if datatype
|
|
71
|
+
if datatype == _state.DT_Varchar:
|
|
69
72
|
t = tables.StringCol(size)
|
|
70
73
|
do_cast = False
|
|
71
74
|
elif datatype == _state.DT_Text:
|
|
72
75
|
t = tables.StringCol(_state.conf.hdf5_text_len)
|
|
73
76
|
do_cast = False
|
|
74
|
-
elif datatype
|
|
77
|
+
elif datatype == _state.DT_Long:
|
|
78
|
+
t = tables.Int64Col()
|
|
79
|
+
do_cast = False
|
|
80
|
+
elif datatype == _state.DT_Integer:
|
|
75
81
|
t = tables.IntCol()
|
|
76
82
|
do_cast = False
|
|
77
83
|
elif datatype in (_state.DT_Float, _state.DT_Decimal):
|
|
@@ -87,25 +93,20 @@ def write_query_to_hdf5(
|
|
|
87
93
|
raise ErrInfo("error", other_msg=f"Invalid data type for export to HDF5: {repr(datatype)}")
|
|
88
94
|
return t, do_cast
|
|
89
95
|
|
|
90
|
-
# Create a dictionary of column names with the HDF5 data types
|
|
91
96
|
tbl_desc = DataTable(hdrs, rows)
|
|
92
97
|
h5type_dict = {}
|
|
93
98
|
cast_flags = []
|
|
94
|
-
# Iterate over hdrs instead of tbl_desc.cols to preserve column order.
|
|
95
99
|
for h in hdrs:
|
|
96
100
|
dt = [col for col in tbl_desc.cols if col.name == h][0].dt
|
|
97
|
-
# dt is
|
|
101
|
+
# dt is (name, data-type-class, max-length-or-None, ...)
|
|
98
102
|
h5typ, as_str = h5type(dt[1], dt[2])
|
|
99
103
|
h5type_dict[h] = h5typ
|
|
100
104
|
cast_flags.append(as_str)
|
|
101
|
-
# Open the HDF5 table
|
|
102
105
|
filewriter_close(outfile)
|
|
103
106
|
h5file_mode = "a" if append else "w"
|
|
104
107
|
h5file = tables.open_file(outfile, mode=h5file_mode)
|
|
105
108
|
h5grp = h5file.create_group("/", table_name, title=desc)
|
|
106
109
|
h5tbl = h5file.create_table(h5grp, table_name, h5type_dict)
|
|
107
|
-
# Write the data.
|
|
108
|
-
hdrs, rows = db.select_rowsource(select_stmt)
|
|
109
110
|
for datarow in rows:
|
|
110
111
|
h5row = h5tbl.row
|
|
111
112
|
for i, h in enumerate(hdrs):
|
execsql/format.py
CHANGED
|
@@ -16,11 +16,27 @@ import io
|
|
|
16
16
|
import re
|
|
17
17
|
from pathlib import Path
|
|
18
18
|
|
|
19
|
-
import sqlglot
|
|
20
|
-
import sqlglot.errors
|
|
21
|
-
|
|
22
19
|
__all__ = ["collect_paths", "format_file", "main", "parse_keyword"]
|
|
23
20
|
|
|
21
|
+
|
|
22
|
+
_SQLGLOT_MISSING_MSG = (
|
|
23
|
+
"execsql-format requires sqlglot for SQL reformatting.\n"
|
|
24
|
+
" Install with: pip install execsql2[formatter]\n"
|
|
25
|
+
" Or skip SQL reformatting with the --no-sql flag."
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _require_sqlglot():
|
|
30
|
+
"""Lazy import of sqlglot, raising ImportError with an install hint if missing."""
|
|
31
|
+
try:
|
|
32
|
+
import sqlglot
|
|
33
|
+
import sqlglot.errors # noqa: F401
|
|
34
|
+
|
|
35
|
+
return sqlglot
|
|
36
|
+
except ImportError as e:
|
|
37
|
+
raise ImportError(_SQLGLOT_MISSING_MSG) from e
|
|
38
|
+
|
|
39
|
+
|
|
24
40
|
# ---------------------------------------------------------------------------
|
|
25
41
|
# Constants
|
|
26
42
|
# ---------------------------------------------------------------------------
|
|
@@ -161,6 +177,9 @@ def _sqlglot_format(
|
|
|
161
177
|
leading_comma: bool = False,
|
|
162
178
|
) -> list[str]:
|
|
163
179
|
"""Format a list of SQL-only lines (no comment-only lines) via sqlglot."""
|
|
180
|
+
sqlglot = _require_sqlglot()
|
|
181
|
+
import sqlglot.errors as sqlglot_errors
|
|
182
|
+
|
|
164
183
|
text = "\n".join(sql_lines)
|
|
165
184
|
protected, replacements = _protect_variables(text)
|
|
166
185
|
|
|
@@ -169,7 +188,7 @@ def _sqlglot_format(
|
|
|
169
188
|
|
|
170
189
|
try:
|
|
171
190
|
with contextlib.redirect_stderr(io.StringIO()):
|
|
172
|
-
ast = sqlglot.parse(protected, read="postgres", error_level=
|
|
191
|
+
ast = sqlglot.parse(protected, read="postgres", error_level=sqlglot_errors.ErrorLevel.IGNORE)
|
|
173
192
|
statements: list[str] = []
|
|
174
193
|
for node in ast:
|
|
175
194
|
if node is None:
|
execsql/importers/base.py
CHANGED
|
@@ -15,7 +15,6 @@ from typing import Any
|
|
|
15
15
|
from execsql.exceptions import ErrInfo
|
|
16
16
|
from execsql.db.base import Database
|
|
17
17
|
import execsql.state as _state
|
|
18
|
-
from execsql.types import dbt_firebird
|
|
19
18
|
|
|
20
19
|
__all__ = ["import_data_table"]
|
|
21
20
|
|
|
@@ -84,9 +83,9 @@ def import_data_table(
|
|
|
84
83
|
sql = get_ts().create_table(db.type, schemaname, tablename)
|
|
85
84
|
try:
|
|
86
85
|
db.execute(sql)
|
|
87
|
-
#
|
|
88
|
-
#
|
|
89
|
-
if db.
|
|
86
|
+
# Most adapters delay commit until after populating the table; adapters
|
|
87
|
+
# that need DDL committed first (Firebird) must do it here.
|
|
88
|
+
if db.needs_explicit_commit_after_ddl():
|
|
90
89
|
db.conn.commit()
|
|
91
90
|
except Exception as e:
|
|
92
91
|
raise ErrInfo(
|
execsql/importers/xls.py
CHANGED
|
@@ -39,7 +39,12 @@ def xls_data(
|
|
|
39
39
|
|
|
40
40
|
wbk = XlsFile()
|
|
41
41
|
elif ext3 == "lsx":
|
|
42
|
-
|
|
42
|
+
from execsql.utils.fileio import check_zip_decompression_ratio
|
|
43
|
+
|
|
44
|
+
try:
|
|
45
|
+
check_zip_decompression_ratio(filename)
|
|
46
|
+
except FileNotFoundError:
|
|
47
|
+
pass
|
|
43
48
|
from execsql.exporters.xls import XlsxFile
|
|
44
49
|
|
|
45
50
|
wbk = XlsxFile()
|
execsql/metacommands/__init__.py
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
Importing this module populates a ``MetaCommandList`` (``DISPATCH_TABLE``)
|
|
4
4
|
with every metacommand regex and its handler function. The dispatch
|
|
5
|
-
table is consumed by
|
|
6
|
-
``_state.metacommandlist``.
|
|
5
|
+
table is consumed by :func:`execsql.script.executor._exec_metacommand`
|
|
6
|
+
via ``_state.metacommandlist``.
|
|
7
7
|
|
|
8
8
|
The table itself is built by ``build_dispatch_table()`` in
|
|
9
9
|
:mod:`execsql.metacommands.dispatch`. Handler functions are organized
|
execsql/metacommands/data.py
CHANGED
execsql/script/executor.py
CHANGED
|
@@ -83,10 +83,6 @@ _DEFER_RX = re.compile(r"!\{([$@&~#+]?\w+)\}!")
|
|
|
83
83
|
_VARLIKE = re.compile(r"!![$@&~#]?\w+!!", re.I)
|
|
84
84
|
|
|
85
85
|
|
|
86
|
-
# Legacy module-level alias — ``_ast_scripts`` is now ``ctx.ast_scripts``
|
|
87
|
-
# on the RuntimeContext. Kept as a comment for grep-ability.
|
|
88
|
-
|
|
89
|
-
|
|
90
86
|
def _stack_localvars(ctx: RuntimeContext) -> SubVarSet | None:
|
|
91
87
|
"""Build the merged ``~`` local + ``#`` param overlay for the current scope.
|
|
92
88
|
|
|
@@ -210,7 +206,7 @@ def _set_command_vars(ctx: RuntimeContext, source: str, line_no: int) -> None:
|
|
|
210
206
|
|
|
211
207
|
|
|
212
208
|
# ---------------------------------------------------------------------------
|
|
213
|
-
# SQL execution
|
|
209
|
+
# SQL execution
|
|
214
210
|
# ---------------------------------------------------------------------------
|
|
215
211
|
|
|
216
212
|
|
|
@@ -264,7 +260,7 @@ def _exec_sql(
|
|
|
264
260
|
|
|
265
261
|
|
|
266
262
|
# ---------------------------------------------------------------------------
|
|
267
|
-
# Metacommand execution
|
|
263
|
+
# Metacommand execution
|
|
268
264
|
# ---------------------------------------------------------------------------
|
|
269
265
|
|
|
270
266
|
|
execsql/script/parser.py
CHANGED
|
@@ -102,6 +102,19 @@ def _strip_quotes(s: str) -> str:
|
|
|
102
102
|
return s
|
|
103
103
|
|
|
104
104
|
|
|
105
|
+
def _unclosed_block_msg(top: _BlockFrame) -> str:
|
|
106
|
+
"""Message blaming an unclosed opening block for a wrong-kind END-keyword error."""
|
|
107
|
+
if top.kind == "if":
|
|
108
|
+
return f"IF on line {top.start_line} of {top.source} has no matching ENDIF."
|
|
109
|
+
if top.kind == "loop":
|
|
110
|
+
return f"LOOP on line {top.start_line} of {top.source} has no matching ENDLOOP."
|
|
111
|
+
if top.kind == "batch":
|
|
112
|
+
return f"BEGIN BATCH on line {top.start_line} of {top.source} has no matching END BATCH."
|
|
113
|
+
if top.kind == "script":
|
|
114
|
+
return f"BEGIN SCRIPT on line {top.start_line} of {top.source} has no matching END SCRIPT."
|
|
115
|
+
return f"{top.kind.upper()} on line {top.start_line} of {top.source} is unclosed."
|
|
116
|
+
|
|
117
|
+
|
|
105
118
|
_EXEC_SCRIPT_RX = re.compile(
|
|
106
119
|
r"^\s*(?:EXEC(?:UTE)?|RUN)\s+SCRIPT"
|
|
107
120
|
r"(?P<exists>\s+IF\s+EXISTS)?"
|
|
@@ -529,11 +542,17 @@ def _parse_lines(lines: Iterable[str], source_name: str) -> Script:
|
|
|
529
542
|
end_name = m.group("name")
|
|
530
543
|
if end_name is not None:
|
|
531
544
|
end_name = end_name.lower()
|
|
532
|
-
if not block_stack
|
|
545
|
+
if not block_stack:
|
|
546
|
+
raise ErrInfo(
|
|
547
|
+
type="cmd",
|
|
548
|
+
command_text=line,
|
|
549
|
+
other_msg=f"END SCRIPT on line {file_lineno} of {source_name} has no matching BEGIN SCRIPT.",
|
|
550
|
+
)
|
|
551
|
+
if block_stack[-1].kind != "script":
|
|
533
552
|
raise ErrInfo(
|
|
534
553
|
type="cmd",
|
|
535
554
|
command_text=line,
|
|
536
|
-
other_msg=
|
|
555
|
+
other_msg=_unclosed_block_msg(block_stack[-1]),
|
|
537
556
|
)
|
|
538
557
|
frame = block_stack[-1]
|
|
539
558
|
script_node = frame.node
|
|
@@ -595,7 +614,7 @@ def _parse_lines(lines: Iterable[str], source_name: str) -> Script:
|
|
|
595
614
|
raise ErrInfo(
|
|
596
615
|
type="cmd",
|
|
597
616
|
command_text=line,
|
|
598
|
-
other_msg=f"ELSEIF
|
|
617
|
+
other_msg=f"ELSEIF on line {file_lineno} of {source_name} has no matching IF.",
|
|
599
618
|
)
|
|
600
619
|
frame = block_stack[-1]
|
|
601
620
|
frame._in_else = False
|
|
@@ -616,7 +635,7 @@ def _parse_lines(lines: Iterable[str], source_name: str) -> Script:
|
|
|
616
635
|
raise ErrInfo(
|
|
617
636
|
type="cmd",
|
|
618
637
|
command_text=line,
|
|
619
|
-
other_msg=f"ANDIF
|
|
638
|
+
other_msg=f"ANDIF on line {file_lineno} of {source_name} has no matching IF.",
|
|
620
639
|
)
|
|
621
640
|
modifier = ConditionModifier(
|
|
622
641
|
kind="AND",
|
|
@@ -638,7 +657,7 @@ def _parse_lines(lines: Iterable[str], source_name: str) -> Script:
|
|
|
638
657
|
raise ErrInfo(
|
|
639
658
|
type="cmd",
|
|
640
659
|
command_text=line,
|
|
641
|
-
other_msg=f"ORIF
|
|
660
|
+
other_msg=f"ORIF on line {file_lineno} of {source_name} has no matching IF.",
|
|
642
661
|
)
|
|
643
662
|
modifier = ConditionModifier(
|
|
644
663
|
kind="OR",
|
|
@@ -660,7 +679,7 @@ def _parse_lines(lines: Iterable[str], source_name: str) -> Script:
|
|
|
660
679
|
raise ErrInfo(
|
|
661
680
|
type="cmd",
|
|
662
681
|
command_text=line,
|
|
663
|
-
other_msg=f"ELSE
|
|
682
|
+
other_msg=f"ELSE on line {file_lineno} of {source_name} has no matching IF.",
|
|
664
683
|
)
|
|
665
684
|
frame = block_stack[-1]
|
|
666
685
|
frame._in_else = True
|
|
@@ -671,11 +690,17 @@ def _parse_lines(lines: Iterable[str], source_name: str) -> Script:
|
|
|
671
690
|
# -- ENDIF --
|
|
672
691
|
m = _ENDIF_RX.match(cmd_text)
|
|
673
692
|
if m:
|
|
674
|
-
if not block_stack
|
|
693
|
+
if not block_stack:
|
|
675
694
|
raise ErrInfo(
|
|
676
695
|
type="cmd",
|
|
677
696
|
command_text=line,
|
|
678
|
-
other_msg=f"ENDIF
|
|
697
|
+
other_msg=f"ENDIF on line {file_lineno} of {source_name} has no matching IF.",
|
|
698
|
+
)
|
|
699
|
+
if block_stack[-1].kind != "if":
|
|
700
|
+
raise ErrInfo(
|
|
701
|
+
type="cmd",
|
|
702
|
+
command_text=line,
|
|
703
|
+
other_msg=_unclosed_block_msg(block_stack[-1]),
|
|
679
704
|
)
|
|
680
705
|
frame = block_stack.pop()
|
|
681
706
|
frame.node.span = SourceSpan(source_name, frame.start_line, file_lineno)
|
|
@@ -702,11 +727,17 @@ def _parse_lines(lines: Iterable[str], source_name: str) -> Script:
|
|
|
702
727
|
# -- ENDLOOP --
|
|
703
728
|
m = _ENDLOOP_RX.match(cmd_text)
|
|
704
729
|
if m:
|
|
705
|
-
if not block_stack
|
|
730
|
+
if not block_stack:
|
|
731
|
+
raise ErrInfo(
|
|
732
|
+
type="cmd",
|
|
733
|
+
command_text=line,
|
|
734
|
+
other_msg=f"ENDLOOP on line {file_lineno} of {source_name} has no matching LOOP.",
|
|
735
|
+
)
|
|
736
|
+
if block_stack[-1].kind != "loop":
|
|
706
737
|
raise ErrInfo(
|
|
707
738
|
type="cmd",
|
|
708
739
|
command_text=line,
|
|
709
|
-
other_msg=
|
|
740
|
+
other_msg=_unclosed_block_msg(block_stack[-1]),
|
|
710
741
|
)
|
|
711
742
|
frame = block_stack.pop()
|
|
712
743
|
frame.node.span = SourceSpan(source_name, frame.start_line, file_lineno)
|
|
@@ -729,11 +760,17 @@ def _parse_lines(lines: Iterable[str], source_name: str) -> Script:
|
|
|
729
760
|
# -- END BATCH --
|
|
730
761
|
m = _END_BATCH_RX.match(cmd_text)
|
|
731
762
|
if m:
|
|
732
|
-
if not block_stack
|
|
763
|
+
if not block_stack:
|
|
764
|
+
raise ErrInfo(
|
|
765
|
+
type="cmd",
|
|
766
|
+
command_text=line,
|
|
767
|
+
other_msg=f"END BATCH on line {file_lineno} of {source_name} has no matching BEGIN BATCH.",
|
|
768
|
+
)
|
|
769
|
+
if block_stack[-1].kind != "batch":
|
|
733
770
|
raise ErrInfo(
|
|
734
771
|
type="cmd",
|
|
735
772
|
command_text=line,
|
|
736
|
-
other_msg=
|
|
773
|
+
other_msg=_unclosed_block_msg(block_stack[-1]),
|
|
737
774
|
)
|
|
738
775
|
frame = block_stack.pop()
|
|
739
776
|
frame.node.span = SourceSpan(source_name, frame.start_line, file_lineno)
|
{execsql2-2.18.1.data → execsql2-2.19.0.data}/data/execsql2_extras/example_config_prompt.sql
RENAMED
|
@@ -122,7 +122,7 @@
|
|
|
122
122
|
from configspecs cs inner join configusage cu
|
|
123
123
|
on cu.sub_var = cs.sub_var
|
|
124
124
|
where
|
|
125
|
-
usage = '
|
|
125
|
+
usage = !'!#usage!'!;
|
|
126
126
|
-- !x! prompt entry_form !!~spectbl!! message "You may change any of the configuration settings below."
|
|
127
127
|
-- !x! if(sub_defined(~boolean_int)) {config boolean_int !!~boolean_int!!}
|
|
128
128
|
-- !x! if(sub_defined(~boolean_words)) {config boolean_words !!~boolean_words!!}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# execsql.conf — Configuration file for execsql
|
|
2
2
|
#
|
|
3
|
-
# Documentation: https://execsql2.readthedocs.io/reference/configuration/
|
|
3
|
+
# Documentation: https://execsql2.readthedocs.io/en/latest/reference/configuration/
|
|
4
4
|
#
|
|
5
5
|
# This file uses INI format. Section names are case-sensitive (lowercase).
|
|
6
6
|
# Property names are not case-sensitive. Lines starting with # are comments.
|
|
@@ -263,7 +263,7 @@
|
|
|
263
263
|
#message_css=
|
|
264
264
|
|
|
265
265
|
# Obfuscated password (XOR, not cryptographically secure — use keyring instead).
|
|
266
|
-
# See: https://execsql2.readthedocs.io/reference/security/#credentials
|
|
266
|
+
# See: https://execsql2.readthedocs.io/en/latest/reference/security/#credentials
|
|
267
267
|
#enc_password=
|
|
268
268
|
|
|
269
269
|
|
|
@@ -279,6 +279,38 @@
|
|
|
279
279
|
# Values: Yes or No. Default: Yes.
|
|
280
280
|
#allow_system_cmd=Yes
|
|
281
281
|
|
|
282
|
+
# Whether to allow the RM_FILE metacommand (which deletes a file).
|
|
283
|
+
# Set to No to prevent scripts from deleting files.
|
|
284
|
+
# Also controllable via --no-rm-file CLI flag.
|
|
285
|
+
# Values: Yes or No. Default: Yes.
|
|
286
|
+
#allow_rm_file=Yes
|
|
287
|
+
|
|
288
|
+
# Whether to allow the SERVE metacommand (which opens an HTTP server on
|
|
289
|
+
# a local port to serve a single file). Set to No to disable.
|
|
290
|
+
# Also controllable via --no-serve CLI flag.
|
|
291
|
+
# Values: Yes or No. Default: Yes.
|
|
292
|
+
#allow_serve=Yes
|
|
293
|
+
|
|
294
|
+
# Root directory under which INCLUDE / EXECUTE SCRIPT targets must
|
|
295
|
+
# resolve. When set, attempts to include files outside this root via
|
|
296
|
+
# ../, absolute paths, drive letters, or UNC paths are rejected with
|
|
297
|
+
# an error. Default: no containment (any readable path is permitted).
|
|
298
|
+
#include_root=
|
|
299
|
+
|
|
300
|
+
# Root directory under which SERVE targets must resolve. Same
|
|
301
|
+
# containment semantics as include_root. Default: no containment.
|
|
302
|
+
#serve_root=
|
|
303
|
+
|
|
304
|
+
# Root directory under which Jinja2 / string.Template loader paths
|
|
305
|
+
# must resolve. Same containment semantics as include_root.
|
|
306
|
+
# Default: no containment.
|
|
307
|
+
#template_root=
|
|
308
|
+
|
|
309
|
+
# Maximum byte size of any single substitution-variable expansion,
|
|
310
|
+
# enforced by the substitute_vars() engine to defeat exponential-
|
|
311
|
+
# expansion bombs. Default: 10 MB (10485760).
|
|
312
|
+
#max_substitution_bytes=10485760
|
|
313
|
+
|
|
282
314
|
# Whether to log all data variable assignments.
|
|
283
315
|
# Values: Yes or No. Default: Yes.
|
|
284
316
|
#log_datavars=Yes
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: execsql2
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.19.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: Homepage, https://execsql2.readthedocs.io
|
|
6
6
|
Project-URL: Repository, https://github.com/geocoug/execsql
|
|
@@ -41,8 +41,7 @@ Classifier: Topic :: Database :: Front-Ends
|
|
|
41
41
|
Requires-Python: >=3.10
|
|
42
42
|
Requires-Dist: python-dateutil>=2.8
|
|
43
43
|
Requires-Dist: rich>=13.0
|
|
44
|
-
Requires-Dist:
|
|
45
|
-
Requires-Dist: textual>=0.47.0
|
|
44
|
+
Requires-Dist: textual>=1.0
|
|
46
45
|
Requires-Dist: typer>=0.12
|
|
47
46
|
Provides-Extra: all
|
|
48
47
|
Requires-Dist: defusedxml; extra == 'all'
|
|
@@ -59,6 +58,7 @@ Requires-Dist: psycopg2-binary; extra == 'all'
|
|
|
59
58
|
Requires-Dist: pymysql; extra == 'all'
|
|
60
59
|
Requires-Dist: pyodbc; extra == 'all'
|
|
61
60
|
Requires-Dist: pyyaml; extra == 'all'
|
|
61
|
+
Requires-Dist: sqlglot>=25.0; extra == 'all'
|
|
62
62
|
Requires-Dist: tables; extra == 'all'
|
|
63
63
|
Requires-Dist: tkintermapview>=1.29; extra == 'all'
|
|
64
64
|
Requires-Dist: xlrd; extra == 'all'
|
|
@@ -93,6 +93,7 @@ Requires-Dist: pre-commit>=3.5.0; extra == 'dev'
|
|
|
93
93
|
Requires-Dist: pytest-cov>=5.0.0; extra == 'dev'
|
|
94
94
|
Requires-Dist: pyyaml; extra == 'dev'
|
|
95
95
|
Requires-Dist: ruff>=0.4; extra == 'dev'
|
|
96
|
+
Requires-Dist: sqlglot>=25.0; extra == 'dev'
|
|
96
97
|
Requires-Dist: tables; extra == 'dev'
|
|
97
98
|
Requires-Dist: tox-uv>=1.13.1; extra == 'dev'
|
|
98
99
|
Requires-Dist: twine>=6.1.0; extra == 'dev'
|
|
@@ -111,6 +112,8 @@ Requires-Dist: polars; extra == 'formats'
|
|
|
111
112
|
Requires-Dist: pyyaml; extra == 'formats'
|
|
112
113
|
Requires-Dist: tables; extra == 'formats'
|
|
113
114
|
Requires-Dist: xlrd; extra == 'formats'
|
|
115
|
+
Provides-Extra: formatter
|
|
116
|
+
Requires-Dist: sqlglot>=25.0; extra == 'formatter'
|
|
114
117
|
Provides-Extra: map
|
|
115
118
|
Requires-Dist: tkintermapview>=1.29; extra == 'map'
|
|
116
119
|
Provides-Extra: mssql
|
|
@@ -380,14 +383,20 @@ The `PROMPT` metacommand produces a GUI display of the data:
|
|
|
380
383
|
|
|
381
384
|
# Formatting Scripts
|
|
382
385
|
|
|
383
|
-
The `execsql-format` command normalizes execsql script files: it uppercases metacommand keywords, corrects block indentation, and optionally reformats SQL via sqlglot
|
|
386
|
+
The `execsql-format` command normalizes execsql script files: it uppercases metacommand keywords, corrects block indentation, and optionally reformats SQL via `sqlglot`. The metacommand / indent / keyword reformatting is built into `execsql2`; SQL reformatting requires the `[formatter]` extra (or pass `--no-sql` to skip it):
|
|
384
387
|
|
|
385
388
|
```bash
|
|
389
|
+
# Install with the SQL-reformatting extra
|
|
390
|
+
pip install execsql2[formatter]
|
|
391
|
+
|
|
386
392
|
# Format files in place
|
|
387
393
|
execsql-format --in-place scripts/
|
|
388
394
|
|
|
389
395
|
# Check formatting without writing (useful in CI)
|
|
390
396
|
execsql-format --check scripts/
|
|
397
|
+
|
|
398
|
+
# Run the formatter without sqlglot — keyword/indent normalization only
|
|
399
|
+
execsql-format --no-sql --in-place scripts/
|
|
391
400
|
```
|
|
392
401
|
|
|
393
402
|
`execsql-format` is also available as a [pre-commit](https://pre-commit.com/) hook:
|
|
@@ -3,7 +3,7 @@ execsql/__main__.py,sha256=HdbK-SAhyUmfB6xINY5AzxdMSxGzWSGEG_2dv42Jn64,315
|
|
|
3
3
|
execsql/api.py,sha256=ZFTo_XZPhG21w2vxaeS1lS6o5XmF1FUJRIaypgTOjA8,20919
|
|
4
4
|
execsql/config.py,sha256=OOrCcn9m3CNuXkxVOLp7uMhQikzUS2wh_QVhvIzRqIM,33296
|
|
5
5
|
execsql/exceptions.py,sha256=EkM5cw2s0D9QCOgS4BU29FEyOnEtCxJ0esPT6l1hT9s,9205
|
|
6
|
-
execsql/format.py,sha256=
|
|
6
|
+
execsql/format.py,sha256=HJlWD4kSOvq7lBv99rRF1Cq1FNaMjG-qA3iwSTZtf8Q,26050
|
|
7
7
|
execsql/models.py,sha256=kCTUQg9-vReM6WNFfB_ZrEppuOW5u1uMBQThSkfPC0o,13264
|
|
8
8
|
execsql/parser.py,sha256=P3ea8k7T_XLMrbhpFNZXwytdShrY302MKnhosqza1lo,15493
|
|
9
9
|
execsql/plugins.py,sha256=2voLwT6eFap6BCBoZYndNNC_bMEJO1f_aP6xQTVXwYI,12815
|
|
@@ -14,28 +14,28 @@ execsql/cli/__init__.py,sha256=aJknKKIGxYCXpny0cyHXfqJsJ95dBtlEXXhPASFG8GQ,23114
|
|
|
14
14
|
execsql/cli/dsn.py,sha256=svaZtrUXFRL2W5G6FRRiKtR6kehOp7urrVhIx_642Z8,2820
|
|
15
15
|
execsql/cli/help.py,sha256=ThwdZuMIfLPxLAPpGWwXFY_UfyWvYOCjdlBNK20Vzd8,5718
|
|
16
16
|
execsql/cli/lint.py,sha256=YqKzFNUhyb_Th69hYgKk1ZZVjCsZfJMIiUGqp06JwNs,17236
|
|
17
|
-
execsql/cli/run.py,sha256=
|
|
17
|
+
execsql/cli/run.py,sha256=4plvi8ZaGea5fUeOhtS2f4MrVnGTaetvJyz7qX07s0I,37706
|
|
18
18
|
execsql/data/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
19
|
-
execsql/data/execsql.conf.template,sha256=
|
|
19
|
+
execsql/data/execsql.conf.template,sha256=Sq1Huwb_Uf_lI7zW7m3h11fqU6zjGpGnCU4OVJ_tCe0,10696
|
|
20
20
|
execsql/db/__init__.py,sha256=jTbuafuKOqYtXFR1wvCOoKK5Lr3l1uErfaIbIr6UywI,1063
|
|
21
|
-
execsql/db/access.py,sha256=
|
|
22
|
-
execsql/db/base.py,sha256=
|
|
21
|
+
execsql/db/access.py,sha256=GcY5Pq_vXdp8Thzm6aSLg9vfp5koovGiYKXipQtRt50,19167
|
|
22
|
+
execsql/db/base.py,sha256=UUiYSztUen8xCWVCxDkRQeD098Yv1iA3WPl_qo4XjDI,34686
|
|
23
23
|
execsql/db/dsn.py,sha256=59OzMAuCIfHcdOZNarK9TlDzaBJjhZ1SFFMvyXlH6u8,6086
|
|
24
24
|
execsql/db/duckdb.py,sha256=79lRzKRhw1Pjfqcrba27S4Oq8a8AbDO_d0XkaNKKPQo,3197
|
|
25
25
|
execsql/db/factory.py,sha256=YHdgyqQYy16548O3fGyElLC5C7DdIgva4Z29OsDxXjs,5367
|
|
26
26
|
execsql/db/firebird.py,sha256=p_7RFWhFI7y5ukKCMXeDPE0wjeQ6dpO4IK6uz2dYjrc,9224
|
|
27
|
-
execsql/db/mysql.py,sha256=
|
|
28
|
-
execsql/db/oracle.py,sha256=
|
|
27
|
+
execsql/db/mysql.py,sha256=gOm1IRzFmxnT4ekGEXiF2yM3jB6r4p38Cmtt1Utj27Y,17712
|
|
28
|
+
execsql/db/oracle.py,sha256=1_odb5xmlm8vjdJdQXz7SHm9dzIbZ5sxP_IxTklH3kA,12018
|
|
29
29
|
execsql/db/postgres.py,sha256=UNzrXzMniEyT3Z7qjCA_HLEUY0PVr1cJShuhAxtl5l0,21241
|
|
30
30
|
execsql/db/sqlite.py,sha256=xooU6bvD9Y3frRpnbyesE63r6E1fwEHkkcN1YD_UIUE,11519
|
|
31
|
-
execsql/db/sqlserver.py,sha256=
|
|
31
|
+
execsql/db/sqlserver.py,sha256=sxtOrcN1pGJQ0x7CctrKIL9XFIaHCDVIVvEvxzEOdzY,8744
|
|
32
32
|
execsql/debug/__init__.py,sha256=j6EGUR0dHzUhWN1mHHtf1-Lhjq3Sb1V-vmnq2Ztgj1M,178
|
|
33
|
-
execsql/debug/repl.py,sha256=
|
|
33
|
+
execsql/debug/repl.py,sha256=JObeoEXh15qyk4Q3WQCCqMcBtojlIcu_Xg-ZRDZJi5Q,25491
|
|
34
34
|
execsql/exporters/__init__.py,sha256=-Cnji-OgodJV8ftcDcOyTof0kQMy9J5kKVC8GVFpc3o,670
|
|
35
35
|
execsql/exporters/base.py,sha256=XTPenHl5TbmbZ3cfPYLVFirGNiVps3Kt3AQKFVKy6ss,6408
|
|
36
36
|
execsql/exporters/delimited.py,sha256=GIEeennL_elvcZgq5oSvgxAKAgwr8ea3o5_M_pLmc4g,32341
|
|
37
37
|
execsql/exporters/duckdb.py,sha256=R4WbvzBEIK1ptnIc8w6c7jcinG-cuuFYC85_NYCumH0,3146
|
|
38
|
-
execsql/exporters/feather.py,sha256=
|
|
38
|
+
execsql/exporters/feather.py,sha256=ardyidIcuhrqRBJh9ftUZkG3ZCY7NS7eGC-VAnpFIXI,4131
|
|
39
39
|
execsql/exporters/html.py,sha256=BPTGYODiC5_5zaQsVkZ9QVAl67yfCWFTsjK0D-QOJkM,9717
|
|
40
40
|
execsql/exporters/json.py,sha256=G9lyJcjgmMvymu_MoVrkSqx2H6JRN7qwA5UEomnPkVQ,4343
|
|
41
41
|
execsql/exporters/latex.py,sha256=w_B83_5vKPe8uYxCWGdqvxwJeq0mw5zzKYDiAb7dbN0,4503
|
|
@@ -59,17 +59,17 @@ execsql/gui/console.py,sha256=pCBUcFGjlKXMkMjztbmt9glP3me9jAKAgQxnmUE38-0,19396
|
|
|
59
59
|
execsql/gui/desktop.py,sha256=hdy-1-QxeKXe3GDG1lhxkQHhTjZxH7KNl_mVg5ki1qw,59080
|
|
60
60
|
execsql/gui/tui.py,sha256=INBCuW6iEEV15P_JrHeviI1JlNCXfoiyPBU-IC5MaLY,65200
|
|
61
61
|
execsql/importers/__init__.py,sha256=zZwdQxMaValCNqUrVdvaA7XPU3J8NmqVJ4uolNWY2iU,299
|
|
62
|
-
execsql/importers/base.py,sha256=
|
|
62
|
+
execsql/importers/base.py,sha256=k4ni92H33_-A32dNfxhBd6R5WPwM3o_8dSGABisUlR0,4136
|
|
63
63
|
execsql/importers/csv.py,sha256=GUVRP294vHlOlF8XNecPEzatUBOOFIqnrEV9cBQkiv0,4849
|
|
64
64
|
execsql/importers/feather.py,sha256=g2B69d2uv9vmnXcmjFyTVsMP40LYEzFYkhk3gD26mGw,1900
|
|
65
65
|
execsql/importers/json.py,sha256=fL47h09Zzx4Qw3TTQRQJbdPdD3qVMiU-kK9C7j1IQzg,5534
|
|
66
66
|
execsql/importers/ods.py,sha256=VsxIkr7opBamB0PbSFmMWt7G11w8lLUH1k_kRwz28zw,2847
|
|
67
|
-
execsql/importers/xls.py,sha256=
|
|
68
|
-
execsql/metacommands/__init__.py,sha256=
|
|
67
|
+
execsql/importers/xls.py,sha256=7mGYqXko5_wyvEUz186xSzIx8Eeobn5A6DCVAg4YfTM,3838
|
|
68
|
+
execsql/metacommands/__init__.py,sha256=H_D1Z5kN6zfYtxatejQhQhQV1tlVZ7rcdFZ_9uhXu2Q,13010
|
|
69
69
|
execsql/metacommands/conditions.py,sha256=5njexsBqSN_MNQdnw9Ra51BcS7ekACHNvBI2MXZF6_g,29995
|
|
70
70
|
execsql/metacommands/connect.py,sha256=W24gYGmYDXNQyzBTsqWtl9-qbX2FS0v_c4s_OHj97mY,15327
|
|
71
71
|
execsql/metacommands/control.py,sha256=btF9hP_jzTuTIODPK72CYF0v_oKYpwXpKLATt-Ti2kc,7988
|
|
72
|
-
execsql/metacommands/data.py,sha256=
|
|
72
|
+
execsql/metacommands/data.py,sha256=PPO6AnF_swFxy9GNrtMsv3YttT5ZrjOMA4QtEORDcso,12136
|
|
73
73
|
execsql/metacommands/debug.py,sha256=MeVXAob8ItEg2QzuSUkKDaQCEABnH6u0XcAwJzw36CE,13015
|
|
74
74
|
execsql/metacommands/dispatch.py,sha256=t7x0xWHJA0PeCrYf7jYeSMJgf0yxcZ_xh-_YAtBPHLw,87192
|
|
75
75
|
execsql/metacommands/io.py,sha256=vlGBje5sgnqeilooMdhJDgSRIhysHy5_7LrKtik9Xjs,3011
|
|
@@ -85,8 +85,8 @@ execsql/script/__init__.py,sha256=eGJPBDWj42aaId2lX_quSrqoKrvGwGElIrGDNCyoV1Y,35
|
|
|
85
85
|
execsql/script/ast.py,sha256=TQ4_7Lfw1F8_k6ycdvMZdzwNafrZiljSrthVRWUsuIk,20585
|
|
86
86
|
execsql/script/control.py,sha256=WqLy-HLPqHG3vEzYpKMiIJsD7LpORjyQuUWzFzcGz4w,2327
|
|
87
87
|
execsql/script/engine.py,sha256=52RmtQJGk4KWqXpZY7jfKeiPojAoULHWaigOcm1azm4,20979
|
|
88
|
-
execsql/script/executor.py,sha256=
|
|
89
|
-
execsql/script/parser.py,sha256=
|
|
88
|
+
execsql/script/executor.py,sha256=Y0con6Mz4n240krEVeVSqmeCLVvIXyF4aLlNBMyZQyc,35790
|
|
89
|
+
execsql/script/parser.py,sha256=z19gERLqyvEoDx4hDUws3Z8EKqTM7huNOitTOSmg8_0,35300
|
|
90
90
|
execsql/script/variables.py,sha256=t0BwrRuA8m1LYHGLkDPNbqW6QmudXroOFYsO0fwK2N0,16302
|
|
91
91
|
execsql/utils/__init__.py,sha256=0uR6JwVJQRX3vceByNBduCAf5dd5assKjeqJUWvpZoA,278
|
|
92
92
|
execsql/utils/auth.py,sha256=dnLie8jFxN_l7ZrrRufVuxGw92iG62DIVatIjlEb4pM,8717
|
|
@@ -100,24 +100,24 @@ execsql/utils/numeric.py,sha256=xh02ANSRk3nUpQ-rtm66ILoMqoi7HtzCoRMIOT9U8QI,1570
|
|
|
100
100
|
execsql/utils/regex.py,sha256=diEzTZqU_HHwVMadPAvN1Vgzhl7I03eVaEFGCXyGGL8,3770
|
|
101
101
|
execsql/utils/strings.py,sha256=UQNjpRCEFa1UO6feU-M-9e24wWAvizs_iu_4fFusLxo,8516
|
|
102
102
|
execsql/utils/timer.py,sha256=eDYf5VzCNFk7oo90InJucUm3XcBdhYMogjZMqeg9xzc,1899
|
|
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.
|
|
114
|
-
execsql2-2.
|
|
115
|
-
execsql2-2.
|
|
116
|
-
execsql2-2.
|
|
117
|
-
execsql2-2.
|
|
118
|
-
execsql2-2.
|
|
119
|
-
execsql2-2.
|
|
120
|
-
execsql2-2.
|
|
121
|
-
execsql2-2.
|
|
122
|
-
execsql2-2.
|
|
123
|
-
execsql2-2.
|
|
103
|
+
execsql2-2.19.0.data/data/execsql2_extras/README.md,sha256=sxwVyU0ZahCfANv56LahkyuM505kFjrMhe-1SvWE69E,4845
|
|
104
|
+
execsql2-2.19.0.data/data/execsql2_extras/config_settings.sqlite,sha256=aY5cxR7Q7J6zJ4bC9lu5mHUrhy211Cq3MNKPQVCt02E,20480
|
|
105
|
+
execsql2-2.19.0.data/data/execsql2_extras/example_config_prompt.sql,sha256=2e8KzzVWhho8KxYVHETSVmZdhW7wodioDsqBLSL6m4s,7487
|
|
106
|
+
execsql2-2.19.0.data/data/execsql2_extras/execsql.conf,sha256=Sq1Huwb_Uf_lI7zW7m3h11fqU6zjGpGnCU4OVJ_tCe0,10696
|
|
107
|
+
execsql2-2.19.0.data/data/execsql2_extras/make_config_db.sql,sha256=WwyC6dK-Eh5CAVppiBCDHqiI1_wEI9U95Ytpr4lsZkg,8726
|
|
108
|
+
execsql2-2.19.0.data/data/execsql2_extras/md_compare.sql,sha256=qYYVAjSeHZzjszxV3Bv6bg8Ckbq2kMHl87_gh4sywMU,24140
|
|
109
|
+
execsql2-2.19.0.data/data/execsql2_extras/md_glossary.sql,sha256=hkZ2Onn57LAKKsuXxzhR8tPtcWXkmWEQkwPE58-Tm2k,10796
|
|
110
|
+
execsql2-2.19.0.data/data/execsql2_extras/md_upsert.sql,sha256=_CAK4BzEboRXTNy03SJR-oOjcEdSNMuRBPL6noWUptY,112560
|
|
111
|
+
execsql2-2.19.0.data/data/execsql2_extras/pg_compare.sql,sha256=1zJd4hVUKHR0tncc2qTBC9B4qVV4Us2ITkJpsjN3tMw,24352
|
|
112
|
+
execsql2-2.19.0.data/data/execsql2_extras/pg_glossary.sql,sha256=IKuwna-_8b20ljSkXZruuiQigrCpo7ueQdUqd1MXiuI,9908
|
|
113
|
+
execsql2-2.19.0.data/data/execsql2_extras/pg_upsert.sql,sha256=HpPJtTHvpEjQy03j-3iPxDEOHMRkudOg7O4D4YR38UI,108315
|
|
114
|
+
execsql2-2.19.0.data/data/execsql2_extras/script_template.sql,sha256=2J35ddZPguJ-vwTsz83wErv0jiWUyJcdW_JM0mNKDXA,11155
|
|
115
|
+
execsql2-2.19.0.data/data/execsql2_extras/ss_compare.sql,sha256=j1qVNUPXQsEU7-DoVgDJCGcE0EuIl7whLBT3fgeiMAo,24833
|
|
116
|
+
execsql2-2.19.0.data/data/execsql2_extras/ss_glossary.sql,sha256=2gLxv34xzKt0vy7hSzJH7a9JiMC3ETrv9MofxQwAibU,13065
|
|
117
|
+
execsql2-2.19.0.data/data/execsql2_extras/ss_upsert.sql,sha256=G_8rQ0VzuKIZHWs24O_WrfzpC5S27R1JsL-bFBR3SUQ,117730
|
|
118
|
+
execsql2-2.19.0.dist-info/METADATA,sha256=hafmsG77iDxgwzrG7s4oMg5o97R-fMx_BH513avRy2w,22340
|
|
119
|
+
execsql2-2.19.0.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
|
|
120
|
+
execsql2-2.19.0.dist-info/entry_points.txt,sha256=sUOxkM-dN1eBGGpSpDLsAaE0yNXYQKWZAfxPOlMkQyk,90
|
|
121
|
+
execsql2-2.19.0.dist-info/licenses/LICENSE.txt,sha256=LBdhuxejF8_bLCHZ2kWfmDXpDGUu914Gbd6_3JjCRe0,676
|
|
122
|
+
execsql2-2.19.0.dist-info/licenses/NOTICE,sha256=McYzgxYav3U1OaVsY4Su1sfBrfmplpRdA9b6-gCDQCg,342
|
|
123
|
+
execsql2-2.19.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
|