execsql2 2.25.0__py3-none-any.whl → 2.25.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.
Files changed (21) hide show
  1. execsql/cli/lint.py +86 -26
  2. {execsql2-2.25.0.dist-info → execsql2-2.25.1.dist-info}/METADATA +2 -2
  3. {execsql2-2.25.0.dist-info → execsql2-2.25.1.dist-info}/RECORD +21 -21
  4. {execsql2-2.25.0.data → execsql2-2.25.1.data}/data/execsql2_extras/README.md +0 -0
  5. {execsql2-2.25.0.data → execsql2-2.25.1.data}/data/execsql2_extras/config_settings.sqlite +0 -0
  6. {execsql2-2.25.0.data → execsql2-2.25.1.data}/data/execsql2_extras/example_config_prompt.sql +0 -0
  7. {execsql2-2.25.0.data → execsql2-2.25.1.data}/data/execsql2_extras/make_config_db.sql +0 -0
  8. {execsql2-2.25.0.data → execsql2-2.25.1.data}/data/execsql2_extras/md_compare.sql +0 -0
  9. {execsql2-2.25.0.data → execsql2-2.25.1.data}/data/execsql2_extras/md_glossary.sql +0 -0
  10. {execsql2-2.25.0.data → execsql2-2.25.1.data}/data/execsql2_extras/md_upsert.sql +0 -0
  11. {execsql2-2.25.0.data → execsql2-2.25.1.data}/data/execsql2_extras/pg_compare.sql +0 -0
  12. {execsql2-2.25.0.data → execsql2-2.25.1.data}/data/execsql2_extras/pg_glossary.sql +0 -0
  13. {execsql2-2.25.0.data → execsql2-2.25.1.data}/data/execsql2_extras/pg_upsert.sql +0 -0
  14. {execsql2-2.25.0.data → execsql2-2.25.1.data}/data/execsql2_extras/script_template.sql +0 -0
  15. {execsql2-2.25.0.data → execsql2-2.25.1.data}/data/execsql2_extras/ss_compare.sql +0 -0
  16. {execsql2-2.25.0.data → execsql2-2.25.1.data}/data/execsql2_extras/ss_glossary.sql +0 -0
  17. {execsql2-2.25.0.data → execsql2-2.25.1.data}/data/execsql2_extras/ss_upsert.sql +0 -0
  18. {execsql2-2.25.0.dist-info → execsql2-2.25.1.dist-info}/WHEEL +0 -0
  19. {execsql2-2.25.0.dist-info → execsql2-2.25.1.dist-info}/entry_points.txt +0 -0
  20. {execsql2-2.25.0.dist-info → execsql2-2.25.1.dist-info}/licenses/LICENSE.txt +0 -0
  21. {execsql2-2.25.0.dist-info → execsql2-2.25.1.dist-info}/licenses/NOTICE +0 -0
execsql/cli/lint.py CHANGED
@@ -243,6 +243,12 @@ _RX_PROMPT_CREDENTIALS = re.compile(
243
243
  )
244
244
 
245
245
  _RX_VAR_REF = re.compile(r"!!([$@&~#+]?\w+)!!", re.I)
246
+ # Every candidate reference, including one nested in another's name
247
+ # (``!!N_!!GROUP!!_CHECKS!!``), which _RX_VAR_REF's left-to-right scan skips.
248
+ _RX_VAR_REF_ANY = re.compile(r"(?=!!([$@&~#+]?\w+)!!)")
249
+ # Stands for a substituted value in _unresolved_var_refs: a word character
250
+ # (so an enclosing name still matches) that no script name contains.
251
+ _SUBSTITUTED = "ª"
246
252
 
247
253
 
248
254
  # ---------------------------------------------------------------------------
