execsql2 2.15.8__py3-none-any.whl → 2.16.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/__init__.py +8 -3
- execsql/api.py +580 -0
- execsql/cli/__init__.py +123 -0
- execsql/cli/lint_ast.py +439 -0
- execsql/cli/run.py +113 -102
- execsql/config.py +29 -4
- execsql/db/access.py +1 -0
- execsql/db/base.py +4 -1
- execsql/db/dsn.py +3 -2
- execsql/db/duckdb.py +1 -1
- execsql/db/factory.py +3 -0
- execsql/db/firebird.py +2 -1
- execsql/db/mysql.py +2 -1
- execsql/db/oracle.py +2 -1
- execsql/db/postgres.py +2 -1
- execsql/db/sqlite.py +1 -1
- execsql/db/sqlserver.py +3 -2
- execsql/debug/repl.py +27 -10
- execsql/exporters/base.py +6 -4
- execsql/exporters/delimited.py +11 -3
- execsql/exporters/pretty.py +9 -12
- execsql/gui/tui.py +59 -2
- execsql/metacommands/__init__.py +3 -0
- execsql/metacommands/conditions.py +20 -2
- execsql/metacommands/connect.py +1 -1
- execsql/metacommands/control.py +8 -14
- execsql/metacommands/debug.py +6 -4
- execsql/metacommands/io_export.py +117 -315
- execsql/metacommands/io_fileops.py +7 -13
- execsql/metacommands/io_write.py +1 -1
- execsql/metacommands/script_ext.py +8 -5
- execsql/metacommands/upsert.py +40 -0
- execsql/models.py +8 -12
- execsql/plugins.py +414 -0
- execsql/script/__init__.py +36 -12
- execsql/script/ast.py +562 -0
- execsql/script/engine.py +59 -368
- execsql/script/executor.py +833 -0
- execsql/script/parser.py +663 -0
- execsql/script/variables.py +11 -0
- execsql/state.py +55 -2
- execsql/utils/crypto.py +14 -10
- execsql/utils/errors.py +31 -8
- execsql/utils/gui.py +139 -17
- execsql/utils/mail.py +15 -12
- {execsql2-2.15.8.dist-info → execsql2-2.16.0.dist-info}/METADATA +59 -1
- {execsql2-2.15.8.dist-info → execsql2-2.16.0.dist-info}/RECORD +66 -60
- {execsql2-2.15.8.data → execsql2-2.16.0.data}/data/execsql2_extras/README.md +0 -0
- {execsql2-2.15.8.data → execsql2-2.16.0.data}/data/execsql2_extras/config_settings.sqlite +0 -0
- {execsql2-2.15.8.data → execsql2-2.16.0.data}/data/execsql2_extras/example_config_prompt.sql +0 -0
- {execsql2-2.15.8.data → execsql2-2.16.0.data}/data/execsql2_extras/execsql.conf +0 -0
- {execsql2-2.15.8.data → execsql2-2.16.0.data}/data/execsql2_extras/make_config_db.sql +0 -0
- {execsql2-2.15.8.data → execsql2-2.16.0.data}/data/execsql2_extras/md_compare.sql +0 -0
- {execsql2-2.15.8.data → execsql2-2.16.0.data}/data/execsql2_extras/md_glossary.sql +0 -0
- {execsql2-2.15.8.data → execsql2-2.16.0.data}/data/execsql2_extras/md_upsert.sql +0 -0
- {execsql2-2.15.8.data → execsql2-2.16.0.data}/data/execsql2_extras/pg_compare.sql +0 -0
- {execsql2-2.15.8.data → execsql2-2.16.0.data}/data/execsql2_extras/pg_glossary.sql +0 -0
- {execsql2-2.15.8.data → execsql2-2.16.0.data}/data/execsql2_extras/pg_upsert.sql +0 -0
- {execsql2-2.15.8.data → execsql2-2.16.0.data}/data/execsql2_extras/script_template.sql +0 -0
- {execsql2-2.15.8.data → execsql2-2.16.0.data}/data/execsql2_extras/ss_compare.sql +0 -0
- {execsql2-2.15.8.data → execsql2-2.16.0.data}/data/execsql2_extras/ss_glossary.sql +0 -0
- {execsql2-2.15.8.data → execsql2-2.16.0.data}/data/execsql2_extras/ss_upsert.sql +0 -0
- {execsql2-2.15.8.dist-info → execsql2-2.16.0.dist-info}/WHEEL +0 -0
- {execsql2-2.15.8.dist-info → execsql2-2.16.0.dist-info}/entry_points.txt +0 -0
- {execsql2-2.15.8.dist-info → execsql2-2.16.0.dist-info}/licenses/LICENSE.txt +0 -0
- {execsql2-2.15.8.dist-info → execsql2-2.16.0.dist-info}/licenses/NOTICE +0 -0
|
@@ -56,190 +56,132 @@ def _apply_output_dir(path: str) -> str:
|
|
|
56
56
|
return str(Path(output_dir) / path)
|
|
57
57
|
|
|
58
58
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
59
|
+
# ---------------------------------------------------------------------------
|
|
60
|
+
# Shared format-dispatch logic
|
|
61
|
+
# ---------------------------------------------------------------------------
|
|
62
|
+
|
|
63
|
+
# Formats that cannot be written into a zipfile.
|
|
64
|
+
_NO_ZIP_FORMATS = frozenset({"duckdb", "sqlite", "latex", "feather", "parquet", "hdf5", "ods", "xlsx"})
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _check_zip_compat(outfile: str, filefmt: str, zipfilename: str | None) -> None:
|
|
68
|
+
"""Raise if the format/outfile combination is incompatible with zip output."""
|
|
69
|
+
if zipfilename is None:
|
|
70
|
+
return
|
|
71
|
+
if outfile.lower() == "stdout":
|
|
72
|
+
raise ErrInfo("error", other_msg="Cannot write stdout to a zipfile.")
|
|
73
|
+
if len(outfile) > 1 and outfile[1] == ":":
|
|
74
|
+
raise ErrInfo("error", other_msg="Cannot use a drive letter for a file path within a zipfile.")
|
|
75
|
+
if filefmt in _NO_ZIP_FORMATS:
|
|
76
|
+
raise ErrInfo("error", other_msg=f"Cannot export to the {filefmt} format within a zipfile.")
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _dispatch_format(
|
|
80
|
+
select_stmt: str,
|
|
81
|
+
outfile: str,
|
|
82
|
+
filefmt: str,
|
|
83
|
+
append: bool,
|
|
84
|
+
*,
|
|
85
|
+
description: str | None = None,
|
|
86
|
+
zipfilename: str | None = None,
|
|
87
|
+
notype: bool = False,
|
|
88
|
+
sheetname: str | None = None,
|
|
89
|
+
tablename: str | None = None,
|
|
90
|
+
xml_table: str | None = None,
|
|
91
|
+
hdf5_table: str | None = None,
|
|
92
|
+
) -> None:
|
|
93
|
+
"""Execute the appropriate exporter for *filefmt*.
|
|
94
|
+
|
|
95
|
+
All format-specific parameters that differ between ``x_export`` and
|
|
96
|
+
``x_export_query`` are passed explicitly rather than duplicating the
|
|
97
|
+
dispatch chain.
|
|
98
|
+
"""
|
|
99
|
+
db = _state.dbs.current()
|
|
100
100
|
if filefmt in ("txt", "text"):
|
|
101
|
-
prettyprint_query(
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
outfile,
|
|
105
|
-
append,
|
|
106
|
-
desc=description,
|
|
107
|
-
zipfile=zipfilename,
|
|
108
|
-
)
|
|
109
|
-
elif filefmt in ("txt-and", "text-and", "txt-and", "text-and"):
|
|
110
|
-
prettyprint_query(
|
|
111
|
-
select_stmt,
|
|
112
|
-
_state.dbs.current(),
|
|
113
|
-
outfile,
|
|
114
|
-
append,
|
|
115
|
-
and_val="AND",
|
|
116
|
-
desc=description,
|
|
117
|
-
zipfile=zipfilename,
|
|
118
|
-
)
|
|
101
|
+
prettyprint_query(select_stmt, db, outfile, append, desc=description, zipfile=zipfilename)
|
|
102
|
+
elif filefmt in ("txt-and", "text-and"):
|
|
103
|
+
prettyprint_query(select_stmt, db, outfile, append, and_val="AND", desc=description, zipfile=zipfilename)
|
|
119
104
|
elif filefmt == "ods":
|
|
120
|
-
write_query_to_ods(
|
|
121
|
-
select_stmt,
|
|
122
|
-
_state.dbs.current(),
|
|
123
|
-
outfile,
|
|
124
|
-
append,
|
|
125
|
-
sheetname=queryname,
|
|
126
|
-
desc=description,
|
|
127
|
-
)
|
|
105
|
+
write_query_to_ods(select_stmt, db, outfile, append, sheetname=sheetname, desc=description)
|
|
128
106
|
elif filefmt == "xlsx":
|
|
129
|
-
write_query_to_xlsx(
|
|
130
|
-
select_stmt,
|
|
131
|
-
_state.dbs.current(),
|
|
132
|
-
outfile,
|
|
133
|
-
append,
|
|
134
|
-
sheetname=queryname,
|
|
135
|
-
desc=description,
|
|
136
|
-
)
|
|
107
|
+
write_query_to_xlsx(select_stmt, db, outfile, append, sheetname=sheetname, desc=description)
|
|
137
108
|
elif filefmt == "duckdb":
|
|
138
|
-
write_query_to_duckdb(select_stmt,
|
|
109
|
+
write_query_to_duckdb(select_stmt, db, outfile, append, tablename=tablename)
|
|
139
110
|
elif filefmt == "sqlite":
|
|
140
|
-
write_query_to_sqlite(select_stmt,
|
|
111
|
+
write_query_to_sqlite(select_stmt, db, outfile, append, tablename=tablename)
|
|
141
112
|
elif filefmt == "xml":
|
|
142
|
-
write_query_to_xml(
|
|
143
|
-
select_stmt,
|
|
144
|
-
table,
|
|
145
|
-
_state.dbs.current(),
|
|
146
|
-
outfile,
|
|
147
|
-
append,
|
|
148
|
-
desc=description,
|
|
149
|
-
zipfile=zipfilename,
|
|
150
|
-
)
|
|
113
|
+
write_query_to_xml(select_stmt, xml_table, db, outfile, append, desc=description, zipfile=zipfilename)
|
|
151
114
|
elif filefmt == "json":
|
|
152
|
-
write_query_to_json(
|
|
153
|
-
select_stmt,
|
|
154
|
-
_state.dbs.current(),
|
|
155
|
-
outfile,
|
|
156
|
-
append,
|
|
157
|
-
desc=description,
|
|
158
|
-
zipfile=zipfilename,
|
|
159
|
-
)
|
|
115
|
+
write_query_to_json(select_stmt, db, outfile, append, desc=description, zipfile=zipfilename)
|
|
160
116
|
elif filefmt in ("json_ts", "json_tableschema"):
|
|
161
|
-
write_query_to_json_ts(
|
|
162
|
-
select_stmt,
|
|
163
|
-
_state.dbs.current(),
|
|
164
|
-
outfile,
|
|
165
|
-
append,
|
|
166
|
-
not notype,
|
|
167
|
-
desc=description,
|
|
168
|
-
zipfile=zipfilename,
|
|
169
|
-
)
|
|
117
|
+
write_query_to_json_ts(select_stmt, db, outfile, append, not notype, desc=description, zipfile=zipfilename)
|
|
170
118
|
elif filefmt == "values":
|
|
171
|
-
write_query_to_values(
|
|
172
|
-
select_stmt,
|
|
173
|
-
_state.dbs.current(),
|
|
174
|
-
outfile,
|
|
175
|
-
append,
|
|
176
|
-
desc=description,
|
|
177
|
-
zipfile=zipfilename,
|
|
178
|
-
)
|
|
119
|
+
write_query_to_values(select_stmt, db, outfile, append, desc=description, zipfile=zipfilename)
|
|
179
120
|
elif filefmt == "html":
|
|
180
|
-
write_query_to_html(
|
|
181
|
-
select_stmt,
|
|
182
|
-
_state.dbs.current(),
|
|
183
|
-
outfile,
|
|
184
|
-
append,
|
|
185
|
-
desc=description,
|
|
186
|
-
zipfile=zipfilename,
|
|
187
|
-
)
|
|
121
|
+
write_query_to_html(select_stmt, db, outfile, append, desc=description, zipfile=zipfilename)
|
|
188
122
|
elif filefmt == "cgi-html":
|
|
189
|
-
write_query_to_cgi_html(
|
|
190
|
-
select_stmt,
|
|
191
|
-
_state.dbs.current(),
|
|
192
|
-
outfile,
|
|
193
|
-
append,
|
|
194
|
-
desc=description,
|
|
195
|
-
zipfile=zipfilename,
|
|
196
|
-
)
|
|
123
|
+
write_query_to_cgi_html(select_stmt, db, outfile, append, desc=description, zipfile=zipfilename)
|
|
197
124
|
elif filefmt == "latex":
|
|
198
|
-
write_query_to_latex(
|
|
199
|
-
select_stmt,
|
|
200
|
-
_state.dbs.current(),
|
|
201
|
-
outfile,
|
|
202
|
-
append,
|
|
203
|
-
desc=description,
|
|
204
|
-
zipfile=zipfilename,
|
|
205
|
-
)
|
|
125
|
+
write_query_to_latex(select_stmt, db, outfile, append, desc=description, zipfile=zipfilename)
|
|
206
126
|
elif filefmt == "hdf5":
|
|
207
|
-
write_query_to_hdf5(
|
|
127
|
+
write_query_to_hdf5(hdf5_table, select_stmt, db, outfile, append, desc=description)
|
|
208
128
|
elif filefmt == "yaml":
|
|
209
|
-
write_query_to_yaml(
|
|
210
|
-
select_stmt,
|
|
211
|
-
_state.dbs.current(),
|
|
212
|
-
outfile,
|
|
213
|
-
append,
|
|
214
|
-
desc=description,
|
|
215
|
-
zipfile=zipfilename,
|
|
216
|
-
)
|
|
129
|
+
write_query_to_yaml(select_stmt, db, outfile, append, desc=description, zipfile=zipfilename)
|
|
217
130
|
elif filefmt in ("markdown", "md"):
|
|
218
|
-
write_query_to_markdown(
|
|
219
|
-
select_stmt,
|
|
220
|
-
_state.dbs.current(),
|
|
221
|
-
outfile,
|
|
222
|
-
append,
|
|
223
|
-
desc=description,
|
|
224
|
-
zipfile=zipfilename,
|
|
225
|
-
)
|
|
131
|
+
write_query_to_markdown(select_stmt, db, outfile, append, desc=description, zipfile=zipfilename)
|
|
226
132
|
else:
|
|
227
133
|
try:
|
|
228
|
-
hdrs, rows =
|
|
134
|
+
hdrs, rows = db.select_rowsource(select_stmt)
|
|
229
135
|
except ErrInfo:
|
|
230
136
|
raise
|
|
231
137
|
except Exception as e:
|
|
232
138
|
raise ErrInfo("db", select_stmt, exception_msg=exception_desc()) from e
|
|
233
139
|
if filefmt == "raw":
|
|
234
|
-
write_query_raw(outfile, rows,
|
|
140
|
+
write_query_raw(outfile, rows, db.encoding, append, zipfile=zipfilename)
|
|
235
141
|
elif filefmt == "b64":
|
|
236
|
-
write_query_b64(outfile, rows, append)
|
|
142
|
+
write_query_b64(outfile, rows, append, zipfile=zipfilename)
|
|
237
143
|
elif filefmt == "feather":
|
|
238
144
|
write_query_to_feather(outfile, hdrs, rows)
|
|
239
145
|
elif filefmt == "parquet":
|
|
240
146
|
write_query_to_parquet(outfile, hdrs, rows)
|
|
241
147
|
else:
|
|
242
148
|
write_delimited_file(outfile, filefmt, hdrs, rows, _state.conf.output_encoding, append, zipfilename)
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
# ---------------------------------------------------------------------------
|
|
152
|
+
# EXPORT <table> TO <format> <file>
|
|
153
|
+
# ---------------------------------------------------------------------------
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def x_export(**kwargs: Any) -> None:
|
|
157
|
+
schema = kwargs["schema"]
|
|
158
|
+
table = kwargs["table"]
|
|
159
|
+
queryname = _state.dbs.current().schema_qualified_table_name(schema, table)
|
|
160
|
+
select_stmt = f"select * from {queryname};"
|
|
161
|
+
outfile = _apply_output_dir(kwargs["filename"])
|
|
162
|
+
description = kwargs["description"]
|
|
163
|
+
tee = bool(kwargs["tee"])
|
|
164
|
+
append = bool(kwargs["append"])
|
|
165
|
+
filefmt = kwargs["format"].lower()
|
|
166
|
+
zipfilename = _apply_output_dir(kwargs["zipfilename"]) if kwargs["zipfilename"] else None
|
|
167
|
+
notype = bool(kwargs.get("notype"))
|
|
168
|
+
_check_zip_compat(outfile, filefmt, zipfilename)
|
|
169
|
+
check_dir(zipfilename if zipfilename is not None else outfile)
|
|
170
|
+
if tee and outfile.lower() != "stdout":
|
|
171
|
+
prettyprint_query(select_stmt, _state.dbs.current(), "stdout", False, desc=description)
|
|
172
|
+
_dispatch_format(
|
|
173
|
+
select_stmt,
|
|
174
|
+
outfile,
|
|
175
|
+
filefmt,
|
|
176
|
+
append,
|
|
177
|
+
description=description,
|
|
178
|
+
zipfilename=zipfilename,
|
|
179
|
+
notype=notype,
|
|
180
|
+
sheetname=queryname,
|
|
181
|
+
tablename=queryname,
|
|
182
|
+
xml_table=table,
|
|
183
|
+
hdf5_table=table,
|
|
184
|
+
)
|
|
243
185
|
_state.export_metadata.add(ExportRecord(queryname, outfile, zipfilename, description))
|
|
244
186
|
if _state.exec_log:
|
|
245
187
|
_, line_no = current_script_line()
|
|
@@ -247,178 +189,38 @@ def x_export(**kwargs: Any) -> None:
|
|
|
247
189
|
return None
|
|
248
190
|
|
|
249
191
|
|
|
192
|
+
# ---------------------------------------------------------------------------
|
|
193
|
+
# EXPORT QUERY <sql> TO <format> <file>
|
|
194
|
+
# ---------------------------------------------------------------------------
|
|
195
|
+
|
|
196
|
+
|
|
250
197
|
def x_export_query(**kwargs: Any) -> None:
|
|
251
198
|
select_stmt = kwargs["query"]
|
|
252
199
|
outfile = kwargs["filename"]
|
|
253
200
|
description = kwargs["description"]
|
|
254
|
-
tee = kwargs["tee"]
|
|
255
|
-
|
|
256
|
-
append = kwargs["append"]
|
|
257
|
-
append = bool(append)
|
|
201
|
+
tee = bool(kwargs["tee"])
|
|
202
|
+
append = bool(kwargs["append"])
|
|
258
203
|
filefmt = kwargs["format"].lower()
|
|
259
204
|
zipfilename = kwargs["zipfilename"]
|
|
260
|
-
if zipfilename is not None:
|
|
261
|
-
if outfile == "stdout":
|
|
262
|
-
raise ErrInfo("error", other_msg="Cannot write stdout to a zipfile.")
|
|
263
|
-
elif len(outfile) > 1 and outfile[1] == ":":
|
|
264
|
-
raise ErrInfo("error", other_msg="Cannot use a drive letter for a file path within a zipfile.")
|
|
265
|
-
if filefmt == "latex":
|
|
266
|
-
raise ErrInfo("error", other_msg="Cannot export to the LaTeX format within a zipfile.")
|
|
267
|
-
if filefmt == "feather":
|
|
268
|
-
raise ErrInfo("error", other_msg="Cannot export to the feather format within a zipfile.")
|
|
269
|
-
if filefmt == "parquet":
|
|
270
|
-
raise ErrInfo("error", other_msg="Cannot export to the parquet format within a zipfile.")
|
|
271
|
-
if filefmt == "hdf5":
|
|
272
|
-
raise ErrInfo("error", other_msg="Cannot export to the HDF5 format within a zipfile.")
|
|
273
|
-
if filefmt == "ods":
|
|
274
|
-
raise ErrInfo("error", other_msg="Cannot export to an ODS workbook within a zipfile.")
|
|
275
|
-
if filefmt == "xlsx":
|
|
276
|
-
raise ErrInfo("error", other_msg="Cannot export to an XLSX workbook within a zipfile.")
|
|
277
205
|
notype = bool(kwargs.get("notype"))
|
|
206
|
+
_check_zip_compat(outfile, filefmt, zipfilename)
|
|
278
207
|
check_dir(outfile)
|
|
279
208
|
if tee and outfile.lower() != "stdout":
|
|
280
209
|
prettyprint_query(select_stmt, _state.dbs.current(), "stdout", False, desc=description)
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
select_stmt,
|
|
293
|
-
_state.dbs.current(),
|
|
294
|
-
outfile,
|
|
295
|
-
append,
|
|
296
|
-
and_val="AND",
|
|
297
|
-
desc=description,
|
|
298
|
-
zipfile=zipfilename,
|
|
299
|
-
)
|
|
300
|
-
elif filefmt == "ods":
|
|
301
|
-
script_name, lno = current_script_line()
|
|
302
|
-
write_query_to_ods(
|
|
303
|
-
select_stmt,
|
|
304
|
-
_state.dbs.current(),
|
|
305
|
-
outfile,
|
|
306
|
-
append,
|
|
307
|
-
sheetname=f"Query_{lno}",
|
|
308
|
-
desc=description,
|
|
309
|
-
)
|
|
310
|
-
elif filefmt == "xlsx":
|
|
311
|
-
script_name, lno = current_script_line()
|
|
312
|
-
write_query_to_xlsx(
|
|
313
|
-
select_stmt,
|
|
314
|
-
_state.dbs.current(),
|
|
315
|
-
outfile,
|
|
316
|
-
append,
|
|
317
|
-
sheetname=f"Query_{lno}",
|
|
318
|
-
desc=description,
|
|
319
|
-
)
|
|
320
|
-
elif filefmt == "json":
|
|
321
|
-
write_query_to_json(
|
|
322
|
-
select_stmt,
|
|
323
|
-
_state.dbs.current(),
|
|
324
|
-
outfile,
|
|
325
|
-
append,
|
|
326
|
-
desc=description,
|
|
327
|
-
zipfile=zipfilename,
|
|
328
|
-
)
|
|
329
|
-
elif filefmt in ("json_ts", "json_tableschema"):
|
|
330
|
-
write_query_to_json_ts(
|
|
331
|
-
select_stmt,
|
|
332
|
-
_state.dbs.current(),
|
|
333
|
-
outfile,
|
|
334
|
-
append,
|
|
335
|
-
not notype,
|
|
336
|
-
desc=description,
|
|
337
|
-
zipfile=zipfilename,
|
|
338
|
-
)
|
|
339
|
-
elif filefmt == "values":
|
|
340
|
-
write_query_to_values(
|
|
341
|
-
select_stmt,
|
|
342
|
-
_state.dbs.current(),
|
|
343
|
-
outfile,
|
|
344
|
-
append,
|
|
345
|
-
desc=description,
|
|
346
|
-
zipfile=zipfilename,
|
|
347
|
-
)
|
|
348
|
-
elif filefmt == "html":
|
|
349
|
-
write_query_to_html(
|
|
350
|
-
select_stmt,
|
|
351
|
-
_state.dbs.current(),
|
|
352
|
-
outfile,
|
|
353
|
-
append,
|
|
354
|
-
desc=description,
|
|
355
|
-
zipfile=zipfilename,
|
|
356
|
-
)
|
|
357
|
-
elif filefmt == "cgi-html":
|
|
358
|
-
write_query_to_cgi_html(
|
|
359
|
-
select_stmt,
|
|
360
|
-
_state.dbs.current(),
|
|
361
|
-
outfile,
|
|
362
|
-
append,
|
|
363
|
-
desc=description,
|
|
364
|
-
zipfile=zipfilename,
|
|
365
|
-
)
|
|
366
|
-
elif filefmt == "latex":
|
|
367
|
-
write_query_to_latex(
|
|
368
|
-
select_stmt,
|
|
369
|
-
_state.dbs.current(),
|
|
370
|
-
outfile,
|
|
371
|
-
append,
|
|
372
|
-
desc=description,
|
|
373
|
-
zipfile=zipfilename,
|
|
374
|
-
)
|
|
375
|
-
elif filefmt == "yaml":
|
|
376
|
-
write_query_to_yaml(
|
|
377
|
-
select_stmt,
|
|
378
|
-
_state.dbs.current(),
|
|
379
|
-
outfile,
|
|
380
|
-
append,
|
|
381
|
-
desc=description,
|
|
382
|
-
zipfile=zipfilename,
|
|
383
|
-
)
|
|
384
|
-
elif filefmt in ("markdown", "md"):
|
|
385
|
-
write_query_to_markdown(
|
|
386
|
-
select_stmt,
|
|
387
|
-
_state.dbs.current(),
|
|
388
|
-
outfile,
|
|
389
|
-
append,
|
|
390
|
-
desc=description,
|
|
391
|
-
zipfile=zipfilename,
|
|
392
|
-
)
|
|
393
|
-
else:
|
|
394
|
-
try:
|
|
395
|
-
hdrs, rows = _state.dbs.current().select_rowsource(select_stmt)
|
|
396
|
-
except ErrInfo:
|
|
397
|
-
raise
|
|
398
|
-
except Exception as e:
|
|
399
|
-
raise ErrInfo("db", select_stmt, exception_msg=exception_desc()) from e
|
|
400
|
-
if filefmt == "raw":
|
|
401
|
-
write_query_raw(outfile, rows, _state.dbs.current().encoding, append, zipfile=zipfilename)
|
|
402
|
-
elif filefmt == "b64":
|
|
403
|
-
write_query_b64(outfile, rows, append, zipfile=zipfilename)
|
|
404
|
-
elif filefmt == "feather":
|
|
405
|
-
write_query_to_feather(outfile, hdrs, rows)
|
|
406
|
-
elif filefmt == "parquet":
|
|
407
|
-
write_query_to_parquet(outfile, hdrs, rows)
|
|
408
|
-
else:
|
|
409
|
-
write_delimited_file(
|
|
410
|
-
outfile,
|
|
411
|
-
filefmt,
|
|
412
|
-
hdrs,
|
|
413
|
-
rows,
|
|
414
|
-
_state.conf.output_encoding,
|
|
415
|
-
append,
|
|
416
|
-
zipfile=zipfilename,
|
|
417
|
-
)
|
|
210
|
+
_, lno = current_script_line()
|
|
211
|
+
_dispatch_format(
|
|
212
|
+
select_stmt,
|
|
213
|
+
outfile,
|
|
214
|
+
filefmt,
|
|
215
|
+
append,
|
|
216
|
+
description=description,
|
|
217
|
+
zipfilename=zipfilename,
|
|
218
|
+
notype=notype,
|
|
219
|
+
sheetname=f"Query_{lno}",
|
|
220
|
+
)
|
|
418
221
|
_state.export_metadata.add(ExportRecord(select_stmt, outfile, zipfilename, description))
|
|
419
222
|
if _state.exec_log:
|
|
420
|
-
|
|
421
|
-
_state.exec_log.log_action_export(line_no, select_stmt[:80], outfile)
|
|
223
|
+
_state.exec_log.log_action_export(lno, select_stmt[:80], outfile)
|
|
422
224
|
return None
|
|
423
225
|
|
|
424
226
|
|
|
@@ -16,7 +16,7 @@ from typing import Any
|
|
|
16
16
|
import execsql.state as _state
|
|
17
17
|
from execsql.exceptions import ErrInfo
|
|
18
18
|
from execsql.models import DataTable
|
|
19
|
-
from execsql.script import current_script_line
|
|
19
|
+
from execsql.script import current_script_line
|
|
20
20
|
from execsql.types import dbt_firebird
|
|
21
21
|
from execsql.utils.errors import exception_desc
|
|
22
22
|
from execsql.utils.fileio import filewriter_close
|
|
@@ -24,18 +24,12 @@ from execsql.utils.strings import unquoted
|
|
|
24
24
|
|
|
25
25
|
|
|
26
26
|
def x_include(**kwargs: Any) -> None:
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
read_sqlfile(filename)
|
|
34
|
-
else:
|
|
35
|
-
if not Path(filename).is_file():
|
|
36
|
-
raise ErrInfo(type="error", other_msg=f"File {filename} does not exist.")
|
|
37
|
-
read_sqlfile(filename)
|
|
38
|
-
return None
|
|
27
|
+
# INCLUDE is now handled natively by the AST executor (_execute_include_native).
|
|
28
|
+
# This handler exists only for dispatch table registration compatibility.
|
|
29
|
+
raise ErrInfo(
|
|
30
|
+
type="cmd",
|
|
31
|
+
other_msg="INCLUDE should be handled by the AST executor, not the dispatch table.",
|
|
32
|
+
)
|
|
39
33
|
|
|
40
34
|
|
|
41
35
|
def x_copy(**kwargs: Any) -> None:
|
execsql/metacommands/io_write.py
CHANGED
|
@@ -45,7 +45,7 @@ def x_write(**kwargs: Any) -> None:
|
|
|
45
45
|
except ConsoleUIError as e:
|
|
46
46
|
_state.output.reset()
|
|
47
47
|
_state.exec_log.log_status_info(f"Console UI write failed (message {{{e.value}}}); output reset to stdout.")
|
|
48
|
-
_state.output.write(msg
|
|
48
|
+
_state.output.write(msg)
|
|
49
49
|
if _state.conf.tee_write_log:
|
|
50
50
|
_state.exec_log.log_user_msg(msg)
|
|
51
51
|
return None
|
|
@@ -14,7 +14,7 @@ from typing import Any
|
|
|
14
14
|
|
|
15
15
|
import execsql.state as _state
|
|
16
16
|
from execsql.exceptions import ErrInfo
|
|
17
|
-
from execsql.script import MetacommandStmt, ScriptCmd,
|
|
17
|
+
from execsql.script import MetacommandStmt, ScriptCmd, SqlStmt, current_script_line
|
|
18
18
|
|
|
19
19
|
|
|
20
20
|
def x_extendscript(**kwargs: Any) -> None:
|
|
@@ -57,7 +57,10 @@ def x_extendscript_sql(**kwargs: Any) -> None:
|
|
|
57
57
|
|
|
58
58
|
|
|
59
59
|
def x_executescript(**kwargs: Any) -> None:
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
60
|
+
# EXECUTE SCRIPT is now handled natively by the AST executor
|
|
61
|
+
# (_execute_include / _execute_script_native). This handler exists only
|
|
62
|
+
# for dispatch table registration compatibility.
|
|
63
|
+
raise ErrInfo(
|
|
64
|
+
"cmd",
|
|
65
|
+
other_msg="EXECUTE SCRIPT should be handled by the AST executor, not the dispatch table.",
|
|
66
|
+
)
|
execsql/metacommands/upsert.py
CHANGED
|
@@ -573,3 +573,43 @@ def x_pg_upsert_check(**kwargs: Any) -> None:
|
|
|
573
573
|
command_text=metacommandline,
|
|
574
574
|
other_msg=_qa_failure_msg(result),
|
|
575
575
|
)
|
|
576
|
+
|
|
577
|
+
|
|
578
|
+
# ---------------------------------------------------------------------------
|
|
579
|
+
# Plugin registration
|
|
580
|
+
# ---------------------------------------------------------------------------
|
|
581
|
+
|
|
582
|
+
_PG_UPSERT_RX = r"^\s*PG_UPSERT\s+FROM\s+(?P<staging_schema>\S+)\s+TO\s+(?P<base_schema>\S+)\s+TABLES\s+(?P<tail>.+)$"
|
|
583
|
+
_PG_UPSERT_CHECK_RX = (
|
|
584
|
+
r"^\s*PG_UPSERT\s+CHECK\s+FROM\s+(?P<staging_schema>\S+)\s+TO\s+(?P<base_schema>\S+)\s+TABLES\s+(?P<tail>.+)$"
|
|
585
|
+
)
|
|
586
|
+
_PG_UPSERT_QA_RX = (
|
|
587
|
+
r"^\s*PG_UPSERT\s+QA\s+FROM\s+(?P<staging_schema>\S+)\s+TO\s+(?P<base_schema>\S+)\s+TABLES\s+(?P<tail>.+)$"
|
|
588
|
+
)
|
|
589
|
+
|
|
590
|
+
|
|
591
|
+
def register(mcl: Any) -> None:
|
|
592
|
+
"""Register PG_UPSERT metacommands as a plugin.
|
|
593
|
+
|
|
594
|
+
Called by execsql's plugin discovery via the ``execsql.metacommands``
|
|
595
|
+
entry point. The CHECK and QA variants are registered first so their
|
|
596
|
+
more-specific regexes take priority over the general form.
|
|
597
|
+
"""
|
|
598
|
+
mcl.add(
|
|
599
|
+
_PG_UPSERT_CHECK_RX,
|
|
600
|
+
x_pg_upsert_check,
|
|
601
|
+
description="PG_UPSERT CHECK",
|
|
602
|
+
category="action",
|
|
603
|
+
)
|
|
604
|
+
mcl.add(
|
|
605
|
+
_PG_UPSERT_QA_RX,
|
|
606
|
+
x_pg_upsert_qa,
|
|
607
|
+
description="PG_UPSERT QA",
|
|
608
|
+
category="action",
|
|
609
|
+
)
|
|
610
|
+
mcl.add(
|
|
611
|
+
_PG_UPSERT_RX,
|
|
612
|
+
x_pg_upsert,
|
|
613
|
+
description="PG_UPSERT",
|
|
614
|
+
category="action",
|
|
615
|
+
)
|
execsql/models.py
CHANGED
|
@@ -307,20 +307,16 @@ class DataTable:
|
|
|
307
307
|
class JsonDatatype:
|
|
308
308
|
"""Namespace mapping Python DataType subclasses to JSON Schema type strings."""
|
|
309
309
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
310
|
+
any = "any"
|
|
311
|
+
integer = "integer"
|
|
312
|
+
string = "string"
|
|
313
|
+
date = "date"
|
|
314
|
+
datetime = "datetime"
|
|
315
|
+
time = "time"
|
|
316
|
+
number = "number"
|
|
317
|
+
boolean = "boolean"
|
|
313
318
|
|
|
314
319
|
|
|
315
|
-
JsonDatatype.any = "any"
|
|
316
|
-
JsonDatatype.integer = "integer"
|
|
317
|
-
JsonDatatype.string = "string"
|
|
318
|
-
JsonDatatype.date = "date"
|
|
319
|
-
JsonDatatype.datetime = "datetime"
|
|
320
|
-
JsonDatatype.time = "time"
|
|
321
|
-
JsonDatatype.number = "number"
|
|
322
|
-
JsonDatatype.boolean = "boolean"
|
|
323
|
-
|
|
324
320
|
# Types without a JSON type equivalent are converted
|
|
325
321
|
# to strings via the "default=str" argument of 'json.dumps()'.
|
|
326
322
|
to_json_type = {
|