execsql2 2.0.1__py3-none-any.whl → 2.1.2__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 (90) hide show
  1. execsql/cli.py +322 -108
  2. execsql/config.py +134 -114
  3. execsql/db/access.py +89 -65
  4. execsql/db/base.py +97 -68
  5. execsql/db/dsn.py +45 -29
  6. execsql/db/duckdb.py +4 -5
  7. execsql/db/factory.py +27 -27
  8. execsql/db/firebird.py +30 -18
  9. execsql/db/mysql.py +38 -14
  10. execsql/db/oracle.py +58 -33
  11. execsql/db/postgres.py +68 -28
  12. execsql/db/sqlite.py +36 -27
  13. execsql/db/sqlserver.py +45 -30
  14. execsql/exceptions.py +68 -64
  15. execsql/exporters/__init__.py +1 -1
  16. execsql/exporters/base.py +42 -17
  17. execsql/exporters/delimited.py +60 -59
  18. execsql/exporters/duckdb.py +8 -12
  19. execsql/exporters/feather.py +32 -24
  20. execsql/exporters/html.py +33 -30
  21. execsql/exporters/json.py +18 -17
  22. execsql/exporters/latex.py +11 -13
  23. execsql/exporters/ods.py +50 -46
  24. execsql/exporters/parquet.py +32 -0
  25. execsql/exporters/pretty.py +16 -15
  26. execsql/exporters/raw.py +9 -11
  27. execsql/exporters/sqlite.py +38 -38
  28. execsql/exporters/templates.py +15 -72
  29. execsql/exporters/values.py +13 -12
  30. execsql/exporters/xls.py +26 -26
  31. execsql/exporters/xml.py +12 -12
  32. execsql/exporters/zip.py +0 -3
  33. execsql/gui/__init__.py +2 -2
  34. execsql/gui/console.py +0 -1
  35. execsql/gui/desktop.py +6 -7
  36. execsql/gui/tui.py +8 -14
  37. execsql/importers/base.py +6 -9
  38. execsql/importers/csv.py +10 -17
  39. execsql/importers/feather.py +16 -22
  40. execsql/importers/ods.py +3 -4
  41. execsql/importers/xls.py +5 -6
  42. execsql/metacommands/__init__.py +8 -8
  43. execsql/metacommands/conditions.py +41 -33
  44. execsql/metacommands/connect.py +113 -99
  45. execsql/metacommands/control.py +38 -26
  46. execsql/metacommands/data.py +35 -33
  47. execsql/metacommands/debug.py +13 -9
  48. execsql/metacommands/io.py +288 -229
  49. execsql/metacommands/prompt.py +179 -157
  50. execsql/metacommands/script_ext.py +11 -9
  51. execsql/metacommands/system.py +44 -25
  52. execsql/models.py +9 -16
  53. execsql/parser.py +10 -10
  54. execsql/script.py +183 -157
  55. execsql/state.py +170 -208
  56. execsql/types.py +46 -81
  57. execsql/utils/auth.py +114 -14
  58. execsql/utils/crypto.py +31 -4
  59. execsql/utils/datetime.py +7 -7
  60. execsql/utils/errors.py +34 -29
  61. execsql/utils/fileio.py +90 -55
  62. execsql/utils/gui.py +22 -23
  63. execsql/utils/mail.py +15 -17
  64. execsql/utils/numeric.py +2 -3
  65. execsql/utils/regex.py +9 -12
  66. execsql/utils/strings.py +10 -12
  67. execsql/utils/timer.py +0 -2
  68. {execsql2-2.0.1.data → execsql2-2.1.2.data}/data/execsql2_extras/execsql.conf +1 -1
  69. execsql2-2.1.2.dist-info/METADATA +300 -0
  70. execsql2-2.1.2.dist-info/RECORD +96 -0
  71. execsql2-2.0.1.dist-info/METADATA +0 -406
  72. execsql2-2.0.1.dist-info/RECORD +0 -95
  73. {execsql2-2.0.1.data → execsql2-2.1.2.data}/data/execsql2_extras/READ_ME.rst +0 -0
  74. {execsql2-2.0.1.data → execsql2-2.1.2.data}/data/execsql2_extras/config_settings.sqlite +0 -0
  75. {execsql2-2.0.1.data → execsql2-2.1.2.data}/data/execsql2_extras/example_config_prompt.sql +0 -0
  76. {execsql2-2.0.1.data → execsql2-2.1.2.data}/data/execsql2_extras/make_config_db.sql +0 -0
  77. {execsql2-2.0.1.data → execsql2-2.1.2.data}/data/execsql2_extras/md_compare.sql +0 -0
  78. {execsql2-2.0.1.data → execsql2-2.1.2.data}/data/execsql2_extras/md_glossary.sql +0 -0
  79. {execsql2-2.0.1.data → execsql2-2.1.2.data}/data/execsql2_extras/md_upsert.sql +0 -0
  80. {execsql2-2.0.1.data → execsql2-2.1.2.data}/data/execsql2_extras/pg_compare.sql +0 -0
  81. {execsql2-2.0.1.data → execsql2-2.1.2.data}/data/execsql2_extras/pg_glossary.sql +0 -0
  82. {execsql2-2.0.1.data → execsql2-2.1.2.data}/data/execsql2_extras/pg_upsert.sql +0 -0
  83. {execsql2-2.0.1.data → execsql2-2.1.2.data}/data/execsql2_extras/script_template.sql +0 -0
  84. {execsql2-2.0.1.data → execsql2-2.1.2.data}/data/execsql2_extras/ss_compare.sql +0 -0
  85. {execsql2-2.0.1.data → execsql2-2.1.2.data}/data/execsql2_extras/ss_glossary.sql +0 -0
  86. {execsql2-2.0.1.data → execsql2-2.1.2.data}/data/execsql2_extras/ss_upsert.sql +0 -0
  87. {execsql2-2.0.1.dist-info → execsql2-2.1.2.dist-info}/WHEEL +0 -0
  88. {execsql2-2.0.1.dist-info → execsql2-2.1.2.dist-info}/entry_points.txt +0 -0
  89. {execsql2-2.0.1.dist-info → execsql2-2.1.2.dist-info}/licenses/LICENSE.txt +0 -0
  90. {execsql2-2.0.1.dist-info → execsql2-2.1.2.dist-info}/licenses/NOTICE +0 -0
execsql/utils/mail.py CHANGED
@@ -10,10 +10,8 @@ which compose and send email via SMTP using settings from
10
10
  metacommand and the halt/cancel email-notification hooks.
11
11
  """
12
12
 
13
- import io
14
- import os
15
13
  import re
16
- from typing import List, Optional
14
+ from pathlib import Path
17
15
 
18
16
  import execsql.state as _state
19
17
  from execsql.exceptions import ErrInfo
@@ -67,9 +65,9 @@ class Mailer:
67
65
  send_from: str,
68
66
  send_to: str,
69
67
  subject: str,
70
- msg_content: Optional[str],
71
- content_filename: Optional[str] = None,
72
- attach_filename: Optional[str] = None,
68
+ msg_content: str | None,
69
+ content_filename: str | None = None,
70
+ attach_filename: str | None = None,
73
71
  ) -> None:
74
72
  global smtplib
75
73
  global MIMEMultipart
@@ -89,24 +87,24 @@ class Mailer:
89
87
  if conf.email_format == "html":
90
88
  msg_body = "<html><head>"
91
89
  if conf.email_css is not None:
92
- msg_body += "<style>%s</style>" % conf.email_css
93
- msg_body += "</head><body>%s" % msg_content if msg_content else ""
90
+ msg_body += f"<style>{conf.email_css}</style>"
91
+ msg_body += f"</head><body>{msg_content}" if msg_content else ""
94
92
  else:
95
93
  msg_body = msg_content if msg_content else ""
96
94
  if content_filename is not None:
97
- msg_body += "\n" + io.open(content_filename, "rt").read()
95
+ with open(content_filename) as content_file:
96
+ msg_body += "\n" + content_file.read()
98
97
  if conf.email_format == "html":
99
98
  msg_body += "</body></html>"
100
99
  msg.attach(MIMEText(msg_body, "html"))
101
100
  else:
102
101
  msg.attach(MIMEText(msg_body, "plain"))
103
102
  if attach_filename is not None:
104
- f = io.open(attach_filename, "rb")
105
- fdata = MIMEBase("application", "octet-stream")
106
- fdata.set_payload(f.read())
107
- f.close()
103
+ with open(attach_filename, "rb") as f:
104
+ fdata = MIMEBase("application", "octet-stream")
105
+ fdata.set_payload(f.read())
108
106
  encoders.encode_base64(fdata)
109
- fdata.add_header("Content-Disposition", "attachment", filename=os.path.basename(attach_filename))
107
+ fdata.add_header("Content-Disposition", "attachment", filename=Path(attach_filename).name)
110
108
  msg.attach(fdata)
111
109
  self.smtpconn.sendmail(send_from, recipients, msg.as_string())
112
110
 
@@ -117,9 +115,9 @@ class MailSpec:
117
115
  send_from: str,
118
116
  send_to: str,
119
117
  subject: str,
120
- msg_content: Optional[str],
121
- content_filename: Optional[str] = None,
122
- attach_filename: Optional[str] = None,
118
+ msg_content: str | None,
119
+ content_filename: str | None = None,
120
+ attach_filename: str | None = None,
123
121
  repeatable: bool = False,
124
122
  ) -> None:
125
123
  self.send_from = send_from
execsql/utils/numeric.py CHANGED
@@ -12,9 +12,8 @@ Provides:
12
12
  string, used by the ``FORMAT NUMBER`` substitution variant.
13
13
  """
14
14
 
15
- import math
16
15
  import re
17
- from typing import Any, Optional, Union
16
+ from typing import Any
18
17
 
19
18
 
20
19
  def leading_zero_num(dataval: Any) -> bool:
@@ -42,7 +41,7 @@ def leading_zero_num(dataval: Any) -> bool:
42
41
  return False
43
42
 
44
43
 
45
- def as_numeric(strval: Any) -> Optional[Union[int, float]]:
44
+ def as_numeric(strval: Any) -> int | float | None:
46
45
  # Converts the given value to an int, a float, or None.
47
46
  if type(strval) in (int, float):
48
47
  return strval
execsql/utils/regex.py CHANGED
@@ -13,19 +13,17 @@ dispatch regexes at module load time:
13
13
  """
14
14
 
15
15
  import os
16
- import re
17
- from typing import List, Optional, Tuple
18
16
 
19
17
 
20
18
  def ins_rxs(rx_list: tuple, fragment1: object, fragment2: object) -> tuple:
21
19
  # Returns a tuple of all strings consisting of elements of the 'rx_list' tuple
22
20
  # inserted between 'fragment1' and 'fragment2'. The fragments may themselves
23
21
  # be tuples.
24
- if type(fragment1) != tuple:
22
+ if not isinstance(fragment1, tuple):
25
23
  fragment1 = (fragment1,)
26
24
  if fragment2 is None:
27
25
  fragment2 = ("",)
28
- if type(fragment2) != tuple:
26
+ if not isinstance(fragment2, tuple):
29
27
  fragment2 = (fragment2,)
30
28
  rv = []
31
29
  for te in rx_list:
@@ -36,10 +34,10 @@ def ins_rxs(rx_list: tuple, fragment1: object, fragment2: object) -> tuple:
36
34
 
37
35
 
38
36
  def ins_quoted_rx(fragment1: object, fragment2: object, rx: str) -> tuple:
39
- return ins_rxs((rx, r'"%s"' % rx), fragment1, fragment2)
37
+ return ins_rxs((rx, rf'"{rx}"'), fragment1, fragment2)
40
38
 
41
39
 
42
- def ins_schema_rxs(fragment1: object, fragment2: object, suffix: Optional[str] = None) -> tuple:
40
+ def ins_schema_rxs(fragment1: object, fragment2: object, suffix: str | None = None) -> tuple:
43
41
  schema_exprs = (
44
42
  r'"(?P<schema>[A-Za-z0-9_\- ]+)"',
45
43
  r"(?P<schema>[A-Za-z0-9_\-]+)",
@@ -50,7 +48,7 @@ def ins_schema_rxs(fragment1: object, fragment2: object, suffix: Optional[str] =
50
48
  return ins_rxs(schema_exprs, fragment1, fragment2)
51
49
 
52
50
 
53
- def ins_table_rxs(fragment1: object, fragment2: object, suffix: Optional[str] = None) -> tuple:
51
+ def ins_table_rxs(fragment1: object, fragment2: object, suffix: str | None = None) -> tuple:
54
52
  tbl_exprs = (
55
53
  r'(?:"(?P<schema>[A-Za-z0-9_\- ]+)"\.)?"(?P<table>[A-Za-z0-9_\-\# ]+)"',
56
54
  r"(?:(?P<schema>[A-Za-z0-9_\-]+)\.)?(?P<table>[A-Za-z0-9_\-\#]+)",
@@ -79,13 +77,12 @@ def ins_table_list_rxs(fragment1: object, fragment2: object) -> tuple:
79
77
  def ins_fn_rxs(fragment1: object, fragment2: object, symbolicname: str = "filename") -> tuple:
80
78
  if os.name == "posix":
81
79
  fns = (
82
- r"(?P<%s>[\w\.\-\\\/\'~`!@#$^&()+={}\[\]:;,]*[\w\.\-\\\/\'~`!@#$^&(+={}\[\]:;,])" % symbolicname,
83
- r'"(?P<%s>[\w\s\.\-\\\/\'~`!@#$^&()+={}\[\]:;,]+)"' % symbolicname,
80
+ rf"(?P<{symbolicname}>[\w\.\-\\\/\'~`!@#$^&()+={{}}\[\]:;,]*[\w\.\-\\\/\'~`!@#$^&(+={{}}\[\]:;,])",
81
+ rf'"(?P<{symbolicname}>[\w\s\.\-\\\/\'~`!@#$^&()+={{}}\[\]:;,]+)"',
84
82
  )
85
83
  else:
86
84
  fns = (
87
- r"(?P<%s>([A-Z]\:)?[\w+\,()!@#$^&\+=;\'{}\[\]~`\.\-\\\/]*[\w+\,(!@#$^&\+=;\'{}\[\]~`\.\-\\\/])"
88
- % symbolicname,
89
- r'"(?P<%s>([A-Z]\:)?[\w+\,()!@#$^&\+=;\'{}\[\]~`\s\.\-\\\/]+)"' % symbolicname,
85
+ rf"(?P<{symbolicname}>([A-Z]\:)?[\w+\,()!@#$^&\+=;\'{{}}\[\]~`\.\-\\\/]*[\w+\,(!@#$^&\+=;\'{{}}\[\]~`\.\-\\\/])",
86
+ rf'"(?P<{symbolicname}>([A-Z]\:)?[\w+\,()!@#$^&\+=;\'{{}}\[\]~`\s\.\-\\\/]+)"',
90
87
  )
91
88
  return ins_rxs(fns, fragment1, fragment2)
execsql/utils/strings.py CHANGED
@@ -22,7 +22,6 @@ identifiers:
22
22
  """
23
23
 
24
24
  import re
25
- from typing import Any, List, Optional
26
25
 
27
26
 
28
27
  def clean_word(word: str) -> str:
@@ -34,7 +33,7 @@ def clean_word(word: str) -> str:
34
33
  return s1
35
34
 
36
35
 
37
- def clean_words(wordlist: List[str]) -> List[str]:
36
+ def clean_words(wordlist: list[str]) -> list[str]:
38
37
  return [clean_word(w) for w in wordlist]
39
38
 
40
39
 
@@ -50,7 +49,7 @@ def trim_word(word: str, blr: str) -> str:
50
49
  return word
51
50
 
52
51
 
53
- def trim_words(wordlist: List[str], nblr: str) -> List[str]:
52
+ def trim_words(wordlist: list[str], nblr: str) -> list[str]:
54
53
  return [trim_word(w, nblr) for w in wordlist]
55
54
 
56
55
 
@@ -63,16 +62,16 @@ def fold_word(word: str, foldspec: str) -> str:
63
62
  return word
64
63
 
65
64
 
66
- def fold_words(wordlist: List[str], foldspec: str) -> List[str]:
65
+ def fold_words(wordlist: list[str], foldspec: str) -> list[str]:
67
66
  return [fold_word(w, foldspec) for w in wordlist]
68
67
 
69
68
 
70
- def dedup_words(wordlist: List[str]) -> List[str]:
69
+ def dedup_words(wordlist: list[str]) -> list[str]:
71
70
  # Adds an item number suffix to duplicated words.
72
71
  w2 = wordlist
73
72
  dup_ix = [ix for ix, w in enumerate(w2) if w.lower() in [wrd.lower() for wrd in w2[:ix]]]
74
73
  while len(dup_ix) > 0:
75
- w2 = [w + "_%s" % str(ix + 1) if ix in dup_ix else w for ix, w in enumerate(w2)]
74
+ w2 = [w + f"_{str(ix + 1)}" if ix in dup_ix else w for ix, w in enumerate(w2)]
76
75
  dup_ix = [ix for ix, w in enumerate(w2) if w.lower() in [wrd.lower() for wrd in w2[:ix]]]
77
76
  return w2
78
77
 
@@ -206,10 +205,7 @@ def encodings_match(enc1: str, enc2: str) -> bool:
206
205
  "gb18030",
207
206
  ),
208
207
  )
209
- for eq in equivalents:
210
- if enc1 in eq and enc2 in eq:
211
- return True
212
- return False
208
+ return any(enc1 in eq and enc2 in eq for eq in equivalents)
213
209
 
214
210
 
215
211
  def wo_quotes(argstr: str) -> str:
@@ -247,8 +243,10 @@ def get_subvarset(varname: str, metacommandline: str) -> tuple:
247
243
  raise ErrInfo(
248
244
  type="cmd",
249
245
  command_text=metacommandline,
250
- other_msg="Outer-scope referent variable (%s) has no matching local variable (%s)."
251
- % (re.sub("^[~]", "+", varname), varname),
246
+ other_msg="Outer-scope referent variable ({}) has no matching local variable ({}).".format(
247
+ re.sub("^[~]", "+", varname),
248
+ varname,
249
+ ),
252
250
  )
253
251
  # Global or local variable
254
252
  else:
execsql/utils/timer.py CHANGED
@@ -13,9 +13,7 @@ Provides:
13
13
  """
14
14
 
15
15
  import signal
16
- import threading
17
16
  import time
18
- from typing import Callable, Optional
19
17
 
20
18
  from execsql.exceptions import ExecSqlTimeoutError
21
19
 
@@ -177,7 +177,7 @@
177
177
  #outfile_open_timeout=600
178
178
 
179
179
  # The name of the template processor to be used when exporting data using a template.
180
- # Values: jinja or airspeed.
180
+ # Value: jinja.
181
181
  #template_processor=
182
182
 
183
183
  # The size of the internal buffer used when the EXPORT metacommand exports data to
@@ -0,0 +1,300 @@
1
+ Metadata-Version: 2.4
2
+ Name: execsql2
3
+ Version: 2.1.2
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
+ Project-URL: Repository, https://github.com/geocoug/execsql
6
+ Project-URL: Issues, https://github.com/geocoug/execsql/issues
7
+ Author-email: Dreas Nielsen <cortice@tutanota.com>
8
+ Maintainer-email: Caleb Grant <grantcaleb22@gmail.com>
9
+ License: execsql2 — a fork of execsql.py
10
+ Copyright (c) 2007-2025 R.Dreas Nielsen
11
+ Copyright (c) 2026-present Caleb Grant
12
+
13
+ This program is free software: you can redistribute it and/or modify it under
14
+ the terms of the GNU General Public License as published by the Free Software
15
+ Foundation, either version 3 of the License, or (at your option) any later
16
+ version. This program is distributed in the hope that it will be useful, but
17
+ WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
18
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
19
+ details. The GNU General Public License is available at
20
+ http://www.gnu.org/licenses/.
21
+ License-File: LICENSE.txt
22
+ License-File: NOTICE
23
+ Keywords: Access,CSV,DBMS,DuckDB,ETL,Firebird,HTML,JSON,LaTeX,MariaDB,MySQL,ODBC,OpenDocument,Oracle,PostgreSQL,Postgres,SQL,SQL Server,SQLite,TSV,XML,database,export,import,query,script,table
24
+ Classifier: Development Status :: 5 - Production/Stable
25
+ Classifier: Environment :: Console
26
+ Classifier: Intended Audience :: End Users/Desktop
27
+ Classifier: Intended Audience :: Information Technology
28
+ Classifier: Intended Audience :: System Administrators
29
+ Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
30
+ Classifier: Natural Language :: English
31
+ Classifier: Operating System :: OS Independent
32
+ Classifier: Programming Language :: Python :: 3
33
+ Classifier: Programming Language :: Python :: 3.10
34
+ Classifier: Programming Language :: Python :: 3.11
35
+ Classifier: Programming Language :: Python :: 3.12
36
+ Classifier: Programming Language :: Python :: 3.13
37
+ Classifier: Topic :: Database
38
+ Classifier: Topic :: Database :: Front-Ends
39
+ Requires-Python: >=3.10
40
+ Requires-Dist: rich>=13.0
41
+ Requires-Dist: sqlglot>=25.0
42
+ Requires-Dist: textual>=0.47.0
43
+ Requires-Dist: typer>=0.12
44
+ Provides-Extra: all
45
+ Requires-Dist: duckdb; extra == 'all'
46
+ Requires-Dist: firebird-driver; extra == 'all'
47
+ Requires-Dist: jinja2; extra == 'all'
48
+ Requires-Dist: keyring; extra == 'all'
49
+ Requires-Dist: odfpy; extra == 'all'
50
+ Requires-Dist: openpyxl; extra == 'all'
51
+ Requires-Dist: oracledb; extra == 'all'
52
+ Requires-Dist: polars; extra == 'all'
53
+ Requires-Dist: psycopg2-binary; extra == 'all'
54
+ Requires-Dist: pymysql; extra == 'all'
55
+ Requires-Dist: pyodbc; extra == 'all'
56
+ Requires-Dist: tables; extra == 'all'
57
+ Requires-Dist: xlrd; extra == 'all'
58
+ Provides-Extra: all-db
59
+ Requires-Dist: duckdb; extra == 'all-db'
60
+ Requires-Dist: firebird-driver; extra == 'all-db'
61
+ Requires-Dist: oracledb; extra == 'all-db'
62
+ Requires-Dist: psycopg2-binary; extra == 'all-db'
63
+ Requires-Dist: pymysql; extra == 'all-db'
64
+ Requires-Dist: pyodbc; extra == 'all-db'
65
+ Provides-Extra: auth
66
+ Requires-Dist: keyring; extra == 'auth'
67
+ Provides-Extra: dev
68
+ Requires-Dist: build>=1.2.2.post1; extra == 'dev'
69
+ Requires-Dist: bump-my-version>=1.2.7; extra == 'dev'
70
+ Requires-Dist: markdown-include>=0.8; extra == 'dev'
71
+ Requires-Dist: mkdocstrings-python>=2.0.3; extra == 'dev'
72
+ Requires-Dist: pre-commit>=3.5.0; extra == 'dev'
73
+ Requires-Dist: pytest-cov>=5.0.0; extra == 'dev'
74
+ Requires-Dist: ruff>=0.4; extra == 'dev'
75
+ Requires-Dist: tox-uv>=1.13.1; extra == 'dev'
76
+ Requires-Dist: twine>=6.1.0; extra == 'dev'
77
+ Requires-Dist: zensical>=0.0.28; extra == 'dev'
78
+ Provides-Extra: duckdb
79
+ Requires-Dist: duckdb; extra == 'duckdb'
80
+ Provides-Extra: firebird
81
+ Requires-Dist: firebird-driver; extra == 'firebird'
82
+ Provides-Extra: formats
83
+ Requires-Dist: jinja2; extra == 'formats'
84
+ Requires-Dist: odfpy; extra == 'formats'
85
+ Requires-Dist: openpyxl; extra == 'formats'
86
+ Requires-Dist: polars; extra == 'formats'
87
+ Requires-Dist: tables; extra == 'formats'
88
+ Requires-Dist: xlrd; extra == 'formats'
89
+ Provides-Extra: mssql
90
+ Requires-Dist: pyodbc; extra == 'mssql'
91
+ Provides-Extra: mysql
92
+ Requires-Dist: pymysql; extra == 'mysql'
93
+ Provides-Extra: odbc
94
+ Requires-Dist: pyodbc; extra == 'odbc'
95
+ Provides-Extra: oracle
96
+ Requires-Dist: oracledb; extra == 'oracle'
97
+ Provides-Extra: postgres
98
+ Requires-Dist: psycopg2-binary; extra == 'postgres'
99
+ Description-Content-Type: text/markdown
100
+
101
+ > [!WARNING]
102
+ > **This project is in active development and is not yet stable.**
103
+ > The codebase is being modernized from the upstream monolith into a modular package.
104
+ > APIs, CLI behavior, and configuration options may change without notice.
105
+ > Not recommended for production use. For the stable upstream release, see [execsql](https://execsql.readthedocs.io/).
106
+
107
+ <div align="center">
108
+
109
+ <img src="https://execsql2.readthedocs.io/en/latest/images/execsql_logo_01.png" alt="execsql logo">
110
+
111
+ *Multi-DBMS SQL script processor.*
112
+
113
+ </div>
114
+
115
+ <div align="center">
116
+
117
+ [![CI/CD](https://github.com/geocoug/execsql/actions/workflows/ci-cd.yml/badge.svg)](https://github.com/geocoug/execsql/actions/workflows/ci-cd.yml)
118
+ [![codecov](https://codecov.io/gh/geocoug/execsql/graph/badge.svg)](https://codecov.io/gh/geocoug/execsql)
119
+ [![Docs](https://readthedocs.org/projects/execsql2/badge/)](https://execsql2.readthedocs.io/)
120
+ [![PyPI](https://img.shields.io/pypi/v/execsql2)](https://pypi.org/project/execsql2/)
121
+ [![Python](https://img.shields.io/pypi/pyversions/execsql2)](https://pypi.org/project/execsql2/)
122
+ [![License](https://img.shields.io/pypi/l/execsql2)](https://pypi.org/project/execsql2/)
123
+ [![Downloads](https://pepy.tech/badge/execsql2/month)](https://pepy.tech/project/execsql2)
124
+
125
+ </div>
126
+
127
+ # Fork
128
+
129
+ *execsql2* is a maintained fork of [execsql](https://execsql.readthedocs.io/) originally authored by R.Dreas Nielsen. The upstream project is no longer actively maintained. This fork is maintained by [Caleb Grant](https://github.com/geocoug) and is distributed on PyPI as `execsql2`. Complete documentation is at [execsql2.readthedocs.io](https://execsql2.readthedocs.io/).
130
+
131
+ # Overview
132
+
133
+ *execsql* runs SQL scripts against PostgreSQL, MySQL/MariaDB, SQLite, DuckDB, MS-SQL-Server, MS-Access, Firebird, Oracle, or an ODBC DSN. In addition to standard SQL, it supports a set of metacommands (embedded in SQL comments) for importing and exporting data, copying data between databases, conditional execution, looping, substitution variables, and interactive prompts. Because metacommands live in SQL comments, scripts remain valid SQL and are ignored by other tools such as `psql` or `sqlcmd`.
134
+
135
+ # Installation
136
+
137
+ ```bash
138
+ pip install execsql2
139
+ ```
140
+
141
+ Optional extras install database drivers and feature bundles:
142
+
143
+ ```bash
144
+ # Database drivers
145
+ pip install execsql2[postgres] # PostgreSQL (psycopg2-binary)
146
+ pip install execsql2[mysql] # MySQL / MariaDB (pymysql)
147
+ pip install execsql2[mssql] # SQL Server (pyodbc)
148
+ pip install execsql2[duckdb] # DuckDB
149
+ pip install execsql2[firebird] # Firebird (firebird-driver)
150
+ pip install execsql2[oracle] # Oracle (oracledb)
151
+ pip install execsql2[odbc] # ODBC DSN (pyodbc)
152
+
153
+ # Feature bundles
154
+ pip install execsql2[formats] # ODS, Excel, Jinja2, Feather, Parquet, HDF5
155
+ pip install execsql2[auth] # OS keyring integration
156
+
157
+ # Convenience
158
+ pip install execsql2[all-db] # All database drivers
159
+ pip install execsql2[all] # Everything
160
+ ```
161
+
162
+ SQLite connections use Python's standard library and require no additional packages.
163
+
164
+ # Usage
165
+
166
+ ```text
167
+ execsql [OPTIONS] SQL_SCRIPT [SERVER DATABASE | DATABASE_FILE]
168
+ ```
169
+
170
+ Examples:
171
+
172
+ ```bash
173
+ execsql -tp script.sql myserver mydb # PostgreSQL
174
+ execsql -tm script.sql myserver mydb # MySQL / MariaDB
175
+ execsql -ts script.sql myserver mydb # SQL Server
176
+ execsql -tl script.sql mydb.sqlite # SQLite
177
+ execsql -tk script.sql mydb.duckdb # DuckDB
178
+ execsql -to script.sql myserver myservice # Oracle
179
+ execsql script.sql # read connection from config file
180
+ ```
181
+
182
+ ## Supported Databases
183
+
184
+ | Flag | Database |
185
+ | ---- | --------------- |
186
+ | `p` | PostgreSQL |
187
+ | `m` | MySQL / MariaDB |
188
+ | `s` | MS SQL Server |
189
+ | `l` | SQLite |
190
+ | `k` | DuckDB |
191
+ | `a` | MS Access |
192
+ | `f` | Firebird |
193
+ | `o` | Oracle |
194
+ | `d` | ODBC DSN |
195
+
196
+ ## Options
197
+
198
+ | Flag | Description |
199
+ | ----------------------------------- | --------------------------------------------------------------- |
200
+ | `-t {p,m,s,l,k,a,f,o,d}` | Database type |
201
+ | `-u USER` | Database username |
202
+ | `-p PORT` | Server port |
203
+ | `-a VALUE` | Set substitution variable `$ARG_x` |
204
+ | `-c SCRIPT` | Execute inline SQL or metacommand string |
205
+ | `-d` | Auto-create export directories |
206
+ | `-f ENCODING` | Script file encoding (default: UTF-8) |
207
+ | `-l` | Write run log to `~/execsql.log` |
208
+ | `-m` | List metacommands and exit |
209
+ | `-n` | Create a new SQLite or PostgreSQL database if it does not exist |
210
+ | `-v {0,1,2,3}` | GUI level (0=none, 1=password, 2=selection, 3=full) |
211
+ | `--gui-framework {tkinter,textual}` | GUI framework for interactive prompts |
212
+ | `-w` | Skip password prompt when a username is supplied |
213
+
214
+ Run `execsql --help` for the full option list, or `execsql -m` to list all metacommands.
215
+
216
+ # Features
217
+
218
+ - Import data from CSV, TSV, Excel, OpenDocument, Feather, or Parquet files into a database table.
219
+ - Export query results in 15+ formats including CSV, TSV, JSON, XML, HTML, LaTeX, OpenDocument, Feather, HDF5, DuckDB, SQLite, plain text, and Jinja2 templates.
220
+ - Copy data between databases, including across different DBMS types.
221
+ - Conditionally execute SQL and metacommands using `IF`/`ELSE`/`ENDIF` based on data values, DBMS type, or user input.
222
+ - Loop over blocks of SQL and metacommands using `LOOP`/`ENDLOOP`.
223
+ - Use substitution variables (`SUB`, `$ARG_x`, built-in variables like `$date_tag`) to parameterize scripts.
224
+ - Include or chain scripts with `INCLUDE` and `SCRIPT`.
225
+ - Display query results in a GUI dialog; optionally prompt the user to select a row, enter a value, or submit a form.
226
+ - Write status messages or tabular output to the console or a file during execution.
227
+ - Automatically log each run, recording databases used, scripts executed, and user responses.
228
+
229
+ # An Illustration
230
+
231
+ The following script demonstrates metacommands and substitution variables. Lines prefixed with `-- !x!` are metacommands; identifiers wrapped in `!!` are substitution variables.
232
+
233
+ ```sql
234
+ -- ==== Configuration ====
235
+ -- Put the (date-tagged) logfile name in the 'inputfile' substitution variable.
236
+ -- !x! SUB inputfile logs/errors_!!$date_tag!!
237
+ -- Ensure that the export directory will be created if necessary.
238
+ -- !x! CONFIG MAKE_EXPORT_DIRS Yes
239
+
240
+ -- ==== Display Fatal Errors ====
241
+ -- !x! IF(file_exists(!!inputfile!!))
242
+ -- Import the data to a staging table.
243
+ -- !x! IMPORT TO REPLACEMENT staging.errorlog FROM !!inputfile!!
244
+ -- Create a view to display only fatal errors.
245
+ create temporary view fatals as
246
+ select user, run_time, process
247
+ from staging.errorlog
248
+ where severity = 'FATAL';
249
+ -- !x! IF(HASROWS(fatals))
250
+ -- Export the fatal errors to a dated report.
251
+ -- !x! EXPORT fatals TO reports/error_report_!!$date_tag!! AS CSV
252
+ -- Also display it to the user in a GUI.
253
+ -- !x! PROMPT MESSAGE "Fatal errors in !!inputfile!!:" DISPLAY fatals
254
+ -- !x! ELSE
255
+ -- !x! WRITE "There are no fatal errors."
256
+ -- !x! ENDIF
257
+ -- !x! ELSE
258
+ -- !x! WRITE "There is no error log."
259
+ -- !x! ENDIF
260
+ drop table if exists staging.errorlog cascade;
261
+ ```
262
+
263
+ The `PROMPT` metacommand produces a GUI display of the data:
264
+
265
+ ![PROMPT display of 'fatals' view](https://execsql2.readthedocs.io/en/latest/images/fatals.png)
266
+
267
+ # Formatting Scripts
268
+
269
+ The `execsql-format` command normalizes execsql script files: it uppercases metacommand keywords, corrects block indentation, and optionally reformats SQL via sqlglot. It is installed automatically with the `execsql2` package.
270
+
271
+ ```bash
272
+ # Format files in place
273
+ execsql-format --in-place scripts/
274
+
275
+ # Check formatting without writing (useful in CI)
276
+ execsql-format --check scripts/
277
+ ```
278
+
279
+ See the [formatter documentation](https://execsql2.readthedocs.io/en/latest/formatter/) for all options.
280
+
281
+ # Templates
282
+
283
+ The `templates/` directory in this repository includes ready-to-use execsql scripts:
284
+
285
+ - **Upsert scripts** (`pg_upsert.sql`, `md_upsert.sql`, `ss_upsert.sql`): Perform merge/upsert operations on multiple tables simultaneously, respecting foreign key order, for PostgreSQL, MySQL/MariaDB, and SQL Server.
286
+ - **Comparison scripts** (`pg_compare.sql`, `md_compare.sql`, `ss_compare.sql`): Compare staging and base tables across multiple dimensions.
287
+ - **Glossary scripts** (`pg_glossary.sql`, `md_glossary.sql`, `ss_glossary.sql`): Produce a glossary of column names and definitions to accompany a database export.
288
+ - **`script_template.sql`**: A framework for new scripts with sections for configuration, logging, and error reporting.
289
+ - **`execsql.conf`**: An annotated configuration file covering all available settings.
290
+
291
+ # Documentation
292
+
293
+ Full documentation, including a complete metacommand reference and 30+ examples, is at [execsql2.readthedocs.io](https://execsql2.readthedocs.io/).
294
+
295
+ # Copyright and License
296
+
297
+ Copyright (c) 2007-2025 R.Dreas Nielsen
298
+ Copyright (c) 2026-present Caleb Grant
299
+
300
+ This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the [GNU General Public License](http://www.gnu.org/licenses/) for more details.
@@ -0,0 +1,96 @@
1
+ execsql/__init__.py,sha256=BIny4bL8uHuSl3gXPqEkIB2FtcexlARjR7IYPwtD9bM,486
2
+ execsql/__main__.py,sha256=HdbK-SAhyUmfB6xINY5AzxdMSxGzWSGEG_2dv42Jn64,315
3
+ execsql/cli.py,sha256=vXK97DZ0HAb_jOJP2qFXAXEtNbfdyA5tnhjY_xdB5bo,41347
4
+ execsql/config.py,sha256=qwhavI1KpU9FnxciE30cqbk8JfOmzrZa3g5IVRDJZlA,33344
5
+ execsql/constants.py,sha256=up8I1sfFg1KaQ-ZgwufDOYbupMgTkPVpHzAdWpi5-BQ,12628
6
+ execsql/exceptions.py,sha256=O68QF4VRdqslGQbTQX0IaRT0EW2s6t_4fdHb0-4sA2Y,8003
7
+ execsql/format.py,sha256=4uNF2MSDBYwZuK9k4wywcUZFU4D_9bMokseOyaCjmGU,11911
8
+ execsql/models.py,sha256=INArNIF_lds_Jv3aXXXFO8Cf1W7G_IjsZDLsVnDlSCs,12427
9
+ execsql/parser.py,sha256=kue_NonM_VLAnfYDqJ_7mXJSfKsTLFLJBOj9pRWACJ0,12689
10
+ execsql/script.py,sha256=6vorNTs1RP90VcbL06bDZQp1_04LBbldT04k5TUzbQY,46852
11
+ execsql/state.py,sha256=INT7eXwO8_drZrngHqq2A4hN8Hrh-JFXELUVuqX4wgY,12385
12
+ execsql/types.py,sha256=aqGX-EFIdWUWt5ofiWQFphzhuYhrAhGH0xtFBTvb1KY,29187
13
+ execsql/db/__init__.py,sha256=jTbuafuKOqYtXFR1wvCOoKK5Lr3l1uErfaIbIr6UywI,1063
14
+ execsql/db/access.py,sha256=4VT-FtKixcHInM9pdKWc7983WSvMXDY32wWBr-iu8uI,15930
15
+ execsql/db/base.py,sha256=gsFUH2SnQQdw0fLVrfzJFuQizSIqq-2XKJRQbZjagfw,23924
16
+ execsql/db/dsn.py,sha256=wFx1CcpXRbMQH5YonMLlfe1qHlSurWdMBrccCfFAuWM,4933
17
+ execsql/db/duckdb.py,sha256=4jSLGn7Hh_Q1eNv7YZJHewxijHO59_DT2y9taSIeGo4,2721
18
+ execsql/db/factory.py,sha256=O8YDNRWBFNcsfN6pnlFb3vuTH3rklfzlpQbAFcMnpGg,4150
19
+ execsql/db/firebird.py,sha256=QF7yVdA1pXeQEsJ4Qg-M6g1FSWFV3PnQDRYF_7FmWm8,7544
20
+ execsql/db/mysql.py,sha256=-miUlWUhyySZgxPPfyI4uYhnQgGhfhzKBu3ibSii_V8,12781
21
+ execsql/db/oracle.py,sha256=6it40w2uXkYOjay46_Ei42Ztbmvmcfky9ovHjrvoD4U,9302
22
+ execsql/db/postgres.py,sha256=ny0ZxN97BVrs2u35adFqYPeHabJb_NSyJt2g-naC7tk,18690
23
+ execsql/db/sqlite.py,sha256=4R5MqoQI4QFAtkEMlOIZmdsRVDuvF_oWYYY2VDWfHIk,9209
24
+ execsql/db/sqlserver.py,sha256=teru36YUAdHl4xx2bupC4o0M1Lp6dAa5xz9pKZ3hGeU,6712
25
+ execsql/exporters/__init__.py,sha256=a679NAJp92e3565fhfqbBIbIT_RdyWQWCJ3-wqJJyYE,525
26
+ execsql/exporters/base.py,sha256=jE2F6KpXwI3YG6iotNyHJc09-rlXwGYPhFVbWngym-I,5735
27
+ execsql/exporters/delimited.py,sha256=3dt0XEB81tzRCtfpyMZeNhBhJ9FVPnaMIC1a6MjS1U0,26886
28
+ execsql/exporters/duckdb.py,sha256=rf0Mg3YWkG2GSlu_wCgNBsQ3Z8m92pfDRh3qAHi8Ado,2677
29
+ execsql/exporters/feather.py,sha256=SVMVNp5Zqf1VN5chFI5hZpcabn45XKnaNfwhU5Rg9SQ,3898
30
+ execsql/exporters/html.py,sha256=0WlY14aPhhht2nrm3s5YDP22sZRTR7lbIYjIwv1GB8Y,8247
31
+ execsql/exporters/json.py,sha256=9mUgBFBbZQtuwgs75WPp3KGMZa0KpZsBhodubX0P1oY,3644
32
+ execsql/exporters/latex.py,sha256=q_3u7whsISdeBcsIeFVEYoACD8g-VsJU-OXPCN5Nb7U,4061
33
+ execsql/exporters/ods.py,sha256=WeM7PtHPn-L0erOyW-puslpG1hb_nQJ5r2J_Oy4JwU8,17064
34
+ execsql/exporters/parquet.py,sha256=Qok-zM-vLMpOh4FsevuL7AM8moawzhsht_DZ3lWmcQ0,976
35
+ execsql/exporters/pretty.py,sha256=lWVd8hWBPHck8xThVPg-GZzRgF74S_X1-LhOXS_Gzos,3122
36
+ execsql/exporters/raw.py,sha256=tS0jp3MIZF_i8VTOkWbFhsNGw8C6aHmPBD8zovtnrQE,1595
37
+ execsql/exporters/sqlite.py,sha256=gzVUilTiVt_4hn8KUk43qk7yeyquYo_zg04f1vOyBQo,2430
38
+ execsql/exporters/templates.py,sha256=3y-2EFvHK_8x5VOND77Xrs91iHyaCra3ody0tOi9jRM,4620
39
+ execsql/exporters/values.py,sha256=DzvdbaS9LQvglrvCkUnTVg9ZIkt8wTO01Q4XgZmV9rk,2176
40
+ execsql/exporters/xls.py,sha256=3A5T9wYWtw0UetqpL4fwYa9xbKiqUbiVigyzDEs_MaA,9039
41
+ execsql/exporters/xml.py,sha256=FUcPW71uG2jpWQF9S_P5cdtvBv4VQJUSUgNlPv-ucWk,1754
42
+ execsql/exporters/zip.py,sha256=Ujp4gD3vfqy8Cq2b1iLFhfDdjcGdibawrxnSoNtCjS8,3463
43
+ execsql/gui/__init__.py,sha256=KnLB-8GuRPBZij4W-sihcA9ORRuLDYoa0p2dCeIzYRg,2001
44
+ execsql/gui/base.py,sha256=gbfCqj3nOMkAcn-GNIIAy7RdPFJ0UMbzOxpDpfoEVxk,6042
45
+ execsql/gui/console.py,sha256=Ln9ixVMLlEK5foHi0t2EywrvTiBqWoPEpp8cnof3eIw,14241
46
+ execsql/gui/desktop.py,sha256=zooMA28FWHndy9Aw242wRzchs1QxYwlGGMsWvn4-EKo,42123
47
+ execsql/gui/tui.py,sha256=n5xzwe1jAsU54jj0PpcBmVfnvfycPVOeiLx07-IfVVk,45948
48
+ execsql/importers/__init__.py,sha256=dDsxSVeQYXBvm9yGqN3QswyGbLWTwt08pvUuRJgZhl4,289
49
+ execsql/importers/base.py,sha256=E9CIzy4O_DKdVB6n3lyxF44M1PL_FdDGynE1wCOtDk0,3983
50
+ execsql/importers/csv.py,sha256=BlMsUlVd6q0hzi-4jmAj76P3Zd_nafhm54sOo_pSKzM,4619
51
+ execsql/importers/feather.py,sha256=oRXdgGsJB_16rc8oewvqKYo2lBe3tivFGufJG7Wvfy0,1698
52
+ execsql/importers/ods.py,sha256=f_OlQpjb49CFUDiC5ypct0SjmxUsFNY5OybETvKmVJw,2709
53
+ execsql/importers/xls.py,sha256=bOsAOt_XRtNVYZVthNAweoH-EArG3s56_MyOzgjBErE,3541
54
+ execsql/metacommands/__init__.py,sha256=Gbf_hQJHFqxdVsChoqeQxKbPv0txK6lSENRCBtsPgPg,65018
55
+ execsql/metacommands/conditions.py,sha256=A-6I_jZ_zsq0CV9k4X5mZ6APg0TnHEahEiZ_0oa-nhc,22423
56
+ execsql/metacommands/connect.py,sha256=Nsm0D91i3RX-R2rzQQ-Br-gULaI6Uvdn9fqb7DOAVfE,14804
57
+ execsql/metacommands/control.py,sha256=iaGK_X1vF18HMEcGOWQuJPG7UfaZFkph2iXkOvf7BQc,7617
58
+ execsql/metacommands/data.py,sha256=Cfb9-vpKQkK-ql8hIgKqMAJhjuA4fgb56vaxqqbo4es,11334
59
+ execsql/metacommands/debug.py,sha256=tIGosPmn4YbQTp67VJK8CmUOvwjiSg2WBRPznKOCKfU,8058
60
+ execsql/metacommands/io.py,sha256=7e6Y37gAbMND0m2Zk-_Szy6lG6jNwUg_9glcPfQs2hA,44860
61
+ execsql/metacommands/prompt.py,sha256=yAs7HNMN38ttxjl5ubxY1u-lE7nkBc5B5uk1G5ksK0c,38093
62
+ execsql/metacommands/script_ext.py,sha256=TUgAldB2LSJAwZrCvDDi804hQ1d9BDQD2GDqHNPVOcM,2280
63
+ execsql/metacommands/system.py,sha256=w6xL3dT7zJ0pVOOckyHQM4SBvCmaX9q_daPlwq4Mi4I,7137
64
+ execsql/utils/__init__.py,sha256=0uR6JwVJQRX3vceByNBduCAf5dd5assKjeqJUWvpZoA,278
65
+ execsql/utils/auth.py,sha256=AVAKsLN4qBlfO8Axz4rmav4IujdM50RgPFZeidGmYqI,6931
66
+ execsql/utils/crypto.py,sha256=RRbrmbwq0NoNWUH4ZjzsI1H42um3d7Ftfoap9Fp0Nl4,3033
67
+ execsql/utils/datetime.py,sha256=WNnU5xXJWKN1dEeeaEX8GzOK0wqDuLwnaVnU7LKT0RI,7666
68
+ execsql/utils/errors.py,sha256=1beWINMdvE0HUJamIlToKOdDZEmIdsIxtfUMTv6l_Ko,6210
69
+ execsql/utils/fileio.py,sha256=zVUZ4y5r60b6LUdxRVa7iziJ9ns_3kqPMkyBePCHvMQ,23321
70
+ execsql/utils/gui.py,sha256=LS2Z8sSwW65QGYHHQBApVDaLaPdB6Eq8aTNn71cPimE,16668
71
+ execsql/utils/mail.py,sha256=Z1t0agMd6zQbB0fJM3aejmpVYTi6PbsPDchqT8pKay0,5628
72
+ execsql/utils/numeric.py,sha256=n9DSZOTJ2gAjUY_S6JZVxpAZ71SKDEFiLwdbE7haymw,1524
73
+ execsql/utils/regex.py,sha256=B6XXlaGAznKarrrrowLcjZ52PbkwAWmZzWz1FlRrUZM,3632
74
+ execsql/utils/strings.py,sha256=YSaPlax1b_EEL3VZAAP508AL2UomCEOYuhriX_pG7Kc,8230
75
+ execsql/utils/timer.py,sha256=1oqmmMU7w0L7zgo_3n7b_7rpoHt0H081zCPFYwrWBlo,1862
76
+ execsql2-2.1.2.data/data/execsql2_extras/READ_ME.rst,sha256=xrseluXaBIw0mec1J5rcNVKIgOqHRCJcbGrCsAay7UI,4848
77
+ execsql2-2.1.2.data/data/execsql2_extras/config_settings.sqlite,sha256=aY5cxR7Q7J6zJ4bC9lu5mHUrhy211Cq3MNKPQVCt02E,20480
78
+ execsql2-2.1.2.data/data/execsql2_extras/example_config_prompt.sql,sha256=SY3Jxn1qcVm4kPW9xmmTfknHfvURXmeEYTbRjYrjGSw,7487
79
+ execsql2-2.1.2.data/data/execsql2_extras/execsql.conf,sha256=l0DI9CEZhNAqknjNYzYr5GzGtO3NTO4WwlVUJgj3n7M,9515
80
+ execsql2-2.1.2.data/data/execsql2_extras/make_config_db.sql,sha256=WwyC6dK-Eh5CAVppiBCDHqiI1_wEI9U95Ytpr4lsZkg,8726
81
+ execsql2-2.1.2.data/data/execsql2_extras/md_compare.sql,sha256=B8Wd7LZ0vnMY2qvA139JIEBkPObgRH2i5xj6PejTQt8,24092
82
+ execsql2-2.1.2.data/data/execsql2_extras/md_glossary.sql,sha256=DJRHcU5NbFpxTTX-IwH3yRlsboj1q6BBGrUAHKn4Cuo,10796
83
+ execsql2-2.1.2.data/data/execsql2_extras/md_upsert.sql,sha256=v_7GbWh_N1mBTmw3gvTrkagOVp2q0KmXvM8hE-DlFxY,112524
84
+ execsql2-2.1.2.data/data/execsql2_extras/pg_compare.sql,sha256=9dWa8hnfy5dVJI-z2iGpd9JzQmI4j2ziMlEdpnr66ro,24352
85
+ execsql2-2.1.2.data/data/execsql2_extras/pg_glossary.sql,sha256=pKjIIDsROAgJq2H-1qNEcRMAWManivcZ_AEVHzUUlic,9908
86
+ execsql2-2.1.2.data/data/execsql2_extras/pg_upsert.sql,sha256=k7AFiGTLBy3nf-qO5QIaZrEYTAKvdxxU3JDLx9jqkzs,108315
87
+ execsql2-2.1.2.data/data/execsql2_extras/script_template.sql,sha256=1Estacb_vm1FgK41k_G9nuduP1yiA-fQ1Kn4Z4mv5Ao,11153
88
+ execsql2-2.1.2.data/data/execsql2_extras/ss_compare.sql,sha256=TsVxWm3cEpR5-EiMYXNhtaY0arSNeKZhsJdHdLA7xeI,24833
89
+ execsql2-2.1.2.data/data/execsql2_extras/ss_glossary.sql,sha256=cLM7nN8JOIu9ZVP9oY9qdSK3hrnWJiDcX6nZmQQbQWI,13065
90
+ execsql2-2.1.2.data/data/execsql2_extras/ss_upsert.sql,sha256=BCqmBykXBF-BpCgOFeG1qhf2XfScKsxPD17wd1hYfHw,120647
91
+ execsql2-2.1.2.dist-info/METADATA,sha256=hf_k-tE3yUQEsbzUOc_bkEhQt-c1D3w-C9geEd0Brw0,14893
92
+ execsql2-2.1.2.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
93
+ execsql2-2.1.2.dist-info/entry_points.txt,sha256=sUOxkM-dN1eBGGpSpDLsAaE0yNXYQKWZAfxPOlMkQyk,90
94
+ execsql2-2.1.2.dist-info/licenses/LICENSE.txt,sha256=LBdhuxejF8_bLCHZ2kWfmDXpDGUu914Gbd6_3JjCRe0,676
95
+ execsql2-2.1.2.dist-info/licenses/NOTICE,sha256=sqVrM73Ys9zfvWC_P797lHfTnoPW_ETeBSrUTFaob0A,339
96
+ execsql2-2.1.2.dist-info/RECORD,,