@@ -424,54 +430,108 @@ def _extract_var_definition(
424
430
  _read_ini_vars(ini_file, ini_section, script_dir, defined)
425
431
 
426
432
 
433
+ # Larger than any real SUB_INI file; lint reads no more than this.
434
+ _MAX_INI_BYTES = 1024 * 1024
435
+
436
+
427
437
  def _read_ini_vars(
428
438
  ini_file: str,
429
439
  section: str,
430
440
  script_dir: Path | None,
431
441
  defined_vars: set[str],
432
442
  ) -> None:
433
- """Read an INI file and register its section keys as defined variables."""
443
+ """Read an INI file and register its section keys as defined variables.
444
+
445
+ Only a regular file up to :data:`_MAX_INI_BYTES` is read, so a script
446
+ naming a FIFO or a device (``/dev/zero``) cannot hang lint or the
447
+ language server; a file that is unreadable or not valid INI defines
448
+ nothing.
449
+ """
434
450
  from configparser import ConfigParser
451
+ from configparser import Error as ConfigError
435
452
 
436
453
  p = Path(ini_file)
437
454
  if not p.is_absolute() and script_dir is not None:
438
455
  p = script_dir / p
439
456
 
440
- if not p.exists():
441
- return
442
-
443
457
  cp = ConfigParser()
444
- cp.read(p)
458
+ try:
459
+ if not p.is_file() or p.stat().st_size > _MAX_INI_BYTES:
460
+ return
461
+ cp.read_string(p.read_text(encoding="utf-8"), source=str(p))
462
+ except (OSError, UnicodeDecodeError, ConfigError):
463
+ return
445
464
  if cp.has_section(section):
446
465
  for key, _value in cp.items(section):
447
466
  defined_vars.add(key.upper())
448
467
 
449
468
 
450
- def _check_var_ref(
451
- raw_name: str,
452
- source: str,
453
- line_no: int,
454
- defined_vars: set[str],
455
- issues: list[_Issue],
456
- ) -> None:
457
- """Emit a warning if *raw_name* looks like an undefined user variable."""
458
- if not raw_name:
459
- return
460
-
469
+ def _is_known_var(raw_name: str, defined_vars: set[str]) -> bool:
470
+ """Whether execsql may substitute *raw_name* (as written, with its sigil) at run time."""
461
471
  sigil = raw_name[0] if raw_name[0] in ("$", "@", "&", "~", "#", "+") else ""
462
472
  name = raw_name[len(sigil) :]
463
473
 
464
474
  # Skip non-$ sigil prefixes — resolved at runtime
465
475
  if sigil in ("@", "&", "~", "#", "+"):
466
- return
476
+ return True
467
477
 
468
478
  if sigil == "$":
469
479
  # System variables: $ARG_N is set with -a/--assign-arg, $COUNTER_N by
470
480
  # CounterVars, the rest by execsql itself. A SUB cannot define one.
471
- if re.match(r"^(?:ARG|COUNTER)_\d+$", name, re.I) or name.upper() in _get_builtin_vars():
472
- return
473
- elif name.upper() in defined_vars:
474
- # User variables, defined by a SUB-family metacommand or a prompt.
481
+ return bool(re.match(r"^(?:ARG|COUNTER)_\d+$", name, re.I)) or name.upper() in _get_builtin_vars()
482
+ # User variables, defined by a SUB-family metacommand or a prompt.
483
+ return name.upper() in defined_vars
484
+
485
+
486
+ def _unresolved_var_refs(text: str, defined_vars: set[str]) -> list[str]:
487
+ """The variable names in *text* that execsql would leave unsubstituted, as written.
488
+
489
+ execsql substitutes a known variable wherever ``!!name!!`` appears and
490
+ repeats until nothing changes, so a variable written inside another's
491
+ name (``!!N_!!GROUP!!_CHECKS!!``) is replaced first, and the outer name
492
+ is only known at run time: it is not checked. Here each known variable
493
+ is replaced by a placeholder character, a name holding one counts as
494
+ known, and that repeats until nothing is replaced.
495
+
496
+ In a run of unknown candidates that share delimiters, the inner ones
497
+ (every second, from the second) are reported: with ``GROUP`` undefined,
498
+ ``!!N_!!GROUP!!_CHECKS!!`` reports ``GROUP``, not the fragments ``N_``
499
+ and ``_CHECKS``.
500
+ """
501
+ while True:
502
+ parts: list[str] = []
503
+ end = 0
504
+ for m in _RX_VAR_REF_ANY.finditer(text):
505
+ name = m.group(1)
506
+ if m.start() >= end and (_SUBSTITUTED in name or _is_known_var(name, defined_vars)):
507
+ parts += [text[end : m.start()], _SUBSTITUTED]
508
+ end = m.start() + len(name) + 4
509
+ if not parts:
510
+ break
511
+ text = "".join([*parts, text[end:]])
512
+
513
+ unresolved: list[str] = []
514
+ run: list[str] = []
515
+ end = -1
516
+ for m in _RX_VAR_REF_ANY.finditer(text):
517
+ if run and m.start() != end - 2: # not sharing the previous candidate's closing !!
518
+ unresolved.extend(run if len(run) == 1 else run[1::2])
519
+ run = []
520
+ run.append(m.group(1))
521
+ end = m.start() + len(m.group(1)) + 4
522
+ unresolved.extend(run if len(run) == 1 else run[1::2])
523
+ return unresolved
524
+
525
+
526
+ def _check_var_ref(
527
+ raw_name: str,
528
+ source: str,
529
+ line_no: int,
530
+ defined_vars: set[str],
531
+ issues: list[_Issue],
532
+ ) -> None:
533
+ """Emit a warning if *raw_name* looks like an undefined user variable."""
534
+ if not raw_name or _is_known_var(raw_name, defined_vars):
475
535
  return
476
536
 
477
537
  issues.append(
@@ -735,7 +795,7 @@ def _collect_var_references(nodes: list[Node], seen: set[str]) -> None:
735
795
  """Record every variable read anywhere beneath *nodes*: ``!!var!!``, or ``sub_defined(var)`` / ``sub_empty(var)``."""
736
796
  for node in nodes:
737
797
  for text in _referencing_text(node):
738
- for m in _RX_VAR_REF.finditer(text):
798
+ for m in _RX_VAR_REF_ANY.finditer(text):
739
799
  seen.add(m.group(1).lstrip("$@&~#+").upper())
740
800
  for m in _RX_VAR_TEST.finditer(text):
741
801
  seen.add(m.group(1).lstrip("$@&~#+").upper())
@@ -826,14 +886,14 @@ def _lint_nodes(
826
886
 
827
887
  # -- Variable references in SQL --
828
888
  if isinstance(node, SqlStatement):
829
- for m in _RX_VAR_REF.finditer(node.text):
830
- _check_var_ref(m.group(1), src, lno, defined_vars, issues)
889
+ for name in _unresolved_var_refs(node.text, defined_vars):
890
+ _check_var_ref(name, src, lno, defined_vars, issues)
831
891
 
832
892
  # -- Metacommand checks --
833
893
  elif isinstance(node, MetaCommandStatement):
834
894
  _check_metacommand(node, issues)
835
- for m in _RX_VAR_REF.finditer(node.command):
836
- _check_var_ref(m.group(1), src, lno, defined_vars, issues)
895
+ for name in _unresolved_var_refs(node.command, defined_vars):
896
+ _check_var_ref(name, src, lno, defined_vars, issues)
837
897
 
838
898
  # -- IncludeDirective checks --
839
899
  elif isinstance(node, IncludeDirective):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: execsql2
3
- Version: 2.25.0
3
+ Version: 2.25.1
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
@@ -451,7 +451,7 @@ execsql format --no-sql --in-place scripts/
451
451
  ```yaml
452
452
  repos:
453
453
  - repo: https://github.com/geocoug/execsql
454
- rev: v2.25.0
454
+ rev: v2.25.1
455
455
  hooks:
456
456
  - id: execsql-format
457
457
  - id: execsql-lint
@@ -19,7 +19,7 @@ execsql/cli/application.py,sha256=qKjkhDMWB7fEjEbqOLZ3pNFw80piBnRSeW_DfZg-9MA,12
19
19
  execsql/cli/dispatch.py,sha256=ew10mZNXsFHwiNd_etvpSQYD3T8CiexXkjC3Ja94C4w,2697
20
20
  execsql/cli/dsn.py,sha256=uBspf8DXCn2pXmT1Ug1c4IG7IgR5IFGKp9138puaubo,3289
21
21
  execsql/cli/help.py,sha256=Edr3d2ahKv387a6WGgyJi5vKIFe39tGPgTv1GKp3itE,10848
22
- execsql/cli/lint.py,sha256=7t4HXMqPCxHR6PoRxna0X3myu9hlxuK1rbPn5h2tX1Y,43390
22
+ execsql/cli/lint.py,sha256=ZW-FhAjLSuqVNndfP_LX0YfZNswUCkyFAvk3aNPjHpU,46256
23
23
  execsql/cli/options.py,sha256=Srhf1gEOB5yy_7PxkTKBv07xK8Qzw8H-z7PUUnItpwE,2916
24
24
  execsql/cli/run.py,sha256=5f0SX6QyNl8Ur2MwuW2GaN1OqVpWbEI8CQV3k-hBwH8,41987
25
25
  execsql/cli/commands/__init__.py,sha256=wN5sbhKs11S8EZOTBd_GPP6TPjhYfiQkrI48Sy8n6U0,521
@@ -118,23 +118,23 @@ execsql/utils/numeric.py,sha256=_7bi5R6pHjMCkOPne_Sh5nUj-B8e9g6WFmjptFHaunA,1573
118
118
  execsql/utils/regex.py,sha256=B3mSBhRRx_DpUnX_t9IoYd05KvWAebiLMCZqQAmEwcw,3885
119
119
  execsql/utils/strings.py,sha256=UQNjpRCEFa1UO6feU-M-9e24wWAvizs_iu_4fFusLxo,8516
120
120
  execsql/utils/timer.py,sha256=eDYf5VzCNFk7oo90InJucUm3XcBdhYMogjZMqeg9xzc,1899
121
- execsql2-2.25.0.data/data/execsql2_extras/README.md,sha256=7SG9FbBpdqkgQTTt1aBTbCgymBZyV0rLrQMQrendzxs,5032
122
- execsql2-2.25.0.data/data/execsql2_extras/config_settings.sqlite,sha256=aY5cxR7Q7J6zJ4bC9lu5mHUrhy211Cq3MNKPQVCt02E,20480
123
- execsql2-2.25.0.data/data/execsql2_extras/example_config_prompt.sql,sha256=2e8KzzVWhho8KxYVHETSVmZdhW7wodioDsqBLSL6m4s,7487
124
- execsql2-2.25.0.data/data/execsql2_extras/make_config_db.sql,sha256=WwyC6dK-Eh5CAVppiBCDHqiI1_wEI9U95Ytpr4lsZkg,8726
125
- execsql2-2.25.0.data/data/execsql2_extras/md_compare.sql,sha256=qYYVAjSeHZzjszxV3Bv6bg8Ckbq2kMHl87_gh4sywMU,24140
126
- execsql2-2.25.0.data/data/execsql2_extras/md_glossary.sql,sha256=hkZ2Onn57LAKKsuXxzhR8tPtcWXkmWEQkwPE58-Tm2k,10796
127
- execsql2-2.25.0.data/data/execsql2_extras/md_upsert.sql,sha256=_CAK4BzEboRXTNy03SJR-oOjcEdSNMuRBPL6noWUptY,112560
128
- execsql2-2.25.0.data/data/execsql2_extras/pg_compare.sql,sha256=1zJd4hVUKHR0tncc2qTBC9B4qVV4Us2ITkJpsjN3tMw,24352
129
- execsql2-2.25.0.data/data/execsql2_extras/pg_glossary.sql,sha256=IKuwna-_8b20ljSkXZruuiQigrCpo7ueQdUqd1MXiuI,9908
130
- execsql2-2.25.0.data/data/execsql2_extras/pg_upsert.sql,sha256=HpPJtTHvpEjQy03j-3iPxDEOHMRkudOg7O4D4YR38UI,108315
131
- execsql2-2.25.0.data/data/execsql2_extras/script_template.sql,sha256=2J35ddZPguJ-vwTsz83wErv0jiWUyJcdW_JM0mNKDXA,11155
132
- execsql2-2.25.0.data/data/execsql2_extras/ss_compare.sql,sha256=j1qVNUPXQsEU7-DoVgDJCGcE0EuIl7whLBT3fgeiMAo,24833
133
- execsql2-2.25.0.data/data/execsql2_extras/ss_glossary.sql,sha256=2gLxv34xzKt0vy7hSzJH7a9JiMC3ETrv9MofxQwAibU,13065
134
- execsql2-2.25.0.data/data/execsql2_extras/ss_upsert.sql,sha256=G_8rQ0VzuKIZHWs24O_WrfzpC5S27R1JsL-bFBR3SUQ,117730
135
- execsql2-2.25.0.dist-info/METADATA,sha256=8-U51LILehuLSC5E0zYP_tYCuJEtff0DkTsMSQ4RvHg,25703
136
- execsql2-2.25.0.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
137
- execsql2-2.25.0.dist-info/entry_points.txt,sha256=jOf53-4IVGjEZeNYUOF1GGaiPNfVZcNKxP3vDFKEyMA,58
138
- execsql2-2.25.0.dist-info/licenses/LICENSE.txt,sha256=LBdhuxejF8_bLCHZ2kWfmDXpDGUu914Gbd6_3JjCRe0,676
139
- execsql2-2.25.0.dist-info/licenses/NOTICE,sha256=McYzgxYav3U1OaVsY4Su1sfBrfmplpRdA9b6-gCDQCg,342
140
- execsql2-2.25.0.dist-info/RECORD,,
121
+ execsql2-2.25.1.data/data/execsql2_extras/README.md,sha256=7SG9FbBpdqkgQTTt1aBTbCgymBZyV0rLrQMQrendzxs,5032
122
+ execsql2-2.25.1.data/data/execsql2_extras/config_settings.sqlite,sha256=aY5cxR7Q7J6zJ4bC9lu5mHUrhy211Cq3MNKPQVCt02E,20480
123
+ execsql2-2.25.1.data/data/execsql2_extras/example_config_prompt.sql,sha256=2e8KzzVWhho8KxYVHETSVmZdhW7wodioDsqBLSL6m4s,7487
124
+ execsql2-2.25.1.data/data/execsql2_extras/make_config_db.sql,sha256=WwyC6dK-Eh5CAVppiBCDHqiI1_wEI9U95Ytpr4lsZkg,8726
125
+ execsql2-2.25.1.data/data/execsql2_extras/md_compare.sql,sha256=qYYVAjSeHZzjszxV3Bv6bg8Ckbq2kMHl87_gh4sywMU,24140
126
+ execsql2-2.25.1.data/data/execsql2_extras/md_glossary.sql,sha256=hkZ2Onn57LAKKsuXxzhR8tPtcWXkmWEQkwPE58-Tm2k,10796
127
+ execsql2-2.25.1.data/data/execsql2_extras/md_upsert.sql,sha256=_CAK4BzEboRXTNy03SJR-oOjcEdSNMuRBPL6noWUptY,112560
128
+ execsql2-2.25.1.data/data/execsql2_extras/pg_compare.sql,sha256=1zJd4hVUKHR0tncc2qTBC9B4qVV4Us2ITkJpsjN3tMw,24352
129
+ execsql2-2.25.1.data/data/execsql2_extras/pg_glossary.sql,sha256=IKuwna-_8b20ljSkXZruuiQigrCpo7ueQdUqd1MXiuI,9908
130
+ execsql2-2.25.1.data/data/execsql2_extras/pg_upsert.sql,sha256=HpPJtTHvpEjQy03j-3iPxDEOHMRkudOg7O4D4YR38UI,108315
131
+ execsql2-2.25.1.data/data/execsql2_extras/script_template.sql,sha256=2J35ddZPguJ-vwTsz83wErv0jiWUyJcdW_JM0mNKDXA,11155
132
+ execsql2-2.25.1.data/data/execsql2_extras/ss_compare.sql,sha256=j1qVNUPXQsEU7-DoVgDJCGcE0EuIl7whLBT3fgeiMAo,24833
133
+ execsql2-2.25.1.data/data/execsql2_extras/ss_glossary.sql,sha256=2gLxv34xzKt0vy7hSzJH7a9JiMC3ETrv9MofxQwAibU,13065
134
+ execsql2-2.25.1.data/data/execsql2_extras/ss_upsert.sql,sha256=G_8rQ0VzuKIZHWs24O_WrfzpC5S27R1JsL-bFBR3SUQ,117730
135
+ execsql2-2.25.1.dist-info/METADATA,sha256=vA1V2t6wNC3-PP3Jx3rj-Nmwc6rVT90Df2T02OehzBQ,25703
136
+ execsql2-2.25.1.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
137
+ execsql2-2.25.1.dist-info/entry_points.txt,sha256=jOf53-4IVGjEZeNYUOF1GGaiPNfVZcNKxP3vDFKEyMA,58
138
+ execsql2-2.25.1.dist-info/licenses/LICENSE.txt,sha256=LBdhuxejF8_bLCHZ2kWfmDXpDGUu914Gbd6_3JjCRe0,676
139
+ execsql2-2.25.1.dist-info/licenses/NOTICE,sha256=McYzgxYav3U1OaVsY4Su1sfBrfmplpRdA9b6-gCDQCg,342
140
+ execsql2-2.25.1.dist-info/RECORD,,