execsql2 2.18.0__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/__init__.py +3 -5
- execsql/cli/lint.py +433 -18
- 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/metacommands/dispatch.py +5 -10
- execsql/metacommands/script_ext.py +8 -7
- execsql/script/engine.py +1 -12
- execsql/script/executor.py +2 -6
- execsql/script/parser.py +49 -12
- {execsql2-2.18.0.data → execsql2-2.19.0.data}/data/execsql2_extras/example_config_prompt.sql +1 -1
- {execsql2-2.18.0.data → execsql2-2.19.0.data}/data/execsql2_extras/execsql.conf +34 -2
- {execsql2-2.18.0.dist-info → execsql2-2.19.0.dist-info}/METADATA +54 -43
- {execsql2-2.18.0.dist-info → execsql2-2.19.0.dist-info}/RECORD +42 -43
- {execsql2-2.18.0.dist-info → execsql2-2.19.0.dist-info}/WHEEL +1 -1
- execsql/cli/lint_ast.py +0 -439
- {execsql2-2.18.0.data → execsql2-2.19.0.data}/data/execsql2_extras/README.md +0 -0
- {execsql2-2.18.0.data → execsql2-2.19.0.data}/data/execsql2_extras/config_settings.sqlite +0 -0
- {execsql2-2.18.0.data → execsql2-2.19.0.data}/data/execsql2_extras/make_config_db.sql +0 -0
- {execsql2-2.18.0.data → execsql2-2.19.0.data}/data/execsql2_extras/md_compare.sql +0 -0
- {execsql2-2.18.0.data → execsql2-2.19.0.data}/data/execsql2_extras/md_glossary.sql +0 -0
- {execsql2-2.18.0.data → execsql2-2.19.0.data}/data/execsql2_extras/md_upsert.sql +0 -0
- {execsql2-2.18.0.data → execsql2-2.19.0.data}/data/execsql2_extras/pg_compare.sql +0 -0
- {execsql2-2.18.0.data → execsql2-2.19.0.data}/data/execsql2_extras/pg_glossary.sql +0 -0
- {execsql2-2.18.0.data → execsql2-2.19.0.data}/data/execsql2_extras/pg_upsert.sql +0 -0
- {execsql2-2.18.0.data → execsql2-2.19.0.data}/data/execsql2_extras/script_template.sql +0 -0
- {execsql2-2.18.0.data → execsql2-2.19.0.data}/data/execsql2_extras/ss_compare.sql +0 -0
- {execsql2-2.18.0.data → execsql2-2.19.0.data}/data/execsql2_extras/ss_glossary.sql +0 -0
- {execsql2-2.18.0.data → execsql2-2.19.0.data}/data/execsql2_extras/ss_upsert.sql +0 -0
- {execsql2-2.18.0.dist-info → execsql2-2.19.0.dist-info}/entry_points.txt +0 -0
- {execsql2-2.18.0.dist-info → execsql2-2.19.0.dist-info}/licenses/LICENSE.txt +0 -0
- {execsql2-2.18.0.dist-info → execsql2-2.19.0.dist-info}/licenses/NOTICE +0 -0
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/metacommands/dispatch.py
CHANGED
|
@@ -1377,8 +1377,8 @@ def build_dispatch_table() -> MetaCommandList:
|
|
|
1377
1377
|
# BEGIN / END BATCH / ROLLBACK
|
|
1378
1378
|
# ------------------------------------------------------------------
|
|
1379
1379
|
mcl.add(r"^\s*BEGIN\s+BATCH\s*$", x_begin_batch, description="BEGIN BATCH", category="block")
|
|
1380
|
-
mcl.add(r"^\s*END\s+BATCH\s*$", x_end_batch, "END BATCH",
|
|
1381
|
-
mcl.add(r"^\s*ROLLBACK(:?\s+BATCH)?\s*$", x_rollback, "ROLLBACK BATCH",
|
|
1380
|
+
mcl.add(r"^\s*END\s+BATCH\s*$", x_end_batch, "END BATCH", category="block")
|
|
1381
|
+
mcl.add(r"^\s*ROLLBACK(:?\s+BATCH)?\s*$", x_rollback, "ROLLBACK BATCH", category="block")
|
|
1382
1382
|
|
|
1383
1383
|
# ------------------------------------------------------------------
|
|
1384
1384
|
# ERROR_HALT / METACOMMAND_ERROR_HALT / CANCEL_HALT
|
|
@@ -1727,14 +1727,12 @@ def build_dispatch_table() -> MetaCommandList:
|
|
|
1727
1727
|
x_assert,
|
|
1728
1728
|
description="ASSERT",
|
|
1729
1729
|
category="action",
|
|
1730
|
-
run_when_false=False,
|
|
1731
1730
|
)
|
|
1732
1731
|
mcl.add(
|
|
1733
1732
|
r"^\s*ASSERT\s+(?P<condtest>.+?)\s+(?P<message>(?:\"[^\"]*\"|'[^']*'))\s*$",
|
|
1734
1733
|
x_assert,
|
|
1735
1734
|
description="ASSERT",
|
|
1736
1735
|
category="action",
|
|
1737
|
-
run_when_false=False,
|
|
1738
1736
|
)
|
|
1739
1737
|
|
|
1740
1738
|
# ------------------------------------------------------------------
|
|
@@ -1745,7 +1743,6 @@ def build_dispatch_table() -> MetaCommandList:
|
|
|
1745
1743
|
x_breakpoint,
|
|
1746
1744
|
description="BREAKPOINT",
|
|
1747
1745
|
category="action",
|
|
1748
|
-
run_when_false=False,
|
|
1749
1746
|
)
|
|
1750
1747
|
|
|
1751
1748
|
# ------------------------------------------------------------------
|
|
@@ -1765,26 +1762,24 @@ def build_dispatch_table() -> MetaCommandList:
|
|
|
1765
1762
|
r"^\s*ORIF\s*\(\s*(?P<condtest>.+)\s*\)\s*$",
|
|
1766
1763
|
x_if_orif,
|
|
1767
1764
|
description="ORIF",
|
|
1768
|
-
run_when_false=True,
|
|
1769
1765
|
category="control",
|
|
1770
1766
|
)
|
|
1771
1767
|
mcl.add(
|
|
1772
1768
|
r"^\s*ELSEIF\s*\(\s*(?P<condtest>.+)\s*\)\s*$",
|
|
1773
1769
|
x_if_elseif,
|
|
1774
1770
|
description="ELSEIF",
|
|
1775
|
-
run_when_false=True,
|
|
1776
1771
|
category="control",
|
|
1777
1772
|
)
|
|
1778
1773
|
mcl.add(r"^\s*ANDIF\s*\(\s*(?P<condtest>.+)\s*\)\s*$", x_if_andif, description="ANDIF", category="control")
|
|
1779
|
-
mcl.add(r"^\s*ELSE\s*$", x_if_else, description="ELSE",
|
|
1774
|
+
mcl.add(r"^\s*ELSE\s*$", x_if_else, description="ELSE", category="control")
|
|
1780
1775
|
mcl.add(
|
|
1781
1776
|
r"^\s*IF\s*\(\s*(?P<condtest>.+)\s*\)\s*{\s*(?P<condcmd>.+)\s*}\s*$",
|
|
1782
1777
|
x_if,
|
|
1783
1778
|
description="IF",
|
|
1784
1779
|
category="control",
|
|
1785
1780
|
)
|
|
1786
|
-
mcl.add(r"^\s*IF\s*\(\s*(?P<condtest>.+)\s*\)\s*$", x_if_block,
|
|
1787
|
-
mcl.add(r"^\s*ENDIF\s*$", x_if_end, description="ENDIF",
|
|
1781
|
+
mcl.add(r"^\s*IF\s*\(\s*(?P<condtest>.+)\s*\)\s*$", x_if_block, category="control")
|
|
1782
|
+
mcl.add(r"^\s*ENDIF\s*$", x_if_end, description="ENDIF", category="control")
|
|
1788
1783
|
|
|
1789
1784
|
# ------------------------------------------------------------------
|
|
1790
1785
|
# CONNECT — SQL Server
|
|
@@ -3,22 +3,23 @@ from __future__ import annotations
|
|
|
3
3
|
"""
|
|
4
4
|
Script-block extension and dispatch handlers for execsql.
|
|
5
5
|
|
|
6
|
-
Handlers for the named-script
|
|
7
|
-
|
|
6
|
+
Handlers for the named-script extension metacommands invoked by the
|
|
7
|
+
AST executor:
|
|
8
8
|
|
|
9
|
-
- ``x_executescript`` — ``EXECUTE SCRIPT <name>`` / ``RUN SCRIPT <name>``
|
|
10
|
-
(look up a previously-registered ``BEGIN SCRIPT`` block and run it,
|
|
11
|
-
optionally with parameter bindings and a WHILE / UNTIL loop).
|
|
12
9
|
- ``x_extendscript`` — ``EXTEND SCRIPT <name> WITH SCRIPT|FILE …``
|
|
13
10
|
(append additional commands to an existing named script block from
|
|
14
11
|
an inline source).
|
|
15
12
|
- ``x_extendscript_metacommand`` — ``EXTEND SCRIPT … WITH METACOMMAND …``.
|
|
16
13
|
- ``x_extendscript_sql`` — ``EXTEND SCRIPT … WITH SQL …``.
|
|
14
|
+
- ``x_executescript`` — dispatch-table sentinel only. ``EXECUTE SCRIPT``
|
|
15
|
+
/ ``RUN SCRIPT`` are handled natively by the AST executor; this stub
|
|
16
|
+
raises ``ErrInfo`` if the parser ever fails to recognize them as
|
|
17
|
+
structural nodes.
|
|
17
18
|
|
|
18
19
|
Registration of ``BEGIN SCRIPT … END SCRIPT`` blocks themselves is
|
|
19
20
|
handled by the AST parser (block boundaries) and executor (registering
|
|
20
|
-
the block on ``ctx.ast_scripts``); this module is only the
|
|
21
|
-
extension handlers.
|
|
21
|
+
the block on ``ctx.ast_scripts``); this module is only the
|
|
22
|
+
extension-site handlers.
|
|
22
23
|
"""
|
|
23
24
|
|
|
24
25
|
import copy
|
execsql/script/engine.py
CHANGED
|
@@ -67,25 +67,18 @@ class MetaCommand:
|
|
|
67
67
|
rx: Any,
|
|
68
68
|
exec_func: Any,
|
|
69
69
|
description: str | None = None,
|
|
70
|
-
run_in_batch: bool = False,
|
|
71
|
-
run_when_false: bool = False,
|
|
72
70
|
set_error_flag: bool = True,
|
|
73
71
|
category: str | None = None,
|
|
74
72
|
) -> None:
|
|
75
73
|
self.rx = rx
|
|
76
74
|
self.exec_fn = exec_func
|
|
77
75
|
self.description = description
|
|
78
|
-
self.run_in_batch = run_in_batch
|
|
79
|
-
self.run_when_false = run_when_false
|
|
80
76
|
self.set_error_flag = set_error_flag
|
|
81
77
|
self.category = category
|
|
82
78
|
self.hitcount = 0
|
|
83
79
|
|
|
84
80
|
def __repr__(self) -> str:
|
|
85
|
-
return (
|
|
86
|
-
f"MetaCommand({self.rx.pattern!r}, {self.exec_fn!r}, {self.description!r}, "
|
|
87
|
-
f"{self.run_in_batch!r}, {self.run_when_false!r})"
|
|
88
|
-
)
|
|
81
|
+
return f"MetaCommand({self.rx.pattern!r}, {self.exec_fn!r}, {self.description!r})"
|
|
89
82
|
|
|
90
83
|
def run(self, cmd_str: str) -> tuple:
|
|
91
84
|
"""Match *cmd_str* against this entry's regex and, if it matches, invoke the handler.
|
|
@@ -171,8 +164,6 @@ class MetaCommandList:
|
|
|
171
164
|
matching_regexes: Any,
|
|
172
165
|
exec_func: Any,
|
|
173
166
|
description: str | None = None,
|
|
174
|
-
run_in_batch: bool = False,
|
|
175
|
-
run_when_false: bool = False,
|
|
176
167
|
set_error_flag: bool = True,
|
|
177
168
|
category: str | None = None,
|
|
178
169
|
) -> None:
|
|
@@ -193,8 +184,6 @@ class MetaCommandList:
|
|
|
193
184
|
rx,
|
|
194
185
|
exec_func,
|
|
195
186
|
description,
|
|
196
|
-
run_in_batch,
|
|
197
|
-
run_when_false,
|
|
198
187
|
set_error_flag,
|
|
199
188
|
category,
|
|
200
189
|
)
|
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
|
|