execsql2 2.22.5__py3-none-any.whl → 2.22.6__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/format.py +101 -12
- {execsql2-2.22.5.dist-info → execsql2-2.22.6.dist-info}/METADATA +2 -2
- {execsql2-2.22.5.dist-info → execsql2-2.22.6.dist-info}/RECORD +21 -21
- {execsql2-2.22.5.data → execsql2-2.22.6.data}/data/execsql2_extras/README.md +0 -0
- {execsql2-2.22.5.data → execsql2-2.22.6.data}/data/execsql2_extras/config_settings.sqlite +0 -0
- {execsql2-2.22.5.data → execsql2-2.22.6.data}/data/execsql2_extras/example_config_prompt.sql +0 -0
- {execsql2-2.22.5.data → execsql2-2.22.6.data}/data/execsql2_extras/make_config_db.sql +0 -0
- {execsql2-2.22.5.data → execsql2-2.22.6.data}/data/execsql2_extras/md_compare.sql +0 -0
- {execsql2-2.22.5.data → execsql2-2.22.6.data}/data/execsql2_extras/md_glossary.sql +0 -0
- {execsql2-2.22.5.data → execsql2-2.22.6.data}/data/execsql2_extras/md_upsert.sql +0 -0
- {execsql2-2.22.5.data → execsql2-2.22.6.data}/data/execsql2_extras/pg_compare.sql +0 -0
- {execsql2-2.22.5.data → execsql2-2.22.6.data}/data/execsql2_extras/pg_glossary.sql +0 -0
- {execsql2-2.22.5.data → execsql2-2.22.6.data}/data/execsql2_extras/pg_upsert.sql +0 -0
- {execsql2-2.22.5.data → execsql2-2.22.6.data}/data/execsql2_extras/script_template.sql +0 -0
- {execsql2-2.22.5.data → execsql2-2.22.6.data}/data/execsql2_extras/ss_compare.sql +0 -0
- {execsql2-2.22.5.data → execsql2-2.22.6.data}/data/execsql2_extras/ss_glossary.sql +0 -0
- {execsql2-2.22.5.data → execsql2-2.22.6.data}/data/execsql2_extras/ss_upsert.sql +0 -0
- {execsql2-2.22.5.dist-info → execsql2-2.22.6.dist-info}/WHEEL +0 -0
- {execsql2-2.22.5.dist-info → execsql2-2.22.6.dist-info}/entry_points.txt +0 -0
- {execsql2-2.22.5.dist-info → execsql2-2.22.6.dist-info}/licenses/LICENSE.txt +0 -0
- {execsql2-2.22.5.dist-info → execsql2-2.22.6.dist-info}/licenses/NOTICE +0 -0
execsql/format.py
CHANGED
|
@@ -278,6 +278,64 @@ def _sql_literal_texts(sql: str) -> list[str]:
|
|
|
278
278
|
return [sql[start:end] for start, end, _ in _iter_sql_literals(sql)]
|
|
279
279
|
|
|
280
280
|
|
|
281
|
+
def _literal_line_starts(lines: list[str]) -> list[int]:
|
|
282
|
+
"""Return the character offset at which each of *lines* starts once joined."""
|
|
283
|
+
starts: list[int] = []
|
|
284
|
+
offset = 0
|
|
285
|
+
for line in lines:
|
|
286
|
+
starts.append(offset)
|
|
287
|
+
offset += len(line) + 1 # +1 for the newline joined between lines
|
|
288
|
+
return starts
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
def _literal_interior_lines(lines: list[str]) -> set[int]:
|
|
292
|
+
"""Return indices of *lines* that begin inside a multi-line string literal.
|
|
293
|
+
|
|
294
|
+
The line that *opens* a literal is ordinary SQL up to the delimiter, so it
|
|
295
|
+
is indented with the statement as usual. Every later line of that literal
|
|
296
|
+
— through and including the one holding the closing delimiter — begins
|
|
297
|
+
inside the string, which makes its leading whitespace part of the stored
|
|
298
|
+
value rather than layout. Re-indenting those lines changes what the
|
|
299
|
+
database receives, so callers must emit them verbatim.
|
|
300
|
+
|
|
301
|
+
This covers every literal kind the scanner reports, not just dollar-quoted
|
|
302
|
+
bodies: an ordinary ``'…'`` string spanning lines carries its newlines and
|
|
303
|
+
indentation exactly the same way.
|
|
304
|
+
"""
|
|
305
|
+
if len(lines) < 2:
|
|
306
|
+
return set()
|
|
307
|
+
text = "\n".join(lines)
|
|
308
|
+
starts = _literal_line_starts(lines)
|
|
309
|
+
|
|
310
|
+
interior: set[int] = set()
|
|
311
|
+
for start, end, _kind in _iter_sql_literals(text):
|
|
312
|
+
interior.update(i for i, line_start in enumerate(starts) if start < line_start < end)
|
|
313
|
+
return interior
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
def _literal_spanning_lines(lines: list[str]) -> set[int]:
|
|
317
|
+
"""Return indices of every line that any multi-line literal touches.
|
|
318
|
+
|
|
319
|
+
Unlike :func:`_literal_interior_lines` this also includes the line that
|
|
320
|
+
*opens* the literal. Passes that move text between lines — rather than
|
|
321
|
+
only adjusting leading whitespace — must leave the whole span alone, since
|
|
322
|
+
the opening line holds literal content after its delimiter.
|
|
323
|
+
"""
|
|
324
|
+
if len(lines) < 2:
|
|
325
|
+
return set()
|
|
326
|
+
text = "\n".join(lines)
|
|
327
|
+
starts = _literal_line_starts(lines)
|
|
328
|
+
|
|
329
|
+
spanning: set[int] = set()
|
|
330
|
+
for start, end, _kind in _iter_sql_literals(text):
|
|
331
|
+
touched = [i for i, line_start in enumerate(starts) if start < line_start < end]
|
|
332
|
+
if not touched:
|
|
333
|
+
continue # single-line literal — nothing spans
|
|
334
|
+
spanning.update(touched)
|
|
335
|
+
spanning.add(min(touched) - 1) # the line that opens the literal
|
|
336
|
+
return spanning
|
|
337
|
+
|
|
338
|
+
|
|
281
339
|
# Placeholder standing in for a masked E'...' literal. It is a plain literal
|
|
282
340
|
# so that sqlglot sees a string where a string was, and lays the statement out
|
|
283
341
|
# the same way it would have for the real one.
|
|
@@ -503,6 +561,14 @@ def _has_mid_statement_comments(lines: list[str]) -> bool:
|
|
|
503
561
|
|
|
504
562
|
_CMT_MARKER = "EXECSQL_CMTMARKER_"
|
|
505
563
|
_CMT_MARKER_RE = re.compile(rf"/\*\s*({re.escape(_CMT_MARKER)}\d+)\s*\*/")
|
|
564
|
+
# A whole run of adjacent markers plus the whitespace injected around them.
|
|
565
|
+
# Markers are written as ``/* marker */ line`` and several consecutive source
|
|
566
|
+
# comments attach several markers to one line, so removing them one at a time
|
|
567
|
+
# leaves a separator space behind for each. When sqlglot moves the run into
|
|
568
|
+
# the middle of an expression that residue shows up as ``AND RTRIM(`` and is
|
|
569
|
+
# only normalised on the following pass, which costs an extra pass to
|
|
570
|
+
# converge. Matching the run as a unit removes it cleanly. See issue #35.
|
|
571
|
+
_CMT_MARKER_RUN_RE = re.compile(rf"\s*(?:/\*\s*{re.escape(_CMT_MARKER)}\d+\s*\*/\s*)+")
|
|
506
572
|
|
|
507
573
|
|
|
508
574
|
def _format_preserving_comments(
|
|
@@ -570,12 +636,16 @@ def _format_preserving_comments(
|
|
|
570
636
|
markers_here = _CMT_MARKER_RE.findall(fline)
|
|
571
637
|
if markers_here:
|
|
572
638
|
# Strip markers to get the underlying SQL line and its indent
|
|
573
|
-
cleaned =
|
|
574
|
-
#
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
639
|
+
cleaned = _CMT_MARKER_RUN_RE.sub(" ", fline).strip()
|
|
640
|
+
# Indent comes from the line's own leading whitespace, never from
|
|
641
|
+
# what is left after removing the marker. Markers are injected as
|
|
642
|
+
# ``/* marker */ line`` — with a separating space — so measuring
|
|
643
|
+
# after removal counts that space as indentation. Whenever
|
|
644
|
+
# sqlglot leaves the line untouched (an unparsable statement falls
|
|
645
|
+
# back verbatim) the extra space is re-measured and re-added on the
|
|
646
|
+
# next run, growing the indent by one on every pass and never
|
|
647
|
+
# converging. See issue #35.
|
|
648
|
+
line_indent = fline[: len(fline) - len(fline.lstrip())]
|
|
579
649
|
for m in markers_here:
|
|
580
650
|
if m in comment_store:
|
|
581
651
|
orig = comment_store[m]
|
|
@@ -671,12 +741,24 @@ def format_sql_block(
|
|
|
671
741
|
if not non_empty:
|
|
672
742
|
return [""] * len(lines)
|
|
673
743
|
|
|
674
|
-
|
|
744
|
+
# Lines that begin inside a multi-line literal carry content, not layout —
|
|
745
|
+
# dedenting or indenting them changes the value the database stores.
|
|
746
|
+
# Exclude them from the indent arithmetic and emit them verbatim.
|
|
747
|
+
interior = _literal_interior_lines(lines)
|
|
748
|
+
if interior:
|
|
749
|
+
# format_file already withholds dollar-quoted blocks from sqlglot; keep
|
|
750
|
+
# that true here so the line indices below stay aligned with the output.
|
|
751
|
+
use_sql = False
|
|
752
|
+
|
|
753
|
+
layout_lines = [line for i, line in enumerate(lines) if line.strip() and i not in interior]
|
|
754
|
+
base = min((len(line) - len(line.lstrip()) for line in layout_lines), default=0)
|
|
675
755
|
target_prefix = " " * (depth * indent)
|
|
676
|
-
rebased = [line[base:] if line.strip() else "" for line in lines]
|
|
756
|
+
rebased = [line if i in interior else (line[base:] if line.strip() else "") for i, line in enumerate(lines)]
|
|
677
757
|
|
|
678
758
|
if not use_sql:
|
|
679
|
-
return [
|
|
759
|
+
return [
|
|
760
|
+
line if i in interior else (target_prefix + line if line.strip() else "") for i, line in enumerate(rebased)
|
|
761
|
+
]
|
|
680
762
|
|
|
681
763
|
# When comments appear mid-statement, use the marker-based approach
|
|
682
764
|
# which preserves both comments AND sqlglot formatting. When all
|
|
@@ -747,6 +829,9 @@ def _normalize_to_trailing_comma(text: str) -> str:
|
|
|
747
829
|
every invocation. Comments between the two SQL lines stay in place.
|
|
748
830
|
"""
|
|
749
831
|
lines = text.split("\n")
|
|
832
|
+
# Symmetric with _apply_leading_comma: a leading comma inside a multi-line
|
|
833
|
+
# literal is data, not layout, so the whole span is left alone.
|
|
834
|
+
protected = _literal_spanning_lines(lines)
|
|
750
835
|
|
|
751
836
|
def find_prev_sql_line(idx: int) -> int:
|
|
752
837
|
k = idx - 1
|
|
@@ -756,7 +841,7 @@ def _normalize_to_trailing_comma(text: str) -> str:
|
|
|
756
841
|
|
|
757
842
|
for i, line in enumerate(lines):
|
|
758
843
|
stripped = line.lstrip()
|
|
759
|
-
if not stripped.startswith(",") or _is_comment_only(line):
|
|
844
|
+
if not stripped.startswith(",") or _is_comment_only(line) or i in protected:
|
|
760
845
|
continue
|
|
761
846
|
# Don't try to rewrite leading-comma inside a comment-only line.
|
|
762
847
|
# Move the comma onto the previous SQL line as a trailing `,`.
|
|
@@ -792,15 +877,19 @@ def _apply_leading_comma(text: str) -> str:
|
|
|
792
877
|
TestIdempotency).
|
|
793
878
|
"""
|
|
794
879
|
lines = text.split("\n")
|
|
880
|
+
# A comma inside a multi-line string literal is data. Moving it to the next
|
|
881
|
+
# line would rewrite the stored value, so leave every line such a literal
|
|
882
|
+
# touches — including the one that opens it — exactly as it is.
|
|
883
|
+
protected = _literal_spanning_lines(lines)
|
|
795
884
|
i = 0
|
|
796
885
|
n = len(lines)
|
|
797
886
|
while i < n:
|
|
798
887
|
rstripped = lines[i].rstrip()
|
|
799
|
-
if rstripped.endswith(",") and not _is_comment_only(lines[i]):
|
|
888
|
+
if rstripped.endswith(",") and not _is_comment_only(lines[i]) and i not in protected:
|
|
800
889
|
j = i + 1
|
|
801
890
|
while j < n and (not lines[j].strip() or _is_comment_only(lines[j])):
|
|
802
891
|
j += 1
|
|
803
|
-
if j < n:
|
|
892
|
+
if j < n and j not in protected:
|
|
804
893
|
stripped = lines[i].rstrip()
|
|
805
894
|
comma_idx = stripped.rfind(",")
|
|
806
895
|
lines[i] = stripped[:comma_idx] + stripped[comma_idx + 1 :]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: execsql2
|
|
3
|
-
Version: 2.22.
|
|
3
|
+
Version: 2.22.6
|
|
4
4
|
Summary: Runs a SQL script against a PostgreSQL, SQLite, MariaDB/MySQL, DuckDB, Firebird, MS-Access, MS-SQL-Server, or Oracle database, or an ODBC DSN. Provides metacommands to import and export data, copy data between databases, conditionally execute SQL and metacommands, and dynamically alter SQL and metacommands with substitution variables.
|
|
5
5
|
Project-URL: Homepage, https://execsql2.readthedocs.io
|
|
6
6
|
Project-URL: Repository, https://github.com/geocoug/execsql
|
|
@@ -384,7 +384,7 @@ execsql-format --no-sql --in-place scripts/
|
|
|
384
384
|
```yaml
|
|
385
385
|
repos:
|
|
386
386
|
- repo: https://github.com/geocoug/execsql
|
|
387
|
-
rev: v2.22.
|
|
387
|
+
rev: v2.22.6
|
|
388
388
|
hooks:
|
|
389
389
|
- id: execsql-format
|
|
390
390
|
```
|
|
@@ -3,7 +3,7 @@ execsql/__main__.py,sha256=HdbK-SAhyUmfB6xINY5AzxdMSxGzWSGEG_2dv42Jn64,315
|
|
|
3
3
|
execsql/api.py,sha256=EOtBwMnm-8fmB_KmvxyjglR6C7CjdYhCPBisJ9CFfJg,22227
|
|
4
4
|
execsql/config.py,sha256=txKziUJLV7UPonj2sGFgkmlinDdSV50aHmR_cuCbEIo,34369
|
|
5
5
|
execsql/exceptions.py,sha256=GlphAiR8iZu4UR4U50WgSqEUMd9_8AeQJ_hhPcJOeQU,9381
|
|
6
|
-
execsql/format.py,sha256=
|
|
6
|
+
execsql/format.py,sha256=YJH_JA1_IqBfeWhgrusA3KKs_xrmbm3ktuVf_xJryws,46017
|
|
7
7
|
execsql/models.py,sha256=-DWTVrPNwM-n1h3e3rXMoBv9-wpWVdZahyy4RB7fRRE,15502
|
|
8
8
|
execsql/parser.py,sha256=aOxZ7Mzgvk7G4-nvy9Bgz7xlaZm6oo81cffDeBk_LCk,15597
|
|
9
9
|
execsql/plugins.py,sha256=l82VWa5NpvOQWD6vcO-WKP-vtn0wp9__wWDFAbwxZg0,12838
|
|
@@ -101,23 +101,23 @@ execsql/utils/numeric.py,sha256=_7bi5R6pHjMCkOPne_Sh5nUj-B8e9g6WFmjptFHaunA,1573
|
|
|
101
101
|
execsql/utils/regex.py,sha256=B3mSBhRRx_DpUnX_t9IoYd05KvWAebiLMCZqQAmEwcw,3885
|
|
102
102
|
execsql/utils/strings.py,sha256=UQNjpRCEFa1UO6feU-M-9e24wWAvizs_iu_4fFusLxo,8516
|
|
103
103
|
execsql/utils/timer.py,sha256=eDYf5VzCNFk7oo90InJucUm3XcBdhYMogjZMqeg9xzc,1899
|
|
104
|
-
execsql2-2.22.
|
|
105
|
-
execsql2-2.22.
|
|
106
|
-
execsql2-2.22.
|
|
107
|
-
execsql2-2.22.
|
|
108
|
-
execsql2-2.22.
|
|
109
|
-
execsql2-2.22.
|
|
110
|
-
execsql2-2.22.
|
|
111
|
-
execsql2-2.22.
|
|
112
|
-
execsql2-2.22.
|
|
113
|
-
execsql2-2.22.
|
|
114
|
-
execsql2-2.22.
|
|
115
|
-
execsql2-2.22.
|
|
116
|
-
execsql2-2.22.
|
|
117
|
-
execsql2-2.22.
|
|
118
|
-
execsql2-2.22.
|
|
119
|
-
execsql2-2.22.
|
|
120
|
-
execsql2-2.22.
|
|
121
|
-
execsql2-2.22.
|
|
122
|
-
execsql2-2.22.
|
|
123
|
-
execsql2-2.22.
|
|
104
|
+
execsql2-2.22.6.data/data/execsql2_extras/README.md,sha256=vX4NTL095dUoA_hesyRMGYBorEZ_Y_tJ9qrd-MVV09I,5032
|
|
105
|
+
execsql2-2.22.6.data/data/execsql2_extras/config_settings.sqlite,sha256=aY5cxR7Q7J6zJ4bC9lu5mHUrhy211Cq3MNKPQVCt02E,20480
|
|
106
|
+
execsql2-2.22.6.data/data/execsql2_extras/example_config_prompt.sql,sha256=2e8KzzVWhho8KxYVHETSVmZdhW7wodioDsqBLSL6m4s,7487
|
|
107
|
+
execsql2-2.22.6.data/data/execsql2_extras/make_config_db.sql,sha256=WwyC6dK-Eh5CAVppiBCDHqiI1_wEI9U95Ytpr4lsZkg,8726
|
|
108
|
+
execsql2-2.22.6.data/data/execsql2_extras/md_compare.sql,sha256=qYYVAjSeHZzjszxV3Bv6bg8Ckbq2kMHl87_gh4sywMU,24140
|
|
109
|
+
execsql2-2.22.6.data/data/execsql2_extras/md_glossary.sql,sha256=hkZ2Onn57LAKKsuXxzhR8tPtcWXkmWEQkwPE58-Tm2k,10796
|
|
110
|
+
execsql2-2.22.6.data/data/execsql2_extras/md_upsert.sql,sha256=_CAK4BzEboRXTNy03SJR-oOjcEdSNMuRBPL6noWUptY,112560
|
|
111
|
+
execsql2-2.22.6.data/data/execsql2_extras/pg_compare.sql,sha256=1zJd4hVUKHR0tncc2qTBC9B4qVV4Us2ITkJpsjN3tMw,24352
|
|
112
|
+
execsql2-2.22.6.data/data/execsql2_extras/pg_glossary.sql,sha256=IKuwna-_8b20ljSkXZruuiQigrCpo7ueQdUqd1MXiuI,9908
|
|
113
|
+
execsql2-2.22.6.data/data/execsql2_extras/pg_upsert.sql,sha256=HpPJtTHvpEjQy03j-3iPxDEOHMRkudOg7O4D4YR38UI,108315
|
|
114
|
+
execsql2-2.22.6.data/data/execsql2_extras/script_template.sql,sha256=2J35ddZPguJ-vwTsz83wErv0jiWUyJcdW_JM0mNKDXA,11155
|
|
115
|
+
execsql2-2.22.6.data/data/execsql2_extras/ss_compare.sql,sha256=j1qVNUPXQsEU7-DoVgDJCGcE0EuIl7whLBT3fgeiMAo,24833
|
|
116
|
+
execsql2-2.22.6.data/data/execsql2_extras/ss_glossary.sql,sha256=2gLxv34xzKt0vy7hSzJH7a9JiMC3ETrv9MofxQwAibU,13065
|
|
117
|
+
execsql2-2.22.6.data/data/execsql2_extras/ss_upsert.sql,sha256=G_8rQ0VzuKIZHWs24O_WrfzpC5S27R1JsL-bFBR3SUQ,117730
|
|
118
|
+
execsql2-2.22.6.dist-info/METADATA,sha256=zX8fCinRuVHLFNIAKypooRUh_qPFfn-J6oBwu30_J_U,20382
|
|
119
|
+
execsql2-2.22.6.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
|
|
120
|
+
execsql2-2.22.6.dist-info/entry_points.txt,sha256=sUOxkM-dN1eBGGpSpDLsAaE0yNXYQKWZAfxPOlMkQyk,90
|
|
121
|
+
execsql2-2.22.6.dist-info/licenses/LICENSE.txt,sha256=LBdhuxejF8_bLCHZ2kWfmDXpDGUu914Gbd6_3JjCRe0,676
|
|
122
|
+
execsql2-2.22.6.dist-info/licenses/NOTICE,sha256=McYzgxYav3U1OaVsY4Su1sfBrfmplpRdA9b6-gCDQCg,342
|
|
123
|
+
execsql2-2.22.6.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
{execsql2-2.22.5.data → execsql2-2.22.6.data}/data/execsql2_extras/example_config_prompt.sql
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|