execsql2 2.34.0__py3-none-any.whl → 2.37.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/api.py +17 -8
- execsql/cli/lint.py +54 -6
- execsql/cli/run.py +5 -10
- execsql/config.py +2 -0
- execsql/data/execsql.conf.template +17 -7
- execsql/db/base.py +12 -2
- execsql/db/duckdb.py +8 -1
- execsql/debug/repl.py +2 -2
- execsql/metacommands/connect.py +10 -9
- execsql/metacommands/data.py +7 -1
- execsql/metacommands/dispatch.py +7 -0
- execsql/metacommands/io_fileops.py +6 -3
- execsql/metacommands/io_write.py +3 -1
- execsql/metacommands/reference.py +1 -0
- execsql/metacommands/script_ext.py +13 -10
- execsql/metacommands/system.py +13 -6
- execsql/models.py +30 -4
- execsql/script/ast.py +21 -0
- execsql/script/control.py +63 -3
- execsql/script/executor.py +379 -229
- execsql/script/parser.py +102 -52
- execsql/state.py +23 -0
- execsql/utils/errors.py +24 -6
- execsql/utils/sigterm.py +154 -0
- {execsql2-2.34.0.dist-info → execsql2-2.37.0.dist-info}/METADATA +2 -2
- {execsql2-2.34.0.dist-info → execsql2-2.37.0.dist-info}/RECORD +43 -42
- {execsql2-2.34.0.data → execsql2-2.37.0.data}/data/execsql2_extras/README.md +0 -0
- {execsql2-2.34.0.data → execsql2-2.37.0.data}/data/execsql2_extras/config_settings.sqlite +0 -0
- {execsql2-2.34.0.data → execsql2-2.37.0.data}/data/execsql2_extras/example_config_prompt.sql +0 -0
- {execsql2-2.34.0.data → execsql2-2.37.0.data}/data/execsql2_extras/make_config_db.sql +0 -0
- {execsql2-2.34.0.data → execsql2-2.37.0.data}/data/execsql2_extras/md_compare.sql +0 -0
- {execsql2-2.34.0.data → execsql2-2.37.0.data}/data/execsql2_extras/md_glossary.sql +0 -0
- {execsql2-2.34.0.data → execsql2-2.37.0.data}/data/execsql2_extras/md_upsert.sql +0 -0
- {execsql2-2.34.0.data → execsql2-2.37.0.data}/data/execsql2_extras/pg_compare.sql +0 -0
- {execsql2-2.34.0.data → execsql2-2.37.0.data}/data/execsql2_extras/pg_glossary.sql +0 -0
- {execsql2-2.34.0.data → execsql2-2.37.0.data}/data/execsql2_extras/script_template.sql +0 -0
- {execsql2-2.34.0.data → execsql2-2.37.0.data}/data/execsql2_extras/ss_compare.sql +0 -0
- {execsql2-2.34.0.data → execsql2-2.37.0.data}/data/execsql2_extras/ss_glossary.sql +0 -0
- {execsql2-2.34.0.data → execsql2-2.37.0.data}/data/execsql2_extras/ss_upsert.sql +0 -0
- {execsql2-2.34.0.dist-info → execsql2-2.37.0.dist-info}/WHEEL +0 -0
- {execsql2-2.34.0.dist-info → execsql2-2.37.0.dist-info}/entry_points.txt +0 -0
- {execsql2-2.34.0.dist-info → execsql2-2.37.0.dist-info}/licenses/LICENSE.txt +0 -0
- {execsql2-2.34.0.dist-info → execsql2-2.37.0.dist-info}/licenses/NOTICE +0 -0
execsql/script/ast.py
CHANGED
|
@@ -212,12 +212,15 @@ class ElseIfClause:
|
|
|
212
212
|
condition_modifiers: ANDIF/ORIF modifiers that compound the ELSEIF
|
|
213
213
|
condition, evaluated left-to-right at runtime.
|
|
214
214
|
body: Nodes executed when this condition is true.
|
|
215
|
+
command: The metacommand as written, without ``-- !x!``, for error
|
|
216
|
+
reports.
|
|
215
217
|
"""
|
|
216
218
|
|
|
217
219
|
condition: str
|
|
218
220
|
span: SourceSpan
|
|
219
221
|
condition_modifiers: list[ConditionModifier] = field(default_factory=list)
|
|
220
222
|
body: list[Node] = field(default_factory=list)
|
|
223
|
+
command: str = ""
|
|
221
224
|
|
|
222
225
|
|
|
223
226
|
@dataclass
|
|
@@ -232,6 +235,8 @@ class IfBlock(Node):
|
|
|
232
235
|
elseif_clauses: Zero or more ELSEIF branches, evaluated in order.
|
|
233
236
|
else_body: Nodes executed when the IF condition (and all ELSEIFs)
|
|
234
237
|
are false. Empty list means no ELSE branch.
|
|
238
|
+
command: The metacommand as written, without ``-- !x!``, for error
|
|
239
|
+
reports.
|
|
235
240
|
"""
|
|
236
241
|
|
|
237
242
|
condition: str
|
|
@@ -240,6 +245,7 @@ class IfBlock(Node):
|
|
|
240
245
|
elseif_clauses: list[ElseIfClause] = field(default_factory=list)
|
|
241
246
|
else_body: list[Node] = field(default_factory=list)
|
|
242
247
|
else_span: SourceSpan | None = None
|
|
248
|
+
command: str = ""
|
|
243
249
|
|
|
244
250
|
def children(self) -> Iterator[Node]:
|
|
245
251
|
yield from self.body
|
|
@@ -261,11 +267,14 @@ class LoopBlock(Node):
|
|
|
261
267
|
loop_type: Either ``"WHILE"`` or ``"UNTIL"``.
|
|
262
268
|
condition: The condition expression text.
|
|
263
269
|
body: Nodes executed on each iteration.
|
|
270
|
+
command: The metacommand as written, without ``-- !x!``, for error
|
|
271
|
+
reports.
|
|
264
272
|
"""
|
|
265
273
|
|
|
266
274
|
loop_type: str # "WHILE" or "UNTIL"
|
|
267
275
|
condition: str
|
|
268
276
|
body: list[Node] = field(default_factory=list)
|
|
277
|
+
command: str = ""
|
|
269
278
|
|
|
270
279
|
def children(self) -> Iterator[Node]:
|
|
271
280
|
yield from self.body
|
|
@@ -283,9 +292,12 @@ class BatchBlock(Node):
|
|
|
283
292
|
|
|
284
293
|
Attributes:
|
|
285
294
|
body: Nodes within the batch.
|
|
295
|
+
command: The metacommand as written, without ``-- !x!``, for error
|
|
296
|
+
reports.
|
|
286
297
|
"""
|
|
287
298
|
|
|
288
299
|
body: list[Node] = field(default_factory=list)
|
|
300
|
+
command: str = ""
|
|
289
301
|
|
|
290
302
|
def children(self) -> Iterator[Node]:
|
|
291
303
|
yield from self.body
|
|
@@ -325,12 +337,15 @@ class ScriptBlock(Node):
|
|
|
325
337
|
doc: Optional docstring extracted from comments immediately following
|
|
326
338
|
the BEGIN SCRIPT line.
|
|
327
339
|
body: Nodes within the script block.
|
|
340
|
+
command: The metacommand as written, without ``-- !x!``, for error
|
|
341
|
+
reports.
|
|
328
342
|
"""
|
|
329
343
|
|
|
330
344
|
name: str
|
|
331
345
|
param_defs: list[ParamDef] | None = None
|
|
332
346
|
doc: str | None = None
|
|
333
347
|
body: list[Node] = field(default_factory=list)
|
|
348
|
+
command: str = ""
|
|
334
349
|
|
|
335
350
|
@property
|
|
336
351
|
def param_names(self) -> list[str] | None:
|
|
@@ -358,9 +373,12 @@ class SqlBlock(Node):
|
|
|
358
373
|
Attributes:
|
|
359
374
|
body: Nodes within the SQL block (typically a single
|
|
360
375
|
:class:`SqlStatement`).
|
|
376
|
+
command: The metacommand as written, without ``-- !x!``, for error
|
|
377
|
+
reports.
|
|
361
378
|
"""
|
|
362
379
|
|
|
363
380
|
body: list[Node] = field(default_factory=list)
|
|
381
|
+
command: str = ""
|
|
364
382
|
|
|
365
383
|
def children(self) -> Iterator[Node]:
|
|
366
384
|
yield from self.body
|
|
@@ -384,6 +402,8 @@ class IncludeDirective(Node):
|
|
|
384
402
|
arguments: Optional argument expression for EXECUTE SCRIPT.
|
|
385
403
|
loop_type: Optional ``"WHILE"`` or ``"UNTIL"`` for looped execution.
|
|
386
404
|
loop_condition: The loop condition expression, if *loop_type* is set.
|
|
405
|
+
command: The metacommand as written, without ``-- !x!``, for error
|
|
406
|
+
reports.
|
|
387
407
|
"""
|
|
388
408
|
|
|
389
409
|
target: str
|
|
@@ -392,6 +412,7 @@ class IncludeDirective(Node):
|
|
|
392
412
|
arguments: str | None = None
|
|
393
413
|
loop_type: str | None = None
|
|
394
414
|
loop_condition: str | None = None
|
|
415
|
+
command: str = ""
|
|
395
416
|
|
|
396
417
|
def __repr__(self) -> str:
|
|
397
418
|
kind = "EXECUTE SCRIPT" if self.is_execute_script else "INCLUDE"
|
execsql/script/control.py
CHANGED
|
@@ -30,6 +30,8 @@ class BatchLevels:
|
|
|
30
30
|
class Batch:
|
|
31
31
|
def __init__(self) -> None:
|
|
32
32
|
self.dbs_used: list[Any] = []
|
|
33
|
+
# Why this batch can no longer commit (an error inside it), or None.
|
|
34
|
+
self.failed: str | None = None
|
|
33
35
|
|
|
34
36
|
def __init__(self) -> None:
|
|
35
37
|
self.batchlevels: list[BatchLevels.Batch] = []
|
|
@@ -38,6 +40,25 @@ class BatchLevels:
|
|
|
38
40
|
"""Return True if execution is currently inside at least one BATCH block."""
|
|
39
41
|
return len(self.batchlevels) > 0
|
|
40
42
|
|
|
43
|
+
def fail_enclosing(self, reason: str) -> None:
|
|
44
|
+
"""Mark every open batch level as failed, so its END BATCH rolls back instead of committing."""
|
|
45
|
+
for b in self.batchlevels:
|
|
46
|
+
if b.failed is None:
|
|
47
|
+
b.failed = reason
|
|
48
|
+
|
|
49
|
+
def failure(self) -> str | None:
|
|
50
|
+
"""Why the innermost batch can no longer commit, or None.
|
|
51
|
+
|
|
52
|
+
A batch inside a failed one cannot commit either: its commit would
|
|
53
|
+
commit the failed batch's later statements on a shared connection.
|
|
54
|
+
Nothing clears the mark, ROLLBACK BATCH included: once anything in a
|
|
55
|
+
batch has failed, the whole stack of batches rolls back.
|
|
56
|
+
"""
|
|
57
|
+
for b in reversed(self.batchlevels):
|
|
58
|
+
if b.failed is not None:
|
|
59
|
+
return b.failed
|
|
60
|
+
return None
|
|
61
|
+
|
|
41
62
|
def new_batch(self) -> None:
|
|
42
63
|
"""Push a new empty batch level onto the stack."""
|
|
43
64
|
self.batchlevels.append(self.Batch())
|
|
@@ -61,7 +82,46 @@ class BatchLevels:
|
|
|
61
82
|
db.rollback()
|
|
62
83
|
|
|
63
84
|
def end_batch(self) -> None:
|
|
64
|
-
"""Commit all databases in the innermost batch level and pop the stack.
|
|
85
|
+
"""Commit all databases in the innermost batch level and pop the stack.
|
|
86
|
+
|
|
87
|
+
If a commit fails, :meth:`Database.commit` rolls that database back,
|
|
88
|
+
and the ones not yet committed are rolled back too (except one with
|
|
89
|
+
autocommit off, whose transaction is the script's) before the error
|
|
90
|
+
is raised: a commit that already succeeded
|
|
91
|
+
cannot be undone, but nothing else of the batch is left pending for
|
|
92
|
+
a later statement to commit.
|
|
93
|
+
"""
|
|
94
|
+
b = self.batchlevels.pop()
|
|
95
|
+
for i, db in enumerate(b.dbs_used):
|
|
96
|
+
try:
|
|
97
|
+
db.commit()
|
|
98
|
+
except BaseException:
|
|
99
|
+
_roll_back(b.dbs_used[i + 1 :])
|
|
100
|
+
raise
|
|
101
|
+
|
|
102
|
+
def abandon_batch(self) -> None:
|
|
103
|
+
"""Pop the innermost batch level without committing it.
|
|
104
|
+
|
|
105
|
+
Inside another batch, its databases join the enclosing level, which
|
|
106
|
+
commits or rolls them back. Otherwise they are rolled back, except
|
|
107
|
+
one with autocommit off: :meth:`end_batch` would not have committed
|
|
108
|
+
it either, so its transaction stays the script's to end.
|
|
109
|
+
"""
|
|
65
110
|
b = self.batchlevels.pop()
|
|
66
|
-
|
|
67
|
-
db.
|
|
111
|
+
if self.batchlevels:
|
|
112
|
+
for db in b.dbs_used:
|
|
113
|
+
self.using_db(db)
|
|
114
|
+
return
|
|
115
|
+
_roll_back(b.dbs_used)
|
|
116
|
+
|
|
117
|
+
def abandon_all(self) -> None:
|
|
118
|
+
"""Abandon every open batch level, innermost first."""
|
|
119
|
+
while self.batchlevels:
|
|
120
|
+
self.abandon_batch()
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def _roll_back(dbs: list[Any]) -> None:
|
|
124
|
+
"""Roll back each of *dbs* except one with autocommit off, whose transaction is the script's."""
|
|
125
|
+
for db in dbs:
|
|
126
|
+
if db.autocommit:
|
|
127
|
+
db.rollback()
|