execsql2 2.1.2__py3-none-any.whl → 2.2.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- execsql/cli/__init__.py +436 -0
- execsql/cli/dsn.py +86 -0
- execsql/cli/help.py +140 -0
- execsql/{cli.py → cli/run.py} +14 -589
- execsql/config.py +13 -1
- execsql/db/access.py +16 -12
- execsql/db/base.py +158 -90
- execsql/db/dsn.py +6 -5
- execsql/db/duckdb.py +2 -2
- execsql/db/firebird.py +23 -19
- execsql/db/mysql.py +8 -7
- execsql/db/oracle.py +11 -11
- execsql/db/postgres.py +28 -16
- execsql/db/sqlite.py +12 -11
- execsql/db/sqlserver.py +5 -3
- execsql/exceptions.py +7 -7
- execsql/exporters/base.py +6 -1
- execsql/exporters/delimited.py +44 -35
- execsql/exporters/duckdb.py +2 -2
- execsql/exporters/feather.py +6 -6
- execsql/exporters/html.py +83 -69
- execsql/exporters/json.py +50 -42
- execsql/exporters/latex.py +33 -27
- execsql/exporters/ods.py +4 -4
- execsql/exporters/parquet.py +2 -2
- execsql/exporters/pretty.py +11 -9
- execsql/exporters/raw.py +17 -13
- execsql/exporters/sqlite.py +2 -2
- execsql/exporters/templates.py +23 -15
- execsql/exporters/values.py +22 -20
- execsql/exporters/xls.py +4 -4
- execsql/exporters/xml.py +28 -13
- execsql/importers/base.py +4 -4
- execsql/importers/csv.py +6 -6
- execsql/importers/feather.py +4 -4
- execsql/importers/ods.py +4 -4
- execsql/importers/xls.py +4 -4
- execsql/metacommands/__init__.py +518 -67
- execsql/metacommands/conditions.py +101 -27
- execsql/metacommands/control.py +8 -4
- execsql/metacommands/data.py +6 -6
- execsql/metacommands/debug.py +6 -2
- execsql/metacommands/io.py +67 -1310
- execsql/metacommands/io_export.py +442 -0
- execsql/metacommands/io_fileops.py +287 -0
- execsql/metacommands/io_import.py +398 -0
- execsql/metacommands/io_write.py +248 -0
- execsql/metacommands/prompt.py +22 -66
- execsql/metacommands/system.py +7 -2
- execsql/py.typed +0 -0
- execsql/script.py +49 -5
- execsql/types.py +20 -20
- execsql/utils/fileio.py +15 -8
- {execsql2-2.1.2.dist-info → execsql2-2.2.1.dist-info}/METADATA +6 -6
- execsql2-2.2.1.dist-info/RECORD +104 -0
- execsql2-2.1.2.dist-info/RECORD +0 -96
- {execsql2-2.1.2.data → execsql2-2.2.1.data}/data/execsql2_extras/READ_ME.rst +0 -0
- {execsql2-2.1.2.data → execsql2-2.2.1.data}/data/execsql2_extras/config_settings.sqlite +0 -0
- {execsql2-2.1.2.data → execsql2-2.2.1.data}/data/execsql2_extras/example_config_prompt.sql +0 -0
- {execsql2-2.1.2.data → execsql2-2.2.1.data}/data/execsql2_extras/execsql.conf +0 -0
- {execsql2-2.1.2.data → execsql2-2.2.1.data}/data/execsql2_extras/make_config_db.sql +0 -0
- {execsql2-2.1.2.data → execsql2-2.2.1.data}/data/execsql2_extras/md_compare.sql +0 -0
- {execsql2-2.1.2.data → execsql2-2.2.1.data}/data/execsql2_extras/md_glossary.sql +0 -0
- {execsql2-2.1.2.data → execsql2-2.2.1.data}/data/execsql2_extras/md_upsert.sql +0 -0
- {execsql2-2.1.2.data → execsql2-2.2.1.data}/data/execsql2_extras/pg_compare.sql +0 -0
- {execsql2-2.1.2.data → execsql2-2.2.1.data}/data/execsql2_extras/pg_glossary.sql +0 -0
- {execsql2-2.1.2.data → execsql2-2.2.1.data}/data/execsql2_extras/pg_upsert.sql +0 -0
- {execsql2-2.1.2.data → execsql2-2.2.1.data}/data/execsql2_extras/script_template.sql +0 -0
- {execsql2-2.1.2.data → execsql2-2.2.1.data}/data/execsql2_extras/ss_compare.sql +0 -0
- {execsql2-2.1.2.data → execsql2-2.2.1.data}/data/execsql2_extras/ss_glossary.sql +0 -0
- {execsql2-2.1.2.data → execsql2-2.2.1.data}/data/execsql2_extras/ss_upsert.sql +0 -0
- {execsql2-2.1.2.dist-info → execsql2-2.2.1.dist-info}/WHEEL +0 -0
- {execsql2-2.1.2.dist-info → execsql2-2.2.1.dist-info}/entry_points.txt +0 -0
- {execsql2-2.1.2.dist-info → execsql2-2.2.1.dist-info}/licenses/LICENSE.txt +0 -0
- {execsql2-2.1.2.dist-info → execsql2-2.2.1.dist-info}/licenses/NOTICE +0 -0
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
"""WRITE metacommand handlers.
|
|
2
|
+
|
|
3
|
+
Implements ``x_write``, ``x_write_create_table`` (CSV, ODS, XLS, alias),
|
|
4
|
+
``x_write_prefix``, ``x_write_suffix``, and ``x_writescript``.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
import execsql.state as _state
|
|
13
|
+
from execsql.exceptions import ErrInfo
|
|
14
|
+
from execsql.exporters.delimited import CsvFile
|
|
15
|
+
from execsql.importers.ods import ods_data
|
|
16
|
+
from execsql.importers.xls import xls_data
|
|
17
|
+
from execsql.models import DataTable
|
|
18
|
+
from execsql.script import substitute_vars
|
|
19
|
+
from execsql.utils.errors import exception_desc
|
|
20
|
+
from execsql.utils.fileio import check_dir, filewriter_close, filewriter_open_as_new, filewriter_write
|
|
21
|
+
from execsql.utils.gui import ConsoleUIError
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def x_write(**kwargs: Any) -> None:
|
|
25
|
+
msg = f"{kwargs['text']}\n"
|
|
26
|
+
tee = kwargs["tee"]
|
|
27
|
+
tee = bool(tee)
|
|
28
|
+
outf = kwargs["filename"]
|
|
29
|
+
if _state.conf.write_prefix is not None:
|
|
30
|
+
msg = substitute_vars(_state.conf.write_prefix) + " " + msg
|
|
31
|
+
if _state.conf.write_suffix is not None:
|
|
32
|
+
msg = msg[:-1] + " " + substitute_vars(_state.conf.write_suffix) + "\n"
|
|
33
|
+
if outf:
|
|
34
|
+
check_dir(outf)
|
|
35
|
+
filewriter_write(outf, msg)
|
|
36
|
+
if (not outf) or tee:
|
|
37
|
+
try:
|
|
38
|
+
_state.output.write(msg)
|
|
39
|
+
except TypeError as e:
|
|
40
|
+
raise ErrInfo(
|
|
41
|
+
type="other",
|
|
42
|
+
command_text=kwargs["metacommandline"],
|
|
43
|
+
other_msg="TypeError in 'write' metacommand.",
|
|
44
|
+
) from e
|
|
45
|
+
except ConsoleUIError as e:
|
|
46
|
+
_state.output.reset()
|
|
47
|
+
_state.exec_log.log_status_info(f"Console UI write failed (message {{{e.value}}}); output reset to stdout.")
|
|
48
|
+
_state.output.write(msg.encode(_state.conf.output_encoding))
|
|
49
|
+
if _state.conf.tee_write_log:
|
|
50
|
+
_state.exec_log.log_user_msg(msg)
|
|
51
|
+
return None
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def x_write_create_table(**kwargs: Any) -> None:
|
|
55
|
+
filename = kwargs["filename"]
|
|
56
|
+
if not Path(filename).exists():
|
|
57
|
+
raise ErrInfo(
|
|
58
|
+
type="cmd",
|
|
59
|
+
command_text=kwargs["metacommandline"],
|
|
60
|
+
other_msg="Input file does not exist",
|
|
61
|
+
)
|
|
62
|
+
quotechar = kwargs["quotechar"]
|
|
63
|
+
delimchar = kwargs["delimchar"]
|
|
64
|
+
encoding = kwargs["encoding"]
|
|
65
|
+
if delimchar:
|
|
66
|
+
if delimchar.lower() == "tab":
|
|
67
|
+
delimchar = chr(9)
|
|
68
|
+
elif delimchar.lower() in ("unitsep", "us"):
|
|
69
|
+
delimchar = chr(31)
|
|
70
|
+
junk_hdrs = kwargs["skip"]
|
|
71
|
+
if not junk_hdrs:
|
|
72
|
+
junk_hdrs = 0
|
|
73
|
+
else:
|
|
74
|
+
junk_hdrs = int(junk_hdrs)
|
|
75
|
+
enc = encoding if encoding else _state.conf.import_encoding
|
|
76
|
+
inf = CsvFile(filename, enc, junk_header_lines=junk_hdrs)
|
|
77
|
+
if quotechar and delimchar:
|
|
78
|
+
inf.lineformat(delimchar, quotechar, None)
|
|
79
|
+
inf.evaluate_column_types()
|
|
80
|
+
sql = inf.create_table(_state.dbs.current().type, kwargs["schema"], kwargs["table"], pretty=True)
|
|
81
|
+
inf.close()
|
|
82
|
+
comment = kwargs["comment"]
|
|
83
|
+
outfile = kwargs["outfile"]
|
|
84
|
+
|
|
85
|
+
def write(txt: str) -> None:
|
|
86
|
+
if outfile is None or outfile == "stdout":
|
|
87
|
+
_state.output.write(txt)
|
|
88
|
+
else:
|
|
89
|
+
filewriter_write(outfile, txt)
|
|
90
|
+
|
|
91
|
+
if outfile:
|
|
92
|
+
check_dir(outfile)
|
|
93
|
+
if comment:
|
|
94
|
+
write(f"-- {comment}\n")
|
|
95
|
+
write(f"{sql}\n")
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def x_write_create_table_ods(**kwargs: Any) -> None:
|
|
99
|
+
schemaname = kwargs["schema"]
|
|
100
|
+
tablename = kwargs["table"]
|
|
101
|
+
filename = kwargs["filename"]
|
|
102
|
+
sheetname = kwargs["sheet"]
|
|
103
|
+
hdr_rows = kwargs["skip"]
|
|
104
|
+
if not hdr_rows:
|
|
105
|
+
hdr_rows = 0
|
|
106
|
+
else:
|
|
107
|
+
hdr_rows = int(hdr_rows)
|
|
108
|
+
comment = kwargs["comment"]
|
|
109
|
+
outfile = kwargs["outfile"]
|
|
110
|
+
if not Path(filename).exists():
|
|
111
|
+
raise ErrInfo(
|
|
112
|
+
type="cmd",
|
|
113
|
+
command_text=kwargs["metacommandline"],
|
|
114
|
+
other_msg="Input file does not exist",
|
|
115
|
+
)
|
|
116
|
+
hdrs, data = ods_data(filename, sheetname, hdr_rows)
|
|
117
|
+
tablespec = DataTable(hdrs, data)
|
|
118
|
+
sql = tablespec.create_table(_state.dbs.current().type, schemaname, tablename, pretty=True)
|
|
119
|
+
if outfile:
|
|
120
|
+
if comment:
|
|
121
|
+
filewriter_write(outfile, f"-- {comment}\n")
|
|
122
|
+
filewriter_write(outfile, sql)
|
|
123
|
+
filewriter_close(outfile)
|
|
124
|
+
else:
|
|
125
|
+
if comment:
|
|
126
|
+
_state.output.write(f"-- {comment}\n")
|
|
127
|
+
_state.output.write(f"{sql}\n")
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def x_write_create_table_xls(**kwargs: Any) -> None:
|
|
131
|
+
schemaname = kwargs["schema"]
|
|
132
|
+
tablename = kwargs["table"]
|
|
133
|
+
filename = kwargs["filename"]
|
|
134
|
+
sheetname = kwargs["sheet"]
|
|
135
|
+
junk_hdrs = kwargs["skip"]
|
|
136
|
+
encoding = kwargs["encoding"]
|
|
137
|
+
enc = encoding if encoding else _state.conf.import_encoding
|
|
138
|
+
if not junk_hdrs:
|
|
139
|
+
junk_hdrs = 0
|
|
140
|
+
else:
|
|
141
|
+
junk_hdrs = int(junk_hdrs)
|
|
142
|
+
comment = kwargs["comment"]
|
|
143
|
+
outfile = kwargs["outfile"]
|
|
144
|
+
if not Path(filename).exists():
|
|
145
|
+
raise ErrInfo(
|
|
146
|
+
type="cmd",
|
|
147
|
+
command_text=kwargs["metacommandline"],
|
|
148
|
+
other_msg="Input file does not exist",
|
|
149
|
+
)
|
|
150
|
+
hdrs, data = xls_data(filename, sheetname, junk_hdrs, enc)
|
|
151
|
+
tablespec = DataTable(hdrs, data)
|
|
152
|
+
sql = tablespec.create_table(_state.dbs.current().type, schemaname, tablename, pretty=True)
|
|
153
|
+
if outfile:
|
|
154
|
+
if comment:
|
|
155
|
+
filewriter_write(outfile, f"-- {comment}\n")
|
|
156
|
+
filewriter_write(outfile, sql)
|
|
157
|
+
filewriter_close(outfile)
|
|
158
|
+
else:
|
|
159
|
+
if comment:
|
|
160
|
+
_state.output.write(f"-- {comment}\n")
|
|
161
|
+
_state.output.write(f"{sql}\n")
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def x_write_create_table_alias(**kwargs: Any) -> None:
|
|
165
|
+
alias = kwargs["alias"].lower()
|
|
166
|
+
schema = kwargs["schema"]
|
|
167
|
+
table = kwargs["table"]
|
|
168
|
+
comment = kwargs["comment"]
|
|
169
|
+
outfile = kwargs["filename"]
|
|
170
|
+
if alias not in _state.dbs.aliases():
|
|
171
|
+
raise ErrInfo(
|
|
172
|
+
type="cmd",
|
|
173
|
+
command_text=kwargs["metacommandline"],
|
|
174
|
+
other_msg=f"Unrecognized database alias: {alias}.",
|
|
175
|
+
)
|
|
176
|
+
db = _state.dbs.aliased_as(alias)
|
|
177
|
+
tbl = db.schema_qualified_table_name(schema, table)
|
|
178
|
+
try:
|
|
179
|
+
if not db.table_exists(table, schema):
|
|
180
|
+
raise ErrInfo(
|
|
181
|
+
type="cmd",
|
|
182
|
+
command_text=kwargs["metacommandline"],
|
|
183
|
+
other_msg=f"Table {tbl} does not exist",
|
|
184
|
+
)
|
|
185
|
+
except Exception:
|
|
186
|
+
pass # Best-effort check; some adapters lack information_schema.
|
|
187
|
+
select_stmt = f"select * from {tbl};"
|
|
188
|
+
try:
|
|
189
|
+
hdrs, rows = db.select_rowsource(select_stmt)
|
|
190
|
+
except ErrInfo:
|
|
191
|
+
raise
|
|
192
|
+
except Exception as e:
|
|
193
|
+
raise ErrInfo("db", select_stmt, exception_msg=exception_desc()) from e
|
|
194
|
+
tablespec = DataTable(hdrs, rows)
|
|
195
|
+
sql = tablespec.create_table(_state.dbs.current().type, kwargs["schema1"], kwargs["table1"], pretty=True)
|
|
196
|
+
if outfile:
|
|
197
|
+
if comment:
|
|
198
|
+
filewriter_write(outfile, f"-- {comment}\n")
|
|
199
|
+
filewriter_write(outfile, sql)
|
|
200
|
+
filewriter_close(outfile)
|
|
201
|
+
else:
|
|
202
|
+
if comment:
|
|
203
|
+
_state.output.write(f"-- {comment}\n")
|
|
204
|
+
_state.output.write(f"{sql}\n")
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def x_write_prefix(**kwargs: Any) -> None:
|
|
208
|
+
pf = kwargs["prefix"]
|
|
209
|
+
if pf.lower() == "clear":
|
|
210
|
+
_state.conf.write_prefix = None
|
|
211
|
+
else:
|
|
212
|
+
_state.conf.write_prefix = pf
|
|
213
|
+
return None
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def x_write_suffix(**kwargs: Any) -> None:
|
|
217
|
+
sf = kwargs["suffix"]
|
|
218
|
+
if sf.lower() == "clear":
|
|
219
|
+
_state.conf.write_suffix = None
|
|
220
|
+
else:
|
|
221
|
+
_state.conf.write_suffix = sf
|
|
222
|
+
return None
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def x_writescript(**kwargs: Any) -> None:
|
|
226
|
+
script_id = kwargs["script_id"]
|
|
227
|
+
output_dest = kwargs["filename"]
|
|
228
|
+
append = kwargs["append"]
|
|
229
|
+
|
|
230
|
+
def write(txt: str) -> None:
|
|
231
|
+
if output_dest is None or output_dest == "stdout":
|
|
232
|
+
_state.output.write(txt)
|
|
233
|
+
else:
|
|
234
|
+
filewriter_write(output_dest, txt)
|
|
235
|
+
|
|
236
|
+
if output_dest is not None and output_dest != "stdout":
|
|
237
|
+
check_dir(output_dest)
|
|
238
|
+
if not append:
|
|
239
|
+
filewriter_open_as_new(output_dest)
|
|
240
|
+
script = _state.savedscripts[script_id]
|
|
241
|
+
if script.paramnames is not None and len(script.paramnames) > 0:
|
|
242
|
+
write(f"BEGIN SCRIPT {script_id} ({', '.join(script.paramnames)})\n")
|
|
243
|
+
else:
|
|
244
|
+
write(f"BEGIN SCRIPT {script_id}\n")
|
|
245
|
+
lines = [c.commandline() for c in script.cmdlist]
|
|
246
|
+
for line in lines:
|
|
247
|
+
write(f"{line}\n")
|
|
248
|
+
write(f"END SCRIPT {script_id}\n")
|
execsql/metacommands/prompt.py
CHANGED
|
@@ -29,7 +29,6 @@ from typing import Any
|
|
|
29
29
|
import execsql.state as _state
|
|
30
30
|
from execsql.script import current_script_line
|
|
31
31
|
from execsql.utils.errors import exception_desc, exit_now
|
|
32
|
-
from execsql.utils.fileio import EncodedFile, check_dir
|
|
33
32
|
from execsql.utils.gui import (
|
|
34
33
|
ActionSpec,
|
|
35
34
|
EntrySpec,
|
|
@@ -38,7 +37,6 @@ from execsql.utils.gui import (
|
|
|
38
37
|
GUI_DIRECTORY,
|
|
39
38
|
GUI_DISPLAY,
|
|
40
39
|
GUI_ENTRY,
|
|
41
|
-
GUI_HALT,
|
|
42
40
|
GUI_MAP,
|
|
43
41
|
GUI_MSG,
|
|
44
42
|
GUI_OPENFILE,
|
|
@@ -210,12 +208,12 @@ def x_prompt_entryform(**kwargs: Any) -> None:
|
|
|
210
208
|
initial_value = str(str(v["initial_value"]).lower() in ("yes", "true", "on", "1"))
|
|
211
209
|
else:
|
|
212
210
|
initial_value = str(v["initial_value"])
|
|
213
|
-
except Exception:
|
|
211
|
+
except Exception as e:
|
|
214
212
|
raise ErrInfo(
|
|
215
213
|
type="cmd",
|
|
216
214
|
command_text=kwargs["metacommandline"],
|
|
217
215
|
other_msg=f"The initial value of {v['initial_value']} can't be used.",
|
|
218
|
-
)
|
|
216
|
+
) from e
|
|
219
217
|
if "lookup_table" in colhdrs:
|
|
220
218
|
lt = v["lookup_table"]
|
|
221
219
|
if lt:
|
|
@@ -226,23 +224,23 @@ def x_prompt_entryform(**kwargs: Any) -> None:
|
|
|
226
224
|
if entry_width:
|
|
227
225
|
try:
|
|
228
226
|
entry_width = int(entry_width)
|
|
229
|
-
except Exception:
|
|
227
|
+
except Exception as e:
|
|
230
228
|
raise ErrInfo(
|
|
231
229
|
type="cmd",
|
|
232
230
|
command_text=kwargs["metacommandline"],
|
|
233
231
|
other_msg=f"Entry width {entry_width} is not an integer",
|
|
234
|
-
)
|
|
232
|
+
) from e
|
|
235
233
|
if "height" in colhdrs:
|
|
236
234
|
entry_height = v.get("height")
|
|
237
235
|
if entry_height:
|
|
238
236
|
try:
|
|
239
237
|
entry_height = int(entry_height)
|
|
240
|
-
except Exception:
|
|
238
|
+
except Exception as e:
|
|
241
239
|
raise ErrInfo(
|
|
242
240
|
type="cmd",
|
|
243
241
|
command_text=kwargs["metacommandline"],
|
|
244
242
|
other_msg=f"Entry height {entry_height} is not an integer",
|
|
245
|
-
)
|
|
243
|
+
) from e
|
|
246
244
|
if entry_height < 1:
|
|
247
245
|
entry_height = 1
|
|
248
246
|
if "form_column" in colhdrs:
|
|
@@ -250,12 +248,12 @@ def x_prompt_entryform(**kwargs: Any) -> None:
|
|
|
250
248
|
if entry_col:
|
|
251
249
|
try:
|
|
252
250
|
entry_col = int(entry_col)
|
|
253
|
-
except Exception:
|
|
251
|
+
except Exception as e:
|
|
254
252
|
raise ErrInfo(
|
|
255
253
|
type="cmd",
|
|
256
254
|
command_text=kwargs["metacommandline"],
|
|
257
255
|
other_msg=f"Entry column {entry_col} is not an integer",
|
|
258
|
-
)
|
|
256
|
+
) from e
|
|
259
257
|
if entry_col < 1:
|
|
260
258
|
entry_col = 1
|
|
261
259
|
subvarset = _state.subvars if subvar[0] != "~" else _state.commandliststack[-1].localvars
|
|
@@ -363,15 +361,15 @@ def prompt_compare(button_list: list, **kwargs: Any) -> Any:
|
|
|
363
361
|
if alias1 is not None:
|
|
364
362
|
try:
|
|
365
363
|
db1 = _state.dbs.aliased_as(alias1)
|
|
366
|
-
except Exception:
|
|
367
|
-
raise ErrInfo(type="error", other_msg=badaliasmsg % alias1)
|
|
364
|
+
except Exception as e:
|
|
365
|
+
raise ErrInfo(type="error", other_msg=badaliasmsg % alias1) from e
|
|
368
366
|
else:
|
|
369
367
|
db1 = _state.dbs.current()
|
|
370
368
|
if alias2 is not None:
|
|
371
369
|
try:
|
|
372
370
|
db2 = _state.dbs.aliased_as(alias2)
|
|
373
|
-
except Exception:
|
|
374
|
-
raise ErrInfo(type="error", other_msg=badaliasmsg % alias2)
|
|
371
|
+
except Exception as e:
|
|
372
|
+
raise ErrInfo(type="error", other_msg=badaliasmsg % alias2) from e
|
|
375
373
|
else:
|
|
376
374
|
db2 = _state.dbs.current()
|
|
377
375
|
sq_name1 = db1.schema_qualified_table_name(schema1, table1)
|
|
@@ -666,8 +664,8 @@ def x_prompt_savefile(**kwargs: Any) -> None:
|
|
|
666
664
|
subvarset5.add_substitution(sub_name5, Path(fn).stem)
|
|
667
665
|
except (ErrInfo, SystemExit):
|
|
668
666
|
raise
|
|
669
|
-
except Exception:
|
|
670
|
-
raise ErrInfo(type="exception", exception_msg=exception_desc())
|
|
667
|
+
except Exception as e:
|
|
668
|
+
raise ErrInfo(type="exception", exception_msg=exception_desc()) from e
|
|
671
669
|
return None
|
|
672
670
|
|
|
673
671
|
|
|
@@ -733,8 +731,8 @@ def x_prompt_openfile(**kwargs: Any) -> None:
|
|
|
733
731
|
subvarset5.add_substitution(sub_name5, Path(fn).stem)
|
|
734
732
|
except (ErrInfo, SystemExit):
|
|
735
733
|
raise
|
|
736
|
-
except Exception:
|
|
737
|
-
raise ErrInfo(type="exception", exception_msg=exception_desc())
|
|
734
|
+
except Exception as e:
|
|
735
|
+
raise ErrInfo(type="exception", exception_msg=exception_desc()) from e
|
|
738
736
|
return None
|
|
739
737
|
|
|
740
738
|
|
|
@@ -769,8 +767,8 @@ def x_prompt_directory(**kwargs: Any) -> None:
|
|
|
769
767
|
)
|
|
770
768
|
except (ErrInfo, SystemExit):
|
|
771
769
|
raise
|
|
772
|
-
except Exception:
|
|
773
|
-
raise ErrInfo(type="exception", exception_msg=exception_desc())
|
|
770
|
+
except Exception as e:
|
|
771
|
+
raise ErrInfo(type="exception", exception_msg=exception_desc()) from e
|
|
774
772
|
return None
|
|
775
773
|
|
|
776
774
|
|
|
@@ -787,15 +785,15 @@ def prompt_select_rows(button_list: list, **kwargs: Any) -> Any:
|
|
|
787
785
|
if alias1 is not None:
|
|
788
786
|
try:
|
|
789
787
|
db1 = _state.dbs.aliased_as(alias1)
|
|
790
|
-
except Exception:
|
|
791
|
-
raise ErrInfo(type="error", other_msg=badaliasmsg % alias1)
|
|
788
|
+
except Exception as e:
|
|
789
|
+
raise ErrInfo(type="error", other_msg=badaliasmsg % alias1) from e
|
|
792
790
|
else:
|
|
793
791
|
db1 = _state.dbs.current()
|
|
794
792
|
if alias2 is not None:
|
|
795
793
|
try:
|
|
796
794
|
db2 = _state.dbs.aliased_as(alias2)
|
|
797
|
-
except Exception:
|
|
798
|
-
raise ErrInfo(type="error", other_msg=badaliasmsg % alias2)
|
|
795
|
+
except Exception as e:
|
|
796
|
+
raise ErrInfo(type="error", other_msg=badaliasmsg % alias2) from e
|
|
799
797
|
else:
|
|
800
798
|
db2 = _state.dbs.current()
|
|
801
799
|
sq_name1 = db1.schema_qualified_table_name(schema1, table1)
|
|
@@ -942,48 +940,6 @@ def x_pause(**kwargs: Any) -> None:
|
|
|
942
940
|
return None
|
|
943
941
|
|
|
944
942
|
|
|
945
|
-
def x_halt_msg(**kwargs: Any) -> None:
|
|
946
|
-
errmsg = kwargs["errmsg"]
|
|
947
|
-
tee = kwargs["tee"]
|
|
948
|
-
tee = bool(tee)
|
|
949
|
-
outf = kwargs["filename"]
|
|
950
|
-
errlevel = kwargs["errorlevel"]
|
|
951
|
-
if errlevel:
|
|
952
|
-
errlevel = int(errlevel)
|
|
953
|
-
else:
|
|
954
|
-
errlevel = 3
|
|
955
|
-
conf = _state.conf
|
|
956
|
-
if outf:
|
|
957
|
-
check_dir(outf)
|
|
958
|
-
of = EncodedFile(outf, conf.output_encoding).open("a")
|
|
959
|
-
of.write(f"{errmsg}\n")
|
|
960
|
-
of.close()
|
|
961
|
-
schema = kwargs.get("schema")
|
|
962
|
-
table = kwargs.get("table")
|
|
963
|
-
if table:
|
|
964
|
-
db = _state.dbs.current()
|
|
965
|
-
db_obj = db.schema_qualified_table_name(schema, table)
|
|
966
|
-
sql = f"select * from {db_obj};"
|
|
967
|
-
headers, rows = db.select_data(sql)
|
|
968
|
-
else:
|
|
969
|
-
headers, rows = None, None
|
|
970
|
-
enable_gui()
|
|
971
|
-
return_queue = _queue.Queue()
|
|
972
|
-
gui_args = {
|
|
973
|
-
"title": "HALT",
|
|
974
|
-
"message": errmsg,
|
|
975
|
-
"button_list": [("OK", 1, "<Return>")],
|
|
976
|
-
"no_cancel": True,
|
|
977
|
-
"column_headers": headers,
|
|
978
|
-
"rowset": rows,
|
|
979
|
-
"help_url": None,
|
|
980
|
-
}
|
|
981
|
-
_state.gui_manager_queue.put(GuiSpec(GUI_HALT, gui_args, return_queue))
|
|
982
|
-
return_queue.get(block=True)
|
|
983
|
-
_state.exec_log.log_exit_halt(*current_script_line(), msg=errmsg)
|
|
984
|
-
exit_now(errlevel, None)
|
|
985
|
-
|
|
986
|
-
|
|
987
943
|
def x_msg(**kwargs: Any) -> None:
|
|
988
944
|
message = kwargs["message"]
|
|
989
945
|
current_script_line()
|
execsql/metacommands/system.py
CHANGED
|
@@ -89,6 +89,11 @@ def x_log_datavars(**kwargs: Any) -> None:
|
|
|
89
89
|
_state.conf.log_datavars = setting in ("yes", "on", "true", "1")
|
|
90
90
|
|
|
91
91
|
|
|
92
|
+
def x_log_sql(**kwargs: Any) -> None:
|
|
93
|
+
setting = kwargs["setting"].lower()
|
|
94
|
+
_state.conf.log_sql = setting in ("yes", "on", "true", "1")
|
|
95
|
+
|
|
96
|
+
|
|
92
97
|
def x_console(**kwargs: Any) -> None:
|
|
93
98
|
onoff = kwargs["onoff"].lower()
|
|
94
99
|
if onoff == "on":
|
|
@@ -251,6 +256,6 @@ def x_execute(**kwargs: Any) -> None:
|
|
|
251
256
|
db.commit()
|
|
252
257
|
except ErrInfo:
|
|
253
258
|
raise
|
|
254
|
-
except Exception:
|
|
255
|
-
raise ErrInfo("db", command_text=sql, exception_msg=exception_desc())
|
|
259
|
+
except Exception as e:
|
|
260
|
+
raise ErrInfo("db", command_text=sql, exception_msg=exception_desc()) from e
|
|
256
261
|
return None
|
execsql/py.typed
ADDED
|
File without changes
|
execsql/script.py
CHANGED
|
@@ -419,6 +419,7 @@ class MetaCommand:
|
|
|
419
419
|
run_in_batch: bool = False,
|
|
420
420
|
run_when_false: bool = False,
|
|
421
421
|
set_error_flag: bool = True,
|
|
422
|
+
category: str | None = None,
|
|
422
423
|
) -> None:
|
|
423
424
|
self.rx = rx
|
|
424
425
|
self.exec_fn = exec_func
|
|
@@ -426,6 +427,7 @@ class MetaCommand:
|
|
|
426
427
|
self.run_in_batch = run_in_batch
|
|
427
428
|
self.run_when_false = run_when_false
|
|
428
429
|
self.set_error_flag = set_error_flag
|
|
430
|
+
self.category = category
|
|
429
431
|
self.hitcount = 0
|
|
430
432
|
|
|
431
433
|
def __repr__(self) -> str:
|
|
@@ -488,6 +490,7 @@ class MetaCommandList:
|
|
|
488
490
|
run_in_batch: bool = False,
|
|
489
491
|
run_when_false: bool = False,
|
|
490
492
|
set_error_flag: bool = True,
|
|
493
|
+
category: str | None = None,
|
|
491
494
|
) -> None:
|
|
492
495
|
if type(matching_regexes) in (tuple, list):
|
|
493
496
|
regexes = [re.compile(rx, re.I) for rx in tuple(matching_regexes)]
|
|
@@ -497,9 +500,30 @@ class MetaCommandList:
|
|
|
497
500
|
# Prepend to preserve "last registered, first checked" ordering.
|
|
498
501
|
self._commands.insert(
|
|
499
502
|
0,
|
|
500
|
-
MetaCommand(
|
|
503
|
+
MetaCommand(
|
|
504
|
+
rx,
|
|
505
|
+
exec_func,
|
|
506
|
+
description,
|
|
507
|
+
run_in_batch,
|
|
508
|
+
run_when_false,
|
|
509
|
+
set_error_flag,
|
|
510
|
+
category,
|
|
511
|
+
),
|
|
501
512
|
)
|
|
502
513
|
|
|
514
|
+
def keywords_by_category(self) -> dict[str, list[str]]:
|
|
515
|
+
"""Return ``{category: [keyword, ...]}`` from entries that have both.
|
|
516
|
+
|
|
517
|
+
Used by ``--dump-keywords`` to introspect the dispatch table.
|
|
518
|
+
"""
|
|
519
|
+
result: dict[str, list[str]] = {}
|
|
520
|
+
for mc in self._commands:
|
|
521
|
+
if mc.category and mc.description:
|
|
522
|
+
kw_list = result.setdefault(mc.category, [])
|
|
523
|
+
if mc.description not in kw_list:
|
|
524
|
+
kw_list.append(mc.description)
|
|
525
|
+
return result
|
|
526
|
+
|
|
503
527
|
def eval(self, cmd_str: str) -> tuple:
|
|
504
528
|
"""Evaluate *cmd_str* against the registered metacommands.
|
|
505
529
|
|
|
@@ -549,6 +573,10 @@ class SqlStmt:
|
|
|
549
573
|
)
|
|
550
574
|
try:
|
|
551
575
|
db = _state.dbs.current()
|
|
576
|
+
if _state.conf.log_sql and _state.exec_log:
|
|
577
|
+
lno = getattr(_state, "last_command", None)
|
|
578
|
+
lno = lno.line_no if lno and hasattr(lno, "line_no") else None
|
|
579
|
+
_state.exec_log.log_sql_query(cmd, db.name(), lno)
|
|
552
580
|
db.execute(cmd)
|
|
553
581
|
if commit:
|
|
554
582
|
db.commit()
|
|
@@ -915,6 +943,9 @@ def set_system_vars() -> None:
|
|
|
915
943
|
_state.subvars.add_substitution("$VERSION3", str(_state.tertiary_vno))
|
|
916
944
|
|
|
917
945
|
|
|
946
|
+
_MAX_SUBSTITUTION_DEPTH = 100
|
|
947
|
+
|
|
948
|
+
|
|
918
949
|
def substitute_vars(command_str: str, localvars: SubVarSet | None = None) -> str:
|
|
919
950
|
# Substitutes global variables, global counters, and local variables.
|
|
920
951
|
if localvars is not None:
|
|
@@ -923,11 +954,21 @@ def substitute_vars(command_str: str, localvars: SubVarSet | None = None) -> str
|
|
|
923
954
|
subs = _state.subvars
|
|
924
955
|
cmdstr = copy.copy(command_str)
|
|
925
956
|
subs_made = True
|
|
957
|
+
iterations = 0
|
|
926
958
|
while subs_made:
|
|
927
959
|
subs_made = False
|
|
928
960
|
cmdstr, subs_made = subs.substitute_all(cmdstr)
|
|
929
961
|
cmdstr, any_subbed = _state.counters.substitute_all(cmdstr)
|
|
930
962
|
subs_made = subs_made or any_subbed
|
|
963
|
+
iterations += 1
|
|
964
|
+
if iterations >= _MAX_SUBSTITUTION_DEPTH:
|
|
965
|
+
raise ErrInfo(
|
|
966
|
+
type="error",
|
|
967
|
+
other_msg=(
|
|
968
|
+
f"Substitution variable cycle detected: exceeded {_MAX_SUBSTITUTION_DEPTH} "
|
|
969
|
+
f"iterations while expanding variables in: {command_str[:200]}"
|
|
970
|
+
),
|
|
971
|
+
)
|
|
931
972
|
m = _state.defer_rx.findall(cmdstr)
|
|
932
973
|
# Substitute any deferred substitution variables with regular substitution var flags.
|
|
933
974
|
if m is not None:
|
|
@@ -952,8 +993,8 @@ def runscripts() -> None:
|
|
|
952
993
|
raise
|
|
953
994
|
except ErrInfo:
|
|
954
995
|
raise
|
|
955
|
-
except Exception:
|
|
956
|
-
raise ErrInfo(type="exception", exception_msg=exception_desc())
|
|
996
|
+
except Exception as e:
|
|
997
|
+
raise ErrInfo(type="exception", exception_msg=exception_desc()) from e
|
|
957
998
|
_state.cmds_run += 1
|
|
958
999
|
|
|
959
1000
|
|
|
@@ -1126,8 +1167,11 @@ def read_sqlfile(sql_file_name: str) -> None:
|
|
|
1126
1167
|
|
|
1127
1168
|
sz, dt = file_size_date(sql_file_name)
|
|
1128
1169
|
_state.exec_log.log_status_info(f"Reading script file {sql_file_name} (size: {sz}; date: {dt})")
|
|
1129
|
-
scriptfile_obj = ScriptFile(sql_file_name, _state.conf.script_encoding)
|
|
1130
|
-
|
|
1170
|
+
scriptfile_obj = ScriptFile(sql_file_name, _state.conf.script_encoding)
|
|
1171
|
+
try:
|
|
1172
|
+
sqllist = _parse_script_lines(scriptfile_obj, sql_file_name)
|
|
1173
|
+
finally:
|
|
1174
|
+
scriptfile_obj.close()
|
|
1131
1175
|
if sqllist:
|
|
1132
1176
|
_state.commandliststack.append(CommandList(sqllist, Path(sql_file_name).name))
|
|
1133
1177
|
|