execsql2 2.19.2__py3-none-any.whl → 2.20.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/cli/run.py +28 -11
- execsql/db/access.py +5 -1
- execsql/db/base.py +8 -2
- execsql/db/dsn.py +3 -1
- execsql/db/firebird.py +10 -4
- execsql/db/mysql.py +6 -0
- execsql/db/sqlserver.py +4 -1
- execsql/format.py +172 -16
- execsql/utils/mail.py +7 -4
- {execsql2-2.19.2.data → execsql2-2.20.0.data}/data/execsql2_extras/README.md +1 -1
- {execsql2-2.19.2.dist-info → execsql2-2.20.0.dist-info}/METADATA +54 -56
- {execsql2-2.19.2.dist-info → execsql2-2.20.0.dist-info}/RECORD +29 -30
- execsql2-2.19.2.data/data/execsql2_extras/execsql.conf +0 -359
- {execsql2-2.19.2.data → execsql2-2.20.0.data}/data/execsql2_extras/config_settings.sqlite +0 -0
- {execsql2-2.19.2.data → execsql2-2.20.0.data}/data/execsql2_extras/example_config_prompt.sql +0 -0
- {execsql2-2.19.2.data → execsql2-2.20.0.data}/data/execsql2_extras/make_config_db.sql +0 -0
- {execsql2-2.19.2.data → execsql2-2.20.0.data}/data/execsql2_extras/md_compare.sql +0 -0
- {execsql2-2.19.2.data → execsql2-2.20.0.data}/data/execsql2_extras/md_glossary.sql +0 -0
- {execsql2-2.19.2.data → execsql2-2.20.0.data}/data/execsql2_extras/md_upsert.sql +0 -0
- {execsql2-2.19.2.data → execsql2-2.20.0.data}/data/execsql2_extras/pg_compare.sql +0 -0
- {execsql2-2.19.2.data → execsql2-2.20.0.data}/data/execsql2_extras/pg_glossary.sql +0 -0
- {execsql2-2.19.2.data → execsql2-2.20.0.data}/data/execsql2_extras/pg_upsert.sql +0 -0
- {execsql2-2.19.2.data → execsql2-2.20.0.data}/data/execsql2_extras/script_template.sql +0 -0
- {execsql2-2.19.2.data → execsql2-2.20.0.data}/data/execsql2_extras/ss_compare.sql +0 -0
- {execsql2-2.19.2.data → execsql2-2.20.0.data}/data/execsql2_extras/ss_glossary.sql +0 -0
- {execsql2-2.19.2.data → execsql2-2.20.0.data}/data/execsql2_extras/ss_upsert.sql +0 -0
- {execsql2-2.19.2.dist-info → execsql2-2.20.0.dist-info}/WHEEL +0 -0
- {execsql2-2.19.2.dist-info → execsql2-2.20.0.dist-info}/entry_points.txt +0 -0
- {execsql2-2.19.2.dist-info → execsql2-2.20.0.dist-info}/licenses/LICENSE.txt +0 -0
- {execsql2-2.19.2.dist-info → execsql2-2.20.0.dist-info}/licenses/NOTICE +0 -0
execsql/cli/run.py
CHANGED
|
@@ -206,9 +206,29 @@ def _ping_db(db: Any) -> None:
|
|
|
206
206
|
# ---------------------------------------------------------------------------
|
|
207
207
|
|
|
208
208
|
|
|
209
|
-
# B12/F014: shared sensitive-name filter
|
|
210
|
-
#
|
|
211
|
-
|
|
209
|
+
# B12/F014: shared sensitive-name filter for env-var seeding and any
|
|
210
|
+
# other name-based credential-redaction sites. Case-insensitive substring
|
|
211
|
+
# matches: an env var whose name (uppercased) contains any of these is
|
|
212
|
+
# not seeded into the substitution pool. "_KEY" uses a leading underscore
|
|
213
|
+
# so it catches AWS_ACCESS_KEY_ID / SECRET_KEY without also blocking
|
|
214
|
+
# KEYBOARD-style names. Substring matching catches the common service-
|
|
215
|
+
# prefixed forms (STRIPE_KEY, OPENAI_API_KEY, SENTRY_DSN, SLACK_WEBHOOK);
|
|
216
|
+
# GitHub PAT-suffixed names (GITHUB_PAT) and URL-encoded DSNs
|
|
217
|
+
# (DATABASE_URL) are documented gaps — use a TOKEN- or SECRET-prefixed
|
|
218
|
+
# name when storing those.
|
|
219
|
+
_SENSITIVE_SUBSTRINGS = (
|
|
220
|
+
"SECRET",
|
|
221
|
+
"TOKEN",
|
|
222
|
+
"PASSWORD",
|
|
223
|
+
"PASSWD",
|
|
224
|
+
"PRIVATE_KEY",
|
|
225
|
+
"CREDENTIAL",
|
|
226
|
+
"_KEY",
|
|
227
|
+
"APIKEY",
|
|
228
|
+
"API_KEY",
|
|
229
|
+
"DSN",
|
|
230
|
+
"WEBHOOK",
|
|
231
|
+
)
|
|
212
232
|
|
|
213
233
|
|
|
214
234
|
def _seed_early_subvars() -> SubVarSet:
|
|
@@ -517,15 +537,12 @@ def _setup_logging(
|
|
|
517
537
|
for n, repl in enumerate(sub_vars):
|
|
518
538
|
var = f"$ARG_{n + 1}"
|
|
519
539
|
subvars.add_substitution(var, repl)
|
|
520
|
-
#
|
|
521
|
-
#
|
|
522
|
-
#
|
|
523
|
-
#
|
|
524
|
-
display_repl = repl
|
|
525
|
-
if any(s in str(repl).upper() for s in _SENSITIVE_SUBSTRINGS):
|
|
526
|
-
display_repl = "***"
|
|
540
|
+
# `-a` values are positional and opaque (no name to denylist
|
|
541
|
+
# against). High-entropy secrets (sk-live-*, AKIA*, ghp_*,
|
|
542
|
+
# JWTs) pass any substring/value heuristic, so log the
|
|
543
|
+
# assignment confirmation without the value.
|
|
527
544
|
logger.log_status_info(
|
|
528
|
-
f"Command-line substitution variable assignment: {var} set to {{
|
|
545
|
+
f"Command-line substitution variable assignment: {var} set to {{***}}",
|
|
529
546
|
)
|
|
530
547
|
|
|
531
548
|
return logger
|
execsql/db/access.py
CHANGED
|
@@ -107,7 +107,11 @@ class AccessDatabase(Database):
|
|
|
107
107
|
else:
|
|
108
108
|
connstr = cs % db_name
|
|
109
109
|
try:
|
|
110
|
-
|
|
110
|
+
# Access is file-based but a UNC path on an
|
|
111
|
+
# unreachable share can still block forever; cap
|
|
112
|
+
# the connect attempt at 30 s to match the other
|
|
113
|
+
# adapters.
|
|
114
|
+
self.conn = pyodbc.connect(connstr, timeout=30)
|
|
111
115
|
except Exception:
|
|
112
116
|
if _state.exec_log is not None:
|
|
113
117
|
_state.exec_log.log_status_info(
|
execsql/db/base.py
CHANGED
|
@@ -239,8 +239,14 @@ class Database(ABC):
|
|
|
239
239
|
if self.conn:
|
|
240
240
|
try:
|
|
241
241
|
self.conn.rollback()
|
|
242
|
-
except Exception:
|
|
243
|
-
|
|
242
|
+
except Exception as e:
|
|
243
|
+
# Best-effort; connection may already be closed. Still
|
|
244
|
+
# log so a cascading rollback failure isn't invisible
|
|
245
|
+
# in a CI / cron log.
|
|
246
|
+
if _state.exec_log is not None:
|
|
247
|
+
_state.exec_log.log_status_info(
|
|
248
|
+
f"Rollback failed on {self.__class__.__name__}: {e!r}",
|
|
249
|
+
)
|
|
244
250
|
|
|
245
251
|
def needs_explicit_commit_after_ddl(self) -> bool:
|
|
246
252
|
"""Return True if this adapter's driver does NOT auto-commit DDL.
|
execsql/db/dsn.py
CHANGED
|
@@ -90,7 +90,9 @@ class DsnDatabase(Database):
|
|
|
90
90
|
parts.append(f"PWD={_odbc_quote(self.password)}")
|
|
91
91
|
connstr = ";".join(parts) + ";"
|
|
92
92
|
kwargs = {"autocommit": autocommit} if autocommit else {}
|
|
93
|
-
|
|
93
|
+
# 30 s connect timeout matches the Postgres adapter default
|
|
94
|
+
# so a wedged DSN peer cannot hang a script indefinitely.
|
|
95
|
+
self.conn = pyodbc.connect(connstr, timeout=30, **kwargs)
|
|
94
96
|
|
|
95
97
|
def _try_connect():
|
|
96
98
|
try:
|
execsql/db/firebird.py
CHANGED
|
@@ -18,7 +18,7 @@ __all__ = ["FirebirdDatabase"]
|
|
|
18
18
|
|
|
19
19
|
|
|
20
20
|
class FirebirdDatabase(Database):
|
|
21
|
-
"""Firebird adapter using the firebird-driver
|
|
21
|
+
"""Firebird adapter using the firebird-driver package."""
|
|
22
22
|
|
|
23
23
|
def __init__(
|
|
24
24
|
self,
|
|
@@ -31,10 +31,11 @@ class FirebirdDatabase(Database):
|
|
|
31
31
|
password: str | None = None,
|
|
32
32
|
) -> None:
|
|
33
33
|
try:
|
|
34
|
-
import
|
|
34
|
+
from firebird import driver as firebird_lib # noqa: F401
|
|
35
35
|
except Exception:
|
|
36
36
|
fatal_error(
|
|
37
|
-
"The
|
|
37
|
+
"The firebird-driver module is required to connect to Firebird. "
|
|
38
|
+
"See https://pypi.org/project/firebird-driver/",
|
|
38
39
|
)
|
|
39
40
|
from execsql.types import dbt_firebird
|
|
40
41
|
|
|
@@ -67,7 +68,10 @@ class FirebirdDatabase(Database):
|
|
|
67
68
|
|
|
68
69
|
def open_db(self) -> None:
|
|
69
70
|
"""Open a connection to the Firebird database."""
|
|
70
|
-
import
|
|
71
|
+
from firebird import driver as firebird_lib
|
|
72
|
+
|
|
73
|
+
# 30 s connect timeout matches the Postgres adapter default.
|
|
74
|
+
connect_timeout = 30
|
|
71
75
|
|
|
72
76
|
def db_conn():
|
|
73
77
|
if self.user and self.password:
|
|
@@ -78,6 +82,7 @@ class FirebirdDatabase(Database):
|
|
|
78
82
|
user=self.user,
|
|
79
83
|
password=self.password,
|
|
80
84
|
charset=self.encoding,
|
|
85
|
+
timeout=connect_timeout,
|
|
81
86
|
)
|
|
82
87
|
else:
|
|
83
88
|
return firebird_lib.connect(
|
|
@@ -85,6 +90,7 @@ class FirebirdDatabase(Database):
|
|
|
85
90
|
database=self.db_name,
|
|
86
91
|
port=self.port,
|
|
87
92
|
charset=self.encoding,
|
|
93
|
+
timeout=connect_timeout,
|
|
88
94
|
)
|
|
89
95
|
|
|
90
96
|
if self.conn is None:
|
execsql/db/mysql.py
CHANGED
|
@@ -152,6 +152,10 @@ class MySQLDatabase(Database):
|
|
|
152
152
|
"""Open a connection to the MySQL or MariaDB server."""
|
|
153
153
|
import pymysql as mysql_lib
|
|
154
154
|
|
|
155
|
+
# 30 s default matches the Postgres adapter so a wedged peer
|
|
156
|
+
# can't hang a script indefinitely.
|
|
157
|
+
connect_timeout = 30
|
|
158
|
+
|
|
155
159
|
def db_conn():
|
|
156
160
|
if self.user and self.password:
|
|
157
161
|
return mysql_lib.connect(
|
|
@@ -162,6 +166,7 @@ class MySQLDatabase(Database):
|
|
|
162
166
|
password=self.password,
|
|
163
167
|
charset=self.encoding,
|
|
164
168
|
local_infile=True,
|
|
169
|
+
connect_timeout=connect_timeout,
|
|
165
170
|
)
|
|
166
171
|
else:
|
|
167
172
|
return mysql_lib.connect(
|
|
@@ -170,6 +175,7 @@ class MySQLDatabase(Database):
|
|
|
170
175
|
port=self.port,
|
|
171
176
|
charset=self.encoding,
|
|
172
177
|
local_infile=True,
|
|
178
|
+
connect_timeout=connect_timeout,
|
|
173
179
|
)
|
|
174
180
|
|
|
175
181
|
if self.conn is None:
|
execsql/db/sqlserver.py
CHANGED
|
@@ -113,7 +113,10 @@ class SqlServerDatabase(Database):
|
|
|
113
113
|
f"DATABASE={self.db_name};Trusted_Connection=yes"
|
|
114
114
|
)
|
|
115
115
|
try:
|
|
116
|
-
|
|
116
|
+
# 30 s connect timeout matches the Postgres adapter
|
|
117
|
+
# default; pyodbc treats `timeout` as the login-and-
|
|
118
|
+
# query timeout on the connection.
|
|
119
|
+
self.conn = pyodbc.connect(connstr, timeout=30)
|
|
117
120
|
except Exception:
|
|
118
121
|
if _state.exec_log is not None:
|
|
119
122
|
_state.exec_log.log_status_info(
|
execsql/format.py
CHANGED
|
@@ -45,6 +45,13 @@ METACOMMAND_RE = re.compile(r"^\s*--\s*!x!\s*(.*)", re.IGNORECASE)
|
|
|
45
45
|
|
|
46
46
|
# Multi-word keywords — checked longest-first before single-word fallback.
|
|
47
47
|
# Order matters: more-specific variants must precede their prefixes.
|
|
48
|
+
# Only entries that appear at the *start* of a `-- !x!` payload belong
|
|
49
|
+
# here — `parse_keyword()` matches against the beginning of the payload,
|
|
50
|
+
# not embedded sub-clauses. `IN ZIPFILE` / `WITH TEMPLATE` are EXPORT
|
|
51
|
+
# sub-clauses and never appear at the start, so they don't go here.
|
|
52
|
+
# When adding a new dispatch keyword, mirror it here (or fix the missing
|
|
53
|
+
# entry via the `tests/test_format.py` drift check that pulls names from
|
|
54
|
+
# the dispatch table).
|
|
48
55
|
MULTIWORD_KEYWORDS = [
|
|
49
56
|
"METACOMMAND_ERROR_HALT",
|
|
50
57
|
"ON ERROR_HALT",
|
|
@@ -77,18 +84,31 @@ MULTIWORD_KEYWORDS = [
|
|
|
77
84
|
"SUB_INI",
|
|
78
85
|
"PROMPT ENTRY_FORM",
|
|
79
86
|
"PROMPT SELECT_SUB",
|
|
87
|
+
"PROMPT SELECT_ROWS",
|
|
80
88
|
"PROMPT ENTER_SUB",
|
|
81
89
|
"PROMPT DIRECTORY",
|
|
90
|
+
"PROMPT CREDENTIALS",
|
|
82
91
|
"PROMPT CONNECT",
|
|
83
92
|
"PROMPT COMPARE",
|
|
84
93
|
"PROMPT MESSAGE",
|
|
85
94
|
"PROMPT DISPLAY",
|
|
86
95
|
"PROMPT ACTION",
|
|
96
|
+
"PROMPT OPENFILE",
|
|
97
|
+
"PROMPT SAVEFILE",
|
|
87
98
|
"PROMPT PAUSE",
|
|
88
|
-
|
|
99
|
+
# PROMPT ASK COMPARE must precede PROMPT ASK so longest-match wins;
|
|
100
|
+
# parse_keyword iterates in list order, not by length.
|
|
101
|
+
"PROMPT ASK COMPARE",
|
|
89
102
|
"PROMPT ASK",
|
|
90
|
-
"
|
|
91
|
-
"
|
|
103
|
+
"PROMPT MAP",
|
|
104
|
+
"APPEND SCRIPT",
|
|
105
|
+
"PG_UPSERT CHECK",
|
|
106
|
+
"PG_UPSERT QA",
|
|
107
|
+
"RESET COUNTER",
|
|
108
|
+
"RESET DIALOG_CANCELED",
|
|
109
|
+
"SET COUNTER",
|
|
110
|
+
"WRITE CREATE_TABLE",
|
|
111
|
+
"WRITE SCRIPT",
|
|
92
112
|
"SHOW SCRIPTS",
|
|
93
113
|
]
|
|
94
114
|
|
|
@@ -98,9 +118,18 @@ BLOCK_CLOSE = frozenset({"ENDIF", "END LOOP", "ENDLOOP", "END SCRIPT", "END BATC
|
|
|
98
118
|
PIVOT = frozenset({"ELSE", "ELSEIF"}) # decrease depth before emit, increase after
|
|
99
119
|
CONTINUATION = frozenset({"ANDIF", "ORIF"}) # emit at depth-1, no depth change
|
|
100
120
|
|
|
101
|
-
# Inline IF: "IF (cond) { command }" — self-contained, no ENDIF, no depth
|
|
102
|
-
#
|
|
121
|
+
# Inline IF: "IF (cond) { command }" — self-contained, no ENDIF, no depth
|
|
122
|
+
# change. Pattern must accept the same payloads as
|
|
123
|
+
# src/execsql/script/parser.py:_IF_INLINE_RX. Kept as a separate compiled
|
|
124
|
+
# pattern (not an import) so execsql-format doesn't pull in the AST parser
|
|
125
|
+
# module graph at startup; tests/test_format.py has a drift check that
|
|
126
|
+
# asserts both regexes recognise the same inputs.
|
|
103
127
|
_IF_INLINE_RE = re.compile(r"^\s*IF\s*\(\s*.+\s*\)\s*\{.+\}\s*$", re.I)
|
|
128
|
+
|
|
129
|
+
# Matches both untagged `$$` and tagged `$tag$` dollar-quote markers
|
|
130
|
+
# (PostgreSQL PL/pgSQL / DO-block syntax). Tags are letter-or-underscore
|
|
131
|
+
# followed by word characters; the empty tag (just `$$`) is also valid.
|
|
132
|
+
_DOLLAR_QUOTE_RE = re.compile(r"\$([A-Za-z_][A-Za-z0-9_]*)?\$")
|
|
104
133
|
# BLOCK_OPEN keywords whose bodies are guaranteed-SQL (not metacommand-driven).
|
|
105
134
|
# Blank lines inside these belong to the SQL accumulator, not the output stream.
|
|
106
135
|
_SQL_BODY_BLOCKS = frozenset({"BEGIN SQL", "BEGIN BATCH"})
|
|
@@ -180,9 +209,16 @@ def _is_comment_line(line: str, in_block: bool) -> tuple[bool, bool]:
|
|
|
180
209
|
def _sqlglot_format(
|
|
181
210
|
sql_lines: list[str],
|
|
182
211
|
sql_indent: int = 4,
|
|
183
|
-
leading_comma: bool = False,
|
|
212
|
+
leading_comma: bool = False, # noqa: ARG001 — leading-comma layout is applied as a textual post-pass on the assembled output; sqlglot's own `leading_comma=True` is non-idempotent under inline comments.
|
|
184
213
|
) -> list[str]:
|
|
185
|
-
"""Format a list of SQL-only lines (no comment-only lines) via sqlglot.
|
|
214
|
+
"""Format a list of SQL-only lines (no comment-only lines) via sqlglot.
|
|
215
|
+
|
|
216
|
+
Always emits trailing-comma style; if the caller wants leading commas
|
|
217
|
+
they are produced by ``_apply_leading_comma`` at the end of
|
|
218
|
+
``format_file``. sqlglot's own ``leading_comma=True`` reshuffles inline
|
|
219
|
+
comments and is therefore non-idempotent on SQL with mid-statement
|
|
220
|
+
comments, which is the dominant real-world case.
|
|
221
|
+
"""
|
|
186
222
|
sqlglot = _require_sqlglot()
|
|
187
223
|
import sqlglot.errors as sqlglot_errors
|
|
188
224
|
|
|
@@ -211,7 +247,6 @@ def _sqlglot_format(
|
|
|
211
247
|
pad=sql_indent,
|
|
212
248
|
indent=sql_indent,
|
|
213
249
|
max_text_width=120,
|
|
214
|
-
leading_comma=leading_comma,
|
|
215
250
|
),
|
|
216
251
|
)
|
|
217
252
|
stmts = [s for s in statements if s]
|
|
@@ -508,13 +543,105 @@ def format_metacommand(payload: str, depth: int, indent: int) -> str:
|
|
|
508
543
|
return f"{prefix}-- !x! {keyword}"
|
|
509
544
|
|
|
510
545
|
|
|
546
|
+
def _is_comment_only(s: str) -> bool:
|
|
547
|
+
"""Strict comment classifier for the leading-comma post/pre passes."""
|
|
548
|
+
st = s.strip()
|
|
549
|
+
return st.startswith("--") or st.startswith("/*") or st.startswith("*/")
|
|
550
|
+
|
|
551
|
+
|
|
552
|
+
def _normalize_to_trailing_comma(text: str) -> str:
|
|
553
|
+
"""Rewrite leading-comma SQL (`, foo`) back to trailing-comma style.
|
|
554
|
+
|
|
555
|
+
Symmetric inverse of ``_apply_leading_comma``. Used as a pre-pass so
|
|
556
|
+
that sqlglot — which migrates inline ``/* marker */`` comments under
|
|
557
|
+
leading-comma input — sees a consistent trailing-comma shape on
|
|
558
|
+
every invocation. Comments between the two SQL lines stay in place.
|
|
559
|
+
"""
|
|
560
|
+
lines = text.split("\n")
|
|
561
|
+
|
|
562
|
+
def find_prev_sql_line(idx: int) -> int:
|
|
563
|
+
k = idx - 1
|
|
564
|
+
while k >= 0 and (not lines[k].strip() or _is_comment_only(lines[k])):
|
|
565
|
+
k -= 1
|
|
566
|
+
return k
|
|
567
|
+
|
|
568
|
+
for i, line in enumerate(lines):
|
|
569
|
+
stripped = line.lstrip()
|
|
570
|
+
if not stripped.startswith(",") or _is_comment_only(line):
|
|
571
|
+
continue
|
|
572
|
+
# Don't try to rewrite leading-comma inside a comment-only line.
|
|
573
|
+
# Move the comma onto the previous SQL line as a trailing `,`.
|
|
574
|
+
prev = find_prev_sql_line(i)
|
|
575
|
+
if prev < 0:
|
|
576
|
+
continue
|
|
577
|
+
# Drop the `, ` (or `,`) at the start of this line.
|
|
578
|
+
indent_len = len(line) - len(stripped)
|
|
579
|
+
rest = stripped[1:].lstrip()
|
|
580
|
+
lines[i] = line[:indent_len] + rest
|
|
581
|
+
# Append `,` to the prev SQL line (preserving any existing right-side
|
|
582
|
+
# trailing whitespace — there shouldn't be any after format_file).
|
|
583
|
+
prev_rstripped = lines[prev].rstrip()
|
|
584
|
+
if not prev_rstripped.endswith(","):
|
|
585
|
+
lines[prev] = prev_rstripped + ","
|
|
586
|
+
return "\n".join(lines)
|
|
587
|
+
|
|
588
|
+
|
|
589
|
+
def _apply_leading_comma(text: str) -> str:
|
|
590
|
+
"""Rewrite trailing-comma SQL to leading-comma style as a textual pass.
|
|
591
|
+
|
|
592
|
+
Walks the assembled output line-by-line. For every line that ends with
|
|
593
|
+
``,`` (and is not a comment), strip the comma and prepend ``, `` to the
|
|
594
|
+
next non-blank, non-comment line — preserving that target line's
|
|
595
|
+
indent. Comments between the two SQL lines stay in place. The
|
|
596
|
+
transformation is idempotent: rerunning it on its own output is a
|
|
597
|
+
no-op (the source line no longer ends with ``,``).
|
|
598
|
+
|
|
599
|
+
This decouples our user-facing ``--leading-comma`` flag from
|
|
600
|
+
sqlglot's own ``leading_comma=True`` mode, which is non-idempotent
|
|
601
|
+
when inline ``/* marker */`` comments are present — sqlglot moves
|
|
602
|
+
the markers around between passes (verified in tests/test_format.py
|
|
603
|
+
TestIdempotency).
|
|
604
|
+
"""
|
|
605
|
+
lines = text.split("\n")
|
|
606
|
+
i = 0
|
|
607
|
+
n = len(lines)
|
|
608
|
+
while i < n:
|
|
609
|
+
rstripped = lines[i].rstrip()
|
|
610
|
+
if rstripped.endswith(",") and not _is_comment_only(lines[i]):
|
|
611
|
+
j = i + 1
|
|
612
|
+
while j < n and (not lines[j].strip() or _is_comment_only(lines[j])):
|
|
613
|
+
j += 1
|
|
614
|
+
if j < n:
|
|
615
|
+
stripped = lines[i].rstrip()
|
|
616
|
+
comma_idx = stripped.rfind(",")
|
|
617
|
+
lines[i] = stripped[:comma_idx] + stripped[comma_idx + 1 :]
|
|
618
|
+
target = lines[j]
|
|
619
|
+
indent_len = len(target) - len(target.lstrip())
|
|
620
|
+
lines[j] = target[:indent_len] + ", " + target[indent_len:]
|
|
621
|
+
i += 1
|
|
622
|
+
return "\n".join(lines)
|
|
623
|
+
|
|
624
|
+
|
|
511
625
|
def format_file(source: str, indent: int = 4, use_sql: bool = True, leading_comma: bool = False) -> str:
|
|
512
626
|
"""Format the source text of an execsql script and return the result."""
|
|
627
|
+
# Normalize any leading-comma SQL in the source to trailing commas so
|
|
628
|
+
# that sqlglot always sees the same comma shape regardless of how
|
|
629
|
+
# the user saved the file. The post-pass at the bottom of this
|
|
630
|
+
# function re-applies leading commas when the caller asked for them.
|
|
631
|
+
# This is what makes leading_comma=True idempotent under inline
|
|
632
|
+
# comments — sqlglot itself migrates `/* marker */` comments when
|
|
633
|
+
# parsing leading-comma input.
|
|
634
|
+
source = _normalize_to_trailing_comma(source)
|
|
635
|
+
|
|
513
636
|
depth = 0
|
|
514
637
|
sql_acc: list[str] = []
|
|
515
638
|
output: list[str] = []
|
|
516
639
|
|
|
517
640
|
in_dollar_quote = False
|
|
641
|
+
# When in_dollar_quote, the tag string we are inside ("" for `$$`,
|
|
642
|
+
# "body" for `$body$`, etc.). Nested markers with a different tag
|
|
643
|
+
# are ignored — only a matching close marker re-opens us.
|
|
644
|
+
current_dq_tag: str | None = None
|
|
518
645
|
in_block_comment = False
|
|
519
646
|
# Track whether we are inside an open SQL statement (last SQL line
|
|
520
647
|
# did not end with ';'). Blank lines mid-statement should NOT flush
|
|
@@ -527,7 +654,7 @@ def format_file(source: str, indent: int = 4, use_sql: bool = True, leading_comm
|
|
|
527
654
|
in_explicit_sql_block = False
|
|
528
655
|
|
|
529
656
|
def flush_sql() -> None:
|
|
530
|
-
nonlocal in_dollar_quote, in_sql_statement
|
|
657
|
+
nonlocal in_dollar_quote, current_dq_tag, in_sql_statement
|
|
531
658
|
if sql_acc:
|
|
532
659
|
# If any line in the accumulated block is inside a $$-delimited
|
|
533
660
|
# region, skip sqlglot formatting entirely. PL/pgSQL function
|
|
@@ -596,9 +723,19 @@ def format_file(source: str, indent: int = 4, use_sql: bool = True, leading_comm
|
|
|
596
723
|
output.append(format_metacommand(payload, depth, indent))
|
|
597
724
|
|
|
598
725
|
else:
|
|
599
|
-
# Track $$ boundaries to prevent sqlglot from mangling
|
|
600
|
-
|
|
601
|
-
|
|
726
|
+
# Track $$ and $tag$ boundaries to prevent sqlglot from mangling
|
|
727
|
+
# PL/pgSQL. Walk every dollar-quote marker on the line; toggle
|
|
728
|
+
# state only when we hit the matching open or close.
|
|
729
|
+
for m in _DOLLAR_QUOTE_RE.finditer(raw_line):
|
|
730
|
+
tag = m.group(1) or ""
|
|
731
|
+
if not in_dollar_quote:
|
|
732
|
+
in_dollar_quote = True
|
|
733
|
+
current_dq_tag = tag
|
|
734
|
+
elif tag == current_dq_tag:
|
|
735
|
+
in_dollar_quote = False
|
|
736
|
+
current_dq_tag = None
|
|
737
|
+
# else: tag mismatch — a foreign-tagged marker inside our
|
|
738
|
+
# quoted region; ignore (PG would treat it as literal text).
|
|
602
739
|
sql_acc.append(raw_line)
|
|
603
740
|
# Update statement tracking: if this SQL line ends with ';'
|
|
604
741
|
# (and isn't a comment), the statement is complete.
|
|
@@ -610,6 +747,8 @@ def format_file(source: str, indent: int = 4, use_sql: bool = True, leading_comm
|
|
|
610
747
|
flush_sql()
|
|
611
748
|
|
|
612
749
|
result = "\n".join(output)
|
|
750
|
+
if leading_comma:
|
|
751
|
+
result = _apply_leading_comma(result)
|
|
613
752
|
if not result.endswith("\n"):
|
|
614
753
|
result += "\n"
|
|
615
754
|
return result
|
|
@@ -665,6 +804,12 @@ def main() -> None:
|
|
|
665
804
|
"--leading-comma",
|
|
666
805
|
help="Place commas at the start of lines instead of the end.",
|
|
667
806
|
),
|
|
807
|
+
encoding: str = typer.Option(
|
|
808
|
+
"utf-8",
|
|
809
|
+
"--encoding",
|
|
810
|
+
metavar="NAME",
|
|
811
|
+
help="Text encoding used to read and write SQL files (default utf-8).",
|
|
812
|
+
),
|
|
668
813
|
) -> None:
|
|
669
814
|
use_sql = not no_sql
|
|
670
815
|
paths = collect_paths(targets)
|
|
@@ -673,12 +818,23 @@ def main() -> None:
|
|
|
673
818
|
raise typer.Exit(code=1)
|
|
674
819
|
|
|
675
820
|
any_changed = False
|
|
821
|
+
any_errors = False
|
|
676
822
|
for path in paths:
|
|
677
823
|
try:
|
|
678
|
-
source = path.read_text(encoding=
|
|
824
|
+
source = path.read_text(encoding=encoding)
|
|
679
825
|
except OSError as exc:
|
|
680
826
|
_err_console.print(f"[bold red]Error:[/bold red] reading {path}: {exc}")
|
|
681
|
-
|
|
827
|
+
any_errors = True
|
|
828
|
+
# Collect read errors instead of short-circuiting so a single
|
|
829
|
+
# unreadable file doesn't hide the rest of the report.
|
|
830
|
+
continue
|
|
831
|
+
except UnicodeDecodeError as exc:
|
|
832
|
+
_err_console.print(
|
|
833
|
+
f"[bold red]Error:[/bold red] decoding {path} as {encoding}: {exc}. "
|
|
834
|
+
f"Try [bold]--encoding cp1252[/bold] or another text encoding.",
|
|
835
|
+
)
|
|
836
|
+
any_errors = True
|
|
837
|
+
continue
|
|
682
838
|
|
|
683
839
|
formatted = format_file(source, indent=indent, use_sql=use_sql, leading_comma=leading_comma)
|
|
684
840
|
|
|
@@ -688,12 +844,12 @@ def main() -> None:
|
|
|
688
844
|
any_changed = True
|
|
689
845
|
elif in_place:
|
|
690
846
|
if formatted != source:
|
|
691
|
-
path.write_text(formatted, encoding=
|
|
847
|
+
path.write_text(formatted, encoding=encoding)
|
|
692
848
|
_console.print(f"reformatted {path}")
|
|
693
849
|
else:
|
|
694
850
|
sys.stdout.write(formatted)
|
|
695
851
|
|
|
696
|
-
if check and any_changed:
|
|
852
|
+
if any_errors or (check and any_changed):
|
|
697
853
|
raise typer.Exit(code=1)
|
|
698
854
|
|
|
699
855
|
app()
|
execsql/utils/mail.py
CHANGED
|
@@ -51,16 +51,19 @@ class Mailer:
|
|
|
51
51
|
conf = _state.conf
|
|
52
52
|
if conf.smtp_host is None:
|
|
53
53
|
raise ErrInfo(type="error", other_msg="Can't send email; the email host is not configured.")
|
|
54
|
+
# 30 s connect/read timeout matches the DB-adapter default so a
|
|
55
|
+
# silently-dropped SMTP peer can't hang a script (or a CI run).
|
|
56
|
+
smtp_timeout = 30
|
|
54
57
|
if conf.smtp_port is None:
|
|
55
58
|
if conf.smtp_ssl:
|
|
56
|
-
self.smtpconn = smtplib.SMTP_SSL(conf.smtp_host)
|
|
59
|
+
self.smtpconn = smtplib.SMTP_SSL(conf.smtp_host, timeout=smtp_timeout)
|
|
57
60
|
else:
|
|
58
|
-
self.smtpconn = smtplib.SMTP(conf.smtp_host)
|
|
61
|
+
self.smtpconn = smtplib.SMTP(conf.smtp_host, timeout=smtp_timeout)
|
|
59
62
|
else:
|
|
60
63
|
if conf.smtp_ssl:
|
|
61
|
-
self.smtpconn = smtplib.SMTP_SSL(conf.smtp_host, conf.smtp_port)
|
|
64
|
+
self.smtpconn = smtplib.SMTP_SSL(conf.smtp_host, conf.smtp_port, timeout=smtp_timeout)
|
|
62
65
|
else:
|
|
63
|
-
self.smtpconn = smtplib.SMTP(conf.smtp_host, conf.smtp_port)
|
|
66
|
+
self.smtpconn = smtplib.SMTP(conf.smtp_host, conf.smtp_port, timeout=smtp_timeout)
|
|
64
67
|
self.smtpconn.ehlo_or_hello_if_needed()
|
|
65
68
|
if conf.smtp_tls:
|
|
66
69
|
self.smtpconn.starttls()
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Several types of templates are provided that may be useful in conjunction with execsql. These are:
|
|
4
4
|
|
|
5
|
-
- **execsql.conf** — An annotated
|
|
5
|
+
- **execsql.conf** — An annotated reference of every configuration setting with notes on its usage. Generate a fresh copy in any directory with `execsql --init-config > execsql.conf`; the canonical content ships inside the installed package and is loaded via `importlib.resources` (no need to find or copy a source file).
|
|
6
6
|
|
|
7
7
|
- **script_template.sql** — A framework for SQL scripts that make use of several execsql features. It includes sections for custom configuration settings, custom logfile creation, and reporting of unexpected script exits (through user cancellation or errors).
|
|
8
8
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: execsql2
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.20.0
|
|
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
|
|
@@ -44,88 +44,87 @@ Requires-Dist: rich>=13.0
|
|
|
44
44
|
Requires-Dist: textual>=1.0
|
|
45
45
|
Requires-Dist: typer>=0.12
|
|
46
46
|
Provides-Extra: all
|
|
47
|
-
Requires-Dist: defusedxml; extra == 'all'
|
|
48
|
-
Requires-Dist: duckdb; extra == 'all'
|
|
49
|
-
Requires-Dist: firebird-driver; extra == 'all'
|
|
50
|
-
Requires-Dist: jinja2; extra == 'all'
|
|
51
|
-
Requires-Dist: keyring; extra == 'all'
|
|
52
|
-
Requires-Dist: odfpy; extra == 'all'
|
|
53
|
-
Requires-Dist: openpyxl; extra == 'all'
|
|
54
|
-
Requires-Dist: oracledb; extra == 'all'
|
|
47
|
+
Requires-Dist: defusedxml>=0.7; extra == 'all'
|
|
48
|
+
Requires-Dist: duckdb>=1.0; extra == 'all'
|
|
49
|
+
Requires-Dist: firebird-driver>=1.10; extra == 'all'
|
|
50
|
+
Requires-Dist: jinja2>=3.1; extra == 'all'
|
|
51
|
+
Requires-Dist: keyring>=25.0; extra == 'all'
|
|
52
|
+
Requires-Dist: odfpy>=1.4; extra == 'all'
|
|
53
|
+
Requires-Dist: openpyxl>=3.1; extra == 'all'
|
|
54
|
+
Requires-Dist: oracledb>=3.0; extra == 'all'
|
|
55
55
|
Requires-Dist: pg-upsert>=1.22.1; extra == 'all'
|
|
56
|
-
Requires-Dist: polars; extra == 'all'
|
|
57
|
-
Requires-Dist: psycopg2-binary; extra == 'all'
|
|
58
|
-
Requires-Dist: pymysql; extra == 'all'
|
|
59
|
-
Requires-Dist: pyodbc; extra == 'all'
|
|
60
|
-
Requires-Dist: pyyaml; extra == 'all'
|
|
56
|
+
Requires-Dist: polars>=1.0; extra == 'all'
|
|
57
|
+
Requires-Dist: psycopg2-binary>=2.9; extra == 'all'
|
|
58
|
+
Requires-Dist: pymysql>=1.1; extra == 'all'
|
|
59
|
+
Requires-Dist: pyodbc>=5.0; extra == 'all'
|
|
60
|
+
Requires-Dist: pyyaml>=6.0; extra == 'all'
|
|
61
61
|
Requires-Dist: sqlglot>=25.0; extra == 'all'
|
|
62
|
-
Requires-Dist: tables; extra == 'all'
|
|
62
|
+
Requires-Dist: tables>=3.10; extra == 'all'
|
|
63
63
|
Requires-Dist: tkintermapview>=1.29; extra == 'all'
|
|
64
|
-
Requires-Dist: xlrd; extra == 'all'
|
|
64
|
+
Requires-Dist: xlrd>=2.0; extra == 'all'
|
|
65
65
|
Provides-Extra: all-db
|
|
66
|
-
Requires-Dist: duckdb; extra == 'all-db'
|
|
67
|
-
Requires-Dist: firebird-driver; extra == 'all-db'
|
|
68
|
-
Requires-Dist: oracledb; extra == 'all-db'
|
|
69
|
-
Requires-Dist: psycopg2-binary; extra == 'all-db'
|
|
70
|
-
Requires-Dist: pymysql; extra == 'all-db'
|
|
71
|
-
Requires-Dist: pyodbc; extra == 'all-db'
|
|
66
|
+
Requires-Dist: duckdb>=1.0; extra == 'all-db'
|
|
67
|
+
Requires-Dist: firebird-driver>=1.10; extra == 'all-db'
|
|
68
|
+
Requires-Dist: oracledb>=3.0; extra == 'all-db'
|
|
69
|
+
Requires-Dist: psycopg2-binary>=2.9; extra == 'all-db'
|
|
70
|
+
Requires-Dist: pymysql>=1.1; extra == 'all-db'
|
|
71
|
+
Requires-Dist: pyodbc>=5.0; extra == 'all-db'
|
|
72
72
|
Provides-Extra: auth
|
|
73
|
-
Requires-Dist: keyring; extra == 'auth'
|
|
73
|
+
Requires-Dist: keyring>=25.0; extra == 'auth'
|
|
74
74
|
Provides-Extra: auth-encrypted
|
|
75
|
-
Requires-Dist: keyring; extra == 'auth-encrypted'
|
|
76
|
-
Requires-Dist: keyrings-alt; extra == 'auth-encrypted'
|
|
77
|
-
Requires-Dist: pycryptodome; extra == 'auth-encrypted'
|
|
75
|
+
Requires-Dist: keyring>=25.0; extra == 'auth-encrypted'
|
|
76
|
+
Requires-Dist: keyrings-alt>=5.0; extra == 'auth-encrypted'
|
|
77
|
+
Requires-Dist: pycryptodome>=3.20; extra == 'auth-encrypted'
|
|
78
78
|
Provides-Extra: auth-plaintext
|
|
79
|
-
Requires-Dist: keyring; extra == 'auth-plaintext'
|
|
80
|
-
Requires-Dist: keyrings-alt; extra == 'auth-plaintext'
|
|
79
|
+
Requires-Dist: keyring>=25.0; extra == 'auth-plaintext'
|
|
80
|
+
Requires-Dist: keyrings-alt>=5.0; extra == 'auth-plaintext'
|
|
81
81
|
Provides-Extra: dev
|
|
82
82
|
Requires-Dist: build>=1.2.2.post1; extra == 'dev'
|
|
83
83
|
Requires-Dist: bump-my-version>=1.2.7; extra == 'dev'
|
|
84
|
-
Requires-Dist: defusedxml; extra == 'dev'
|
|
85
|
-
Requires-Dist: jinja2; extra == 'dev'
|
|
86
|
-
Requires-Dist: markdown-include>=0.8; extra == 'dev'
|
|
84
|
+
Requires-Dist: defusedxml>=0.7; extra == 'dev'
|
|
85
|
+
Requires-Dist: jinja2>=3.1; extra == 'dev'
|
|
87
86
|
Requires-Dist: mkdocstrings-python>=2.0.3; extra == 'dev'
|
|
88
87
|
Requires-Dist: mypy>=1.10; extra == 'dev'
|
|
89
|
-
Requires-Dist: odfpy; extra == 'dev'
|
|
90
|
-
Requires-Dist: openpyxl; extra == 'dev'
|
|
91
|
-
Requires-Dist: polars; extra == 'dev'
|
|
88
|
+
Requires-Dist: odfpy>=1.4; extra == 'dev'
|
|
89
|
+
Requires-Dist: openpyxl>=3.1; extra == 'dev'
|
|
90
|
+
Requires-Dist: polars>=1.0; extra == 'dev'
|
|
92
91
|
Requires-Dist: pre-commit>=3.5.0; extra == 'dev'
|
|
93
92
|
Requires-Dist: pytest-cov>=5.0.0; extra == 'dev'
|
|
94
|
-
Requires-Dist: pyyaml; extra == 'dev'
|
|
93
|
+
Requires-Dist: pyyaml>=6.0; extra == 'dev'
|
|
95
94
|
Requires-Dist: ruff>=0.4; extra == 'dev'
|
|
96
95
|
Requires-Dist: sqlglot>=25.0; extra == 'dev'
|
|
97
|
-
Requires-Dist: tables; extra == 'dev'
|
|
96
|
+
Requires-Dist: tables>=3.10; extra == 'dev'
|
|
98
97
|
Requires-Dist: tox-uv>=1.13.1; extra == 'dev'
|
|
99
98
|
Requires-Dist: twine>=6.1.0; extra == 'dev'
|
|
100
|
-
Requires-Dist: xlrd; extra == 'dev'
|
|
101
|
-
Requires-Dist: zensical
|
|
99
|
+
Requires-Dist: xlrd>=2.0; extra == 'dev'
|
|
100
|
+
Requires-Dist: zensical==0.0.28; extra == 'dev'
|
|
102
101
|
Provides-Extra: duckdb
|
|
103
|
-
Requires-Dist: duckdb; extra == 'duckdb'
|
|
102
|
+
Requires-Dist: duckdb>=1.0; extra == 'duckdb'
|
|
104
103
|
Provides-Extra: firebird
|
|
105
|
-
Requires-Dist: firebird-driver; extra == 'firebird'
|
|
104
|
+
Requires-Dist: firebird-driver>=1.10; extra == 'firebird'
|
|
106
105
|
Provides-Extra: formats
|
|
107
|
-
Requires-Dist: defusedxml; extra == 'formats'
|
|
108
|
-
Requires-Dist: jinja2; extra == 'formats'
|
|
109
|
-
Requires-Dist: odfpy; extra == 'formats'
|
|
110
|
-
Requires-Dist: openpyxl; extra == 'formats'
|
|
111
|
-
Requires-Dist: polars; extra == 'formats'
|
|
112
|
-
Requires-Dist: pyyaml; extra == 'formats'
|
|
113
|
-
Requires-Dist: tables; extra == 'formats'
|
|
114
|
-
Requires-Dist: xlrd; extra == 'formats'
|
|
106
|
+
Requires-Dist: defusedxml>=0.7; extra == 'formats'
|
|
107
|
+
Requires-Dist: jinja2>=3.1; extra == 'formats'
|
|
108
|
+
Requires-Dist: odfpy>=1.4; extra == 'formats'
|
|
109
|
+
Requires-Dist: openpyxl>=3.1; extra == 'formats'
|
|
110
|
+
Requires-Dist: polars>=1.0; extra == 'formats'
|
|
111
|
+
Requires-Dist: pyyaml>=6.0; extra == 'formats'
|
|
112
|
+
Requires-Dist: tables>=3.10; extra == 'formats'
|
|
113
|
+
Requires-Dist: xlrd>=2.0; extra == 'formats'
|
|
115
114
|
Provides-Extra: formatter
|
|
116
115
|
Requires-Dist: sqlglot>=25.0; extra == 'formatter'
|
|
117
116
|
Provides-Extra: map
|
|
118
117
|
Requires-Dist: tkintermapview>=1.29; extra == 'map'
|
|
119
118
|
Provides-Extra: mssql
|
|
120
|
-
Requires-Dist: pyodbc; extra == 'mssql'
|
|
119
|
+
Requires-Dist: pyodbc>=5.0; extra == 'mssql'
|
|
121
120
|
Provides-Extra: mysql
|
|
122
|
-
Requires-Dist: pymysql; extra == 'mysql'
|
|
121
|
+
Requires-Dist: pymysql>=1.1; extra == 'mysql'
|
|
123
122
|
Provides-Extra: odbc
|
|
124
|
-
Requires-Dist: pyodbc; extra == 'odbc'
|
|
123
|
+
Requires-Dist: pyodbc>=5.0; extra == 'odbc'
|
|
125
124
|
Provides-Extra: oracle
|
|
126
|
-
Requires-Dist: oracledb; extra == 'oracle'
|
|
125
|
+
Requires-Dist: oracledb>=3.0; extra == 'oracle'
|
|
127
126
|
Provides-Extra: postgres
|
|
128
|
-
Requires-Dist: psycopg2-binary; extra == 'postgres'
|
|
127
|
+
Requires-Dist: psycopg2-binary>=2.9; extra == 'postgres'
|
|
129
128
|
Provides-Extra: upsert
|
|
130
129
|
Requires-Dist: pg-upsert>=1.22.1; extra == 'upsert'
|
|
131
130
|
Description-Content-Type: text/markdown
|
|
@@ -404,13 +403,12 @@ execsql-format --no-sql --in-place scripts/
|
|
|
404
403
|
```yaml
|
|
405
404
|
repos:
|
|
406
405
|
- repo: https://github.com/geocoug/execsql
|
|
407
|
-
rev: v2.
|
|
406
|
+
rev: v2.20.0
|
|
408
407
|
hooks:
|
|
409
408
|
- id: execsql-format
|
|
410
|
-
args: [--in-place]
|
|
411
409
|
```
|
|
412
410
|
|
|
413
|
-
See the [formatter documentation](https://execsql2.readthedocs.io/en/latest/guides/formatter/) for
|
|
411
|
+
The hook rewrites `*.sql` files in place by default. See the [formatter documentation](https://execsql2.readthedocs.io/en/latest/guides/formatter/) for `--check`, `--indent`, and other options.
|
|
414
412
|
|
|
415
413
|
# VS Code Syntax Highlighting
|
|
416
414
|
|
|
@@ -3,7 +3,7 @@ execsql/__main__.py,sha256=HdbK-SAhyUmfB6xINY5AzxdMSxGzWSGEG_2dv42Jn64,315
|
|
|
3
3
|
execsql/api.py,sha256=ZFTo_XZPhG21w2vxaeS1lS6o5XmF1FUJRIaypgTOjA8,20919
|
|
4
4
|
execsql/config.py,sha256=OOrCcn9m3CNuXkxVOLp7uMhQikzUS2wh_QVhvIzRqIM,33296
|
|
5
5
|
execsql/exceptions.py,sha256=EkM5cw2s0D9QCOgS4BU29FEyOnEtCxJ0esPT6l1hT9s,9205
|
|
6
|
-
execsql/format.py,sha256=
|
|
6
|
+
execsql/format.py,sha256=dC2cHGUldOTq1YRENm7vtIIv4vtUswRhxuqeMp99d70,33648
|
|
7
7
|
execsql/models.py,sha256=kCTUQg9-vReM6WNFfB_ZrEppuOW5u1uMBQThSkfPC0o,13264
|
|
8
8
|
execsql/parser.py,sha256=P3ea8k7T_XLMrbhpFNZXwytdShrY302MKnhosqza1lo,15493
|
|
9
9
|
execsql/plugins.py,sha256=2voLwT6eFap6BCBoZYndNNC_bMEJO1f_aP6xQTVXwYI,12815
|
|
@@ -14,21 +14,21 @@ execsql/cli/__init__.py,sha256=aJknKKIGxYCXpny0cyHXfqJsJ95dBtlEXXhPASFG8GQ,23114
|
|
|
14
14
|
execsql/cli/dsn.py,sha256=svaZtrUXFRL2W5G6FRRiKtR6kehOp7urrVhIx_642Z8,2820
|
|
15
15
|
execsql/cli/help.py,sha256=ThwdZuMIfLPxLAPpGWwXFY_UfyWvYOCjdlBNK20Vzd8,5718
|
|
16
16
|
execsql/cli/lint.py,sha256=YqKzFNUhyb_Th69hYgKk1ZZVjCsZfJMIiUGqp06JwNs,17236
|
|
17
|
-
execsql/cli/run.py,sha256=
|
|
17
|
+
execsql/cli/run.py,sha256=0gmn7f7RjSYatOUZ34QIzK8gP_vgQFZE6U4P6EKU2H4,38188
|
|
18
18
|
execsql/data/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
19
19
|
execsql/data/execsql.conf.template,sha256=Sq1Huwb_Uf_lI7zW7m3h11fqU6zjGpGnCU4OVJ_tCe0,10696
|
|
20
20
|
execsql/db/__init__.py,sha256=jTbuafuKOqYtXFR1wvCOoKK5Lr3l1uErfaIbIr6UywI,1063
|
|
21
|
-
execsql/db/access.py,sha256=
|
|
22
|
-
execsql/db/base.py,sha256=
|
|
23
|
-
execsql/db/dsn.py,sha256=
|
|
21
|
+
execsql/db/access.py,sha256=sRJOJJo1psSO5RV6QPsFY1YJE5wc5aNOYCFDtxIn_Wo,19413
|
|
22
|
+
execsql/db/base.py,sha256=0Cy_M3GHdkQDJMbMeWpNyUIPux9m1cVDy21MGMjhJCI,35002
|
|
23
|
+
execsql/db/dsn.py,sha256=CZd5NhUSVvK3irv2maeWsSqBsZjhFsTxIuT29Z0rP2A,6240
|
|
24
24
|
execsql/db/duckdb.py,sha256=79lRzKRhw1Pjfqcrba27S4Oq8a8AbDO_d0XkaNKKPQo,3197
|
|
25
25
|
execsql/db/factory.py,sha256=YHdgyqQYy16548O3fGyElLC5C7DdIgva4Z29OsDxXjs,5367
|
|
26
|
-
execsql/db/firebird.py,sha256=
|
|
27
|
-
execsql/db/mysql.py,sha256=
|
|
26
|
+
execsql/db/firebird.py,sha256=cV5wUzQHoz7yugHdNxAn8IW7fL3v1riIsGLykCZhZwA,9480
|
|
27
|
+
execsql/db/mysql.py,sha256=Z0vI_nQ6vhG3sYa2fv5NOO3JmAsH6_jVjM8FuujQpqI,17961
|
|
28
28
|
execsql/db/oracle.py,sha256=1_odb5xmlm8vjdJdQXz7SHm9dzIbZ5sxP_IxTklH3kA,12018
|
|
29
29
|
execsql/db/postgres.py,sha256=UNzrXzMniEyT3Z7qjCA_HLEUY0PVr1cJShuhAxtl5l0,21241
|
|
30
30
|
execsql/db/sqlite.py,sha256=xooU6bvD9Y3frRpnbyesE63r6E1fwEHkkcN1YD_UIUE,11519
|
|
31
|
-
execsql/db/sqlserver.py,sha256=
|
|
31
|
+
execsql/db/sqlserver.py,sha256=j2ViLoBWzizgaL0u6V4iHfjinrlJ4rpBD3XZiKKdwSU,8968
|
|
32
32
|
execsql/debug/__init__.py,sha256=j6EGUR0dHzUhWN1mHHtf1-Lhjq3Sb1V-vmnq2Ztgj1M,178
|
|
33
33
|
execsql/debug/repl.py,sha256=JObeoEXh15qyk4Q3WQCCqMcBtojlIcu_Xg-ZRDZJi5Q,25491
|
|
34
34
|
execsql/exporters/__init__.py,sha256=-Cnji-OgodJV8ftcDcOyTof0kQMy9J5kKVC8GVFpc3o,670
|
|
@@ -95,29 +95,28 @@ execsql/utils/datetime.py,sha256=rMCXAbvj6bxKCYzC97vrludO6PU5DYQ39buZ0smDC5A,357
|
|
|
95
95
|
execsql/utils/errors.py,sha256=C-9hlpJ7GM5gpDBdTsT4xFhK0OHNjonHgbq7fMl-Vvc,8577
|
|
96
96
|
execsql/utils/fileio.py,sha256=38Z0WBvfaMa624aw3jo9ZOSlUCsacIeJWOhQKYe9YlU,31012
|
|
97
97
|
execsql/utils/gui.py,sha256=kpJkvi8zblXFdO4PgsF8yE_gkOJrsDwWoQWmaDpjJEQ,23402
|
|
98
|
-
execsql/utils/mail.py,sha256=
|
|
98
|
+
execsql/utils/mail.py,sha256=ojdROW6Ti7paVpVMIxRXh7opOdE61fE2duS8xoA6d2Q,5758
|
|
99
99
|
execsql/utils/numeric.py,sha256=xh02ANSRk3nUpQ-rtm66ILoMqoi7HtzCoRMIOT9U8QI,1570
|
|
100
100
|
execsql/utils/regex.py,sha256=diEzTZqU_HHwVMadPAvN1Vgzhl7I03eVaEFGCXyGGL8,3770
|
|
101
101
|
execsql/utils/strings.py,sha256=UQNjpRCEFa1UO6feU-M-9e24wWAvizs_iu_4fFusLxo,8516
|
|
102
102
|
execsql/utils/timer.py,sha256=eDYf5VzCNFk7oo90InJucUm3XcBdhYMogjZMqeg9xzc,1899
|
|
103
|
-
execsql2-2.
|
|
104
|
-
execsql2-2.
|
|
105
|
-
execsql2-2.
|
|
106
|
-
execsql2-2.
|
|
107
|
-
execsql2-2.
|
|
108
|
-
execsql2-2.
|
|
109
|
-
execsql2-2.
|
|
110
|
-
execsql2-2.
|
|
111
|
-
execsql2-2.
|
|
112
|
-
execsql2-2.
|
|
113
|
-
execsql2-2.
|
|
114
|
-
execsql2-2.
|
|
115
|
-
execsql2-2.
|
|
116
|
-
execsql2-2.
|
|
117
|
-
execsql2-2.
|
|
118
|
-
execsql2-2.
|
|
119
|
-
execsql2-2.
|
|
120
|
-
execsql2-2.
|
|
121
|
-
execsql2-2.
|
|
122
|
-
execsql2-2.
|
|
123
|
-
execsql2-2.19.2.dist-info/RECORD,,
|
|
103
|
+
execsql2-2.20.0.data/data/execsql2_extras/README.md,sha256=vX4NTL095dUoA_hesyRMGYBorEZ_Y_tJ9qrd-MVV09I,5032
|
|
104
|
+
execsql2-2.20.0.data/data/execsql2_extras/config_settings.sqlite,sha256=aY5cxR7Q7J6zJ4bC9lu5mHUrhy211Cq3MNKPQVCt02E,20480
|
|
105
|
+
execsql2-2.20.0.data/data/execsql2_extras/example_config_prompt.sql,sha256=2e8KzzVWhho8KxYVHETSVmZdhW7wodioDsqBLSL6m4s,7487
|
|
106
|
+
execsql2-2.20.0.data/data/execsql2_extras/make_config_db.sql,sha256=WwyC6dK-Eh5CAVppiBCDHqiI1_wEI9U95Ytpr4lsZkg,8726
|
|
107
|
+
execsql2-2.20.0.data/data/execsql2_extras/md_compare.sql,sha256=qYYVAjSeHZzjszxV3Bv6bg8Ckbq2kMHl87_gh4sywMU,24140
|
|
108
|
+
execsql2-2.20.0.data/data/execsql2_extras/md_glossary.sql,sha256=hkZ2Onn57LAKKsuXxzhR8tPtcWXkmWEQkwPE58-Tm2k,10796
|
|
109
|
+
execsql2-2.20.0.data/data/execsql2_extras/md_upsert.sql,sha256=_CAK4BzEboRXTNy03SJR-oOjcEdSNMuRBPL6noWUptY,112560
|
|
110
|
+
execsql2-2.20.0.data/data/execsql2_extras/pg_compare.sql,sha256=1zJd4hVUKHR0tncc2qTBC9B4qVV4Us2ITkJpsjN3tMw,24352
|
|
111
|
+
execsql2-2.20.0.data/data/execsql2_extras/pg_glossary.sql,sha256=IKuwna-_8b20ljSkXZruuiQigrCpo7ueQdUqd1MXiuI,9908
|
|
112
|
+
execsql2-2.20.0.data/data/execsql2_extras/pg_upsert.sql,sha256=HpPJtTHvpEjQy03j-3iPxDEOHMRkudOg7O4D4YR38UI,108315
|
|
113
|
+
execsql2-2.20.0.data/data/execsql2_extras/script_template.sql,sha256=2J35ddZPguJ-vwTsz83wErv0jiWUyJcdW_JM0mNKDXA,11155
|
|
114
|
+
execsql2-2.20.0.data/data/execsql2_extras/ss_compare.sql,sha256=j1qVNUPXQsEU7-DoVgDJCGcE0EuIl7whLBT3fgeiMAo,24833
|
|
115
|
+
execsql2-2.20.0.data/data/execsql2_extras/ss_glossary.sql,sha256=2gLxv34xzKt0vy7hSzJH7a9JiMC3ETrv9MofxQwAibU,13065
|
|
116
|
+
execsql2-2.20.0.data/data/execsql2_extras/ss_upsert.sql,sha256=G_8rQ0VzuKIZHWs24O_WrfzpC5S27R1JsL-bFBR3SUQ,117730
|
|
117
|
+
execsql2-2.20.0.dist-info/METADATA,sha256=2j1tQ7oxlDAfsmIPQwgDxxn9O34FN7MbKT1nLznLi-M,22603
|
|
118
|
+
execsql2-2.20.0.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
|
|
119
|
+
execsql2-2.20.0.dist-info/entry_points.txt,sha256=sUOxkM-dN1eBGGpSpDLsAaE0yNXYQKWZAfxPOlMkQyk,90
|
|
120
|
+
execsql2-2.20.0.dist-info/licenses/LICENSE.txt,sha256=LBdhuxejF8_bLCHZ2kWfmDXpDGUu914Gbd6_3JjCRe0,676
|
|
121
|
+
execsql2-2.20.0.dist-info/licenses/NOTICE,sha256=McYzgxYav3U1OaVsY4Su1sfBrfmplpRdA9b6-gCDQCg,342
|
|
122
|
+
execsql2-2.20.0.dist-info/RECORD,,
|
|
@@ -1,359 +0,0 @@
|
|
|
1
|
-
# execsql.conf — Configuration file for execsql
|
|
2
|
-
#
|
|
3
|
-
# Documentation: https://execsql2.readthedocs.io/en/latest/reference/configuration/
|
|
4
|
-
#
|
|
5
|
-
# This file uses INI format. Section names are case-sensitive (lowercase).
|
|
6
|
-
# Property names are not case-sensitive. Lines starting with # are comments.
|
|
7
|
-
#
|
|
8
|
-
# execsql searches for this file in the following locations (in order):
|
|
9
|
-
# 1. System-wide: /etc/execsql.conf (Linux/macOS) or %APPDATA%\execsql.conf (Windows)
|
|
10
|
-
# 2. User: ~/.config/execsql.conf
|
|
11
|
-
# 3. Script directory: same directory as the SQL script
|
|
12
|
-
# 4. Working directory: current directory
|
|
13
|
-
#
|
|
14
|
-
# Later files override earlier ones. CLI arguments override everything.
|
|
15
|
-
# Generate this template with: execsql --init-config > execsql.conf
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
[connect]
|
|
19
|
-
# Connection information for the initial database connection.
|
|
20
|
-
|
|
21
|
-
# Database type.
|
|
22
|
-
# p: PostgreSQL l: SQLite m: MySQL/MariaDB k: DuckDB
|
|
23
|
-
# f: Firebird s: SQL Server a: MS Access o: Oracle d: DSN
|
|
24
|
-
#db_type=l
|
|
25
|
-
|
|
26
|
-
# Server name for client-server databases.
|
|
27
|
-
#server=
|
|
28
|
-
|
|
29
|
-
# Database name for client-server databases.
|
|
30
|
-
#db=
|
|
31
|
-
|
|
32
|
-
# Database file path for file-based databases (SQLite, DuckDB, Access).
|
|
33
|
-
#db_file=
|
|
34
|
-
|
|
35
|
-
# Port number for server-based databases. Only needed if not the default.
|
|
36
|
-
#port=
|
|
37
|
-
|
|
38
|
-
# User name for client-server databases.
|
|
39
|
-
#username=
|
|
40
|
-
|
|
41
|
-
# User name for password-protected MS Access databases if not "Admin".
|
|
42
|
-
#access_username=
|
|
43
|
-
|
|
44
|
-
# Whether to prompt for a password.
|
|
45
|
-
# Values: Yes or No. Default: Yes.
|
|
46
|
-
#password_prompt=Yes
|
|
47
|
-
|
|
48
|
-
# Whether to use the OS credential store (keyring) for password storage.
|
|
49
|
-
# Requires: pip install execsql2[auth]
|
|
50
|
-
# Values: Yes or No. Default: Yes.
|
|
51
|
-
#use_keyring=Yes
|
|
52
|
-
|
|
53
|
-
# Whether to create a new database if it does not exist (SQLite, PostgreSQL, DuckDB).
|
|
54
|
-
# Values: Yes or No. Default: No.
|
|
55
|
-
#new_db=No
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
[encoding]
|
|
59
|
-
# Character encoding for text input and output.
|
|
60
|
-
|
|
61
|
-
#database=
|
|
62
|
-
#script=utf8
|
|
63
|
-
#import=utf8
|
|
64
|
-
#output=utf8
|
|
65
|
-
|
|
66
|
-
# How to handle incompatible encodings.
|
|
67
|
-
# Values: ignore, replace, xmlcharrefreplace, or backslashreplace.
|
|
68
|
-
#error_response=
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
[input]
|
|
72
|
-
# Settings for handling imported data.
|
|
73
|
-
|
|
74
|
-
# Whether to convert numeric values to double precision when using MS Access.
|
|
75
|
-
# Values: Yes or No. Default: No.
|
|
76
|
-
#access_use_numeric=No
|
|
77
|
-
|
|
78
|
-
# Whether to treat integer values of 0 and 1 as Booleans.
|
|
79
|
-
# Values: Yes or No. Default: Yes.
|
|
80
|
-
#boolean_int=Yes
|
|
81
|
-
|
|
82
|
-
# Whether to recognize only full words as Boolean, not "Y", "N", "T", and "F".
|
|
83
|
-
# Values: Yes or No. Default: No.
|
|
84
|
-
#boolean_words=No
|
|
85
|
-
|
|
86
|
-
# Whether to replace non-alphanumeric characters in column names with underscores.
|
|
87
|
-
# Values: Yes or No. Default: No.
|
|
88
|
-
#clean_column_headers=No
|
|
89
|
-
|
|
90
|
-
# Whether to create column headers when missing from an input file.
|
|
91
|
-
# Headers will be "Col1", "Col2", etc.
|
|
92
|
-
# Values: Yes or No. Default: No.
|
|
93
|
-
#create_column_headers=No
|
|
94
|
-
|
|
95
|
-
# Whether to deduplicate column names by appending "_N" (column number).
|
|
96
|
-
# Values: Yes or No. Default: No.
|
|
97
|
-
#dedup_column_headers=No
|
|
98
|
-
|
|
99
|
-
# Whether to completely delete columns with missing headers.
|
|
100
|
-
# Values: Yes or No. Default: No.
|
|
101
|
-
#delete_empty_columns=No
|
|
102
|
-
|
|
103
|
-
# Whether to import completely empty rows.
|
|
104
|
-
# Values: Yes or No. Default: Yes.
|
|
105
|
-
#empty_rows=Yes
|
|
106
|
-
|
|
107
|
-
# Whether empty strings in input data are preserved or replaced by NULL.
|
|
108
|
-
# Values: Yes or No. Default: Yes.
|
|
109
|
-
#empty_strings=Yes
|
|
110
|
-
|
|
111
|
-
# Whether to fold column headers to lowercase, uppercase, or leave unchanged.
|
|
112
|
-
# Values: No, Lower, or Upper. Default: No.
|
|
113
|
-
#fold_column_headers=No
|
|
114
|
-
|
|
115
|
-
# The size of the import buffer, in KB, for the Postgres fast file reading feature.
|
|
116
|
-
#import_buffer=
|
|
117
|
-
|
|
118
|
-
# Whether to ignore extra columns in imported data that are not in the target table.
|
|
119
|
-
# Values: Yes or No. Default: No.
|
|
120
|
-
#import_only_common_columns=No
|
|
121
|
-
|
|
122
|
-
# Interval (in rows) at which to report import progress. 0 disables.
|
|
123
|
-
# Value: A non-negative integer. Default: 0.
|
|
124
|
-
#import_progress_interval=0
|
|
125
|
-
|
|
126
|
-
# The number of rows to buffer when importing data.
|
|
127
|
-
# Value: A positive non-zero integer. Default: 1000.
|
|
128
|
-
#import_row_buffer=1000
|
|
129
|
-
|
|
130
|
-
# The maximum value that will be assigned an integer data type when creating tables.
|
|
131
|
-
#max_int=2147483647
|
|
132
|
-
|
|
133
|
-
# Whether IMPORT treats all columns as text instead of inferring data types.
|
|
134
|
-
# Values: Yes or No. Default: No.
|
|
135
|
-
#only_strings=No
|
|
136
|
-
|
|
137
|
-
# Whether newlines embedded in imported text should be replaced with a space.
|
|
138
|
-
# Values: Yes or No. Default: No.
|
|
139
|
-
#replace_newlines=No
|
|
140
|
-
|
|
141
|
-
# The number of lines to scan in an input file to identify delimiters and quotes.
|
|
142
|
-
# Value: A positive non-zero integer. Default: 100.
|
|
143
|
-
#scan_lines=100
|
|
144
|
-
|
|
145
|
-
# Whether to show a progress bar for long-running IMPORT operations.
|
|
146
|
-
# Values: Yes or No. Default: No.
|
|
147
|
-
#show_progress=No
|
|
148
|
-
|
|
149
|
-
# Whether to trim spaces/underscores from column headers.
|
|
150
|
-
# Values: None, Both, Left, or Right. Default: None.
|
|
151
|
-
#trim_column_headers=None
|
|
152
|
-
|
|
153
|
-
# Whether to trim leading/trailing whitespace from imported text.
|
|
154
|
-
# Values: Yes or No. Default: No.
|
|
155
|
-
#trim_strings=No
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
[output]
|
|
159
|
-
# Settings for data export and output.
|
|
160
|
-
|
|
161
|
-
# Whether WRITE output should also be sent to the log file.
|
|
162
|
-
# Values: Yes or No. Default: No.
|
|
163
|
-
#log_write_messages=No
|
|
164
|
-
|
|
165
|
-
# Whether to create non-existent directories for WRITE or EXPORT output.
|
|
166
|
-
# Values: Yes or No. Default: No.
|
|
167
|
-
#make_export_dirs=No
|
|
168
|
-
|
|
169
|
-
# Whether to quote all text values in delimited exports.
|
|
170
|
-
# Values: Yes or No. Default: No.
|
|
171
|
-
#quote_all_text=No
|
|
172
|
-
|
|
173
|
-
# The number of rows to buffer from the database when exporting.
|
|
174
|
-
# Value: A positive non-zero integer. Default: 1000.
|
|
175
|
-
#export_row_buffer=1000
|
|
176
|
-
|
|
177
|
-
# The length for text data types when exporting to HDF5.
|
|
178
|
-
# Value: A positive non-zero integer. Default: 1000.
|
|
179
|
-
#hdf5_text_len=1000
|
|
180
|
-
|
|
181
|
-
# The URI of a CSS file to use when exporting to HTML.
|
|
182
|
-
#css_file=
|
|
183
|
-
|
|
184
|
-
# CSS style commands to embed in HTML exports.
|
|
185
|
-
#css_styles=
|
|
186
|
-
|
|
187
|
-
# Seconds to keep retrying when an output file has an access conflict.
|
|
188
|
-
# Value: A positive non-zero number. Default: 600.
|
|
189
|
-
#outfile_open_timeout=600
|
|
190
|
-
|
|
191
|
-
# The template processor for template-based exports.
|
|
192
|
-
# Value: jinja.
|
|
193
|
-
#template_processor=
|
|
194
|
-
|
|
195
|
-
# Internal buffer size (in MB) for ZIP exports.
|
|
196
|
-
# Value: A positive non-zero integer. Default: 10.
|
|
197
|
-
#zip_buffer_mb=10
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
[interface]
|
|
201
|
-
# Settings for GUI dialogs and console output.
|
|
202
|
-
|
|
203
|
-
# GUI backend framework.
|
|
204
|
-
# Values: tkinter or textual. Default: tkinter.
|
|
205
|
-
#gui_framework=tkinter
|
|
206
|
-
|
|
207
|
-
# GUI dialog usage level.
|
|
208
|
-
# 0: none (default)
|
|
209
|
-
# 1: passwords and PAUSE metacommands
|
|
210
|
-
# 2: also database selection dialogs
|
|
211
|
-
# 3: open a console window immediately
|
|
212
|
-
#gui_level=0
|
|
213
|
-
|
|
214
|
-
# The height (in lines) of the console window.
|
|
215
|
-
# Value: An integer >= 5. Default: 25.
|
|
216
|
-
#console_height=25
|
|
217
|
-
|
|
218
|
-
# The width (in characters) of the console window.
|
|
219
|
-
# Value: An integer >= 20. Default: 100.
|
|
220
|
-
#console_width=100
|
|
221
|
-
|
|
222
|
-
# Whether the console stays open at the end of the script.
|
|
223
|
-
# Values: Yes or No. Default: No.
|
|
224
|
-
#console_wait_when_done=No
|
|
225
|
-
|
|
226
|
-
# Whether the console stays open when an error halt occurs.
|
|
227
|
-
# Values: Yes or No. Default: No.
|
|
228
|
-
#console_wait_when_error_halt=No
|
|
229
|
-
|
|
230
|
-
# Whether to write warning messages to the console.
|
|
231
|
-
# Values: Yes or No. Default: No.
|
|
232
|
-
#write_warnings=No
|
|
233
|
-
|
|
234
|
-
# Text to prefix to WRITE output (with a space separator). "clear" removes a previous prefix.
|
|
235
|
-
#write_prefix=
|
|
236
|
-
|
|
237
|
-
# Text to append to WRITE output (with a space separator). "clear" removes a previous suffix.
|
|
238
|
-
#write_suffix=
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
[email]
|
|
242
|
-
# Settings for the EMAIL metacommand.
|
|
243
|
-
|
|
244
|
-
# SMTP server connection.
|
|
245
|
-
#host=
|
|
246
|
-
#port=
|
|
247
|
-
#username=
|
|
248
|
-
#password=
|
|
249
|
-
|
|
250
|
-
# Whether to use implicit TLS (port 465). Preferred over use_tls.
|
|
251
|
-
# Values: Yes or No. Default: No.
|
|
252
|
-
#use_ssl=No
|
|
253
|
-
|
|
254
|
-
# Whether to use STARTTLS (port 587).
|
|
255
|
-
# Values: Yes or No. Default: No.
|
|
256
|
-
#use_tls=No
|
|
257
|
-
|
|
258
|
-
# Email body format.
|
|
259
|
-
# Values: plain or html. Default: plain.
|
|
260
|
-
#email_format=plain
|
|
261
|
-
|
|
262
|
-
# Custom CSS styles for HTML email.
|
|
263
|
-
#message_css=
|
|
264
|
-
|
|
265
|
-
# Obfuscated password (XOR, not cryptographically secure — use keyring instead).
|
|
266
|
-
# See: https://execsql2.readthedocs.io/en/latest/reference/security/#credentials
|
|
267
|
-
#enc_password=
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
[config]
|
|
271
|
-
# General program configuration and additional config file chaining.
|
|
272
|
-
|
|
273
|
-
# Path to an additional configuration file to load.
|
|
274
|
-
#config_file=
|
|
275
|
-
|
|
276
|
-
# Whether to allow the SYSTEM_CMD (SHELL) metacommand.
|
|
277
|
-
# Set to No to prevent scripts from executing OS commands.
|
|
278
|
-
# Also controllable via --no-system-cmd CLI flag.
|
|
279
|
-
# Values: Yes or No. Default: Yes.
|
|
280
|
-
#allow_system_cmd=Yes
|
|
281
|
-
|
|
282
|
-
# Whether to allow the RM_FILE metacommand (which deletes a file).
|
|
283
|
-
# Set to No to prevent scripts from deleting files.
|
|
284
|
-
# Also controllable via --no-rm-file CLI flag.
|
|
285
|
-
# Values: Yes or No. Default: Yes.
|
|
286
|
-
#allow_rm_file=Yes
|
|
287
|
-
|
|
288
|
-
# Whether to allow the SERVE metacommand (which opens an HTTP server on
|
|
289
|
-
# a local port to serve a single file). Set to No to disable.
|
|
290
|
-
# Also controllable via --no-serve CLI flag.
|
|
291
|
-
# Values: Yes or No. Default: Yes.
|
|
292
|
-
#allow_serve=Yes
|
|
293
|
-
|
|
294
|
-
# Root directory under which INCLUDE / EXECUTE SCRIPT targets must
|
|
295
|
-
# resolve. When set, attempts to include files outside this root via
|
|
296
|
-
# ../, absolute paths, drive letters, or UNC paths are rejected with
|
|
297
|
-
# an error. Default: no containment (any readable path is permitted).
|
|
298
|
-
#include_root=
|
|
299
|
-
|
|
300
|
-
# Root directory under which SERVE targets must resolve. Same
|
|
301
|
-
# containment semantics as include_root. Default: no containment.
|
|
302
|
-
#serve_root=
|
|
303
|
-
|
|
304
|
-
# Root directory under which Jinja2 / string.Template loader paths
|
|
305
|
-
# must resolve. Same containment semantics as include_root.
|
|
306
|
-
# Default: no containment.
|
|
307
|
-
#template_root=
|
|
308
|
-
|
|
309
|
-
# Maximum byte size of any single substitution-variable expansion,
|
|
310
|
-
# enforced by the substitute_vars() engine to defeat exponential-
|
|
311
|
-
# expansion bombs. Default: 10 MB (10485760).
|
|
312
|
-
#max_substitution_bytes=10485760
|
|
313
|
-
|
|
314
|
-
# Whether to log all data variable assignments.
|
|
315
|
-
# Values: Yes or No. Default: Yes.
|
|
316
|
-
#log_datavars=Yes
|
|
317
|
-
|
|
318
|
-
# Whether to log all executed SQL statements.
|
|
319
|
-
# Values: Yes or No. Default: No.
|
|
320
|
-
#log_sql=No
|
|
321
|
-
|
|
322
|
-
# Maximum log file size in MB before rotation. 0 disables rotation.
|
|
323
|
-
# Value: A non-negative integer. Default: 0.
|
|
324
|
-
#max_log_size_mb=0
|
|
325
|
-
|
|
326
|
-
# Whether to place execsql.log in the user's home directory instead of the script directory.
|
|
327
|
-
# Values: Yes or No. Default: No.
|
|
328
|
-
#user_logfile=No
|
|
329
|
-
|
|
330
|
-
# Seconds between DAO query creation and ODBC data access (MS Access only).
|
|
331
|
-
# Must be >= 5.0.
|
|
332
|
-
#dao_flush_delay_secs=5.0
|
|
333
|
-
|
|
334
|
-
# Platform-specific additional config files.
|
|
335
|
-
#linux_config_file=
|
|
336
|
-
#macos_config_file=
|
|
337
|
-
#win_config_file=
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
[variables]
|
|
341
|
-
# Substitution variables defined at startup. No pre-defined keys.
|
|
342
|
-
# Example:
|
|
343
|
-
# output_dir = /data/exports
|
|
344
|
-
# env_name = production
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
[include_required]
|
|
348
|
-
# Script files to include before the main script (required — error if missing).
|
|
349
|
-
# Keys are integers defining include order; values are file paths.
|
|
350
|
-
# Example:
|
|
351
|
-
# 1 = /opt/execsql/common/setup.sql
|
|
352
|
-
# 2 = /opt/execsql/common/helpers.sql
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
[include_optional]
|
|
356
|
-
# Script files to include before the main script (optional — skipped if missing).
|
|
357
|
-
# Same format as [include_required].
|
|
358
|
-
# Example:
|
|
359
|
-
# 1 = local_overrides.sql
|
|
File without changes
|
{execsql2-2.19.2.data → execsql2-2.20.0.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
|