execsql2 2.17.3__py3-none-any.whl → 2.18.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- execsql/cli/__init__.py +15 -5
- execsql/cli/lint.py +296 -430
- execsql/cli/run.py +29 -2
- execsql/config.py +20 -0
- execsql/db/access.py +6 -0
- execsql/db/base.py +57 -1
- execsql/db/dsn.py +19 -9
- execsql/db/firebird.py +6 -0
- execsql/db/mysql.py +81 -0
- execsql/db/oracle.py +6 -0
- execsql/db/sqlite.py +37 -18
- execsql/db/sqlserver.py +31 -6
- execsql/exporters/base.py +1 -1
- execsql/exporters/duckdb.py +8 -4
- execsql/exporters/ods.py +11 -0
- execsql/exporters/sqlite.py +10 -3
- execsql/exporters/templates.py +10 -0
- execsql/exporters/xls.py +4 -0
- execsql/exporters/xlsx.py +9 -0
- execsql/importers/json.py +49 -32
- execsql/metacommands/conditions.py +7 -2
- execsql/metacommands/dispatch.py +5 -10
- execsql/metacommands/io_export.py +21 -26
- execsql/metacommands/io_fileops.py +21 -3
- execsql/metacommands/io_import.py +23 -3
- execsql/metacommands/script_ext.py +8 -7
- execsql/script/ast.py +8 -0
- execsql/script/engine.py +33 -12
- execsql/script/executor.py +12 -0
- execsql/script/variables.py +41 -15
- execsql/utils/auth.py +49 -1
- execsql/utils/fileio.py +120 -0
- execsql/utils/gui.py +11 -1
- {execsql2-2.17.3.data → execsql2-2.18.1.data}/data/execsql2_extras/md_compare.sql +12 -12
- {execsql2-2.17.3.data → execsql2-2.18.1.data}/data/execsql2_extras/md_glossary.sql +5 -5
- {execsql2-2.17.3.data → execsql2-2.18.1.data}/data/execsql2_extras/md_upsert.sql +13 -13
- {execsql2-2.17.3.data → execsql2-2.18.1.data}/data/execsql2_extras/pg_compare.sql +24 -24
- {execsql2-2.17.3.data → execsql2-2.18.1.data}/data/execsql2_extras/pg_glossary.sql +5 -5
- {execsql2-2.17.3.data → execsql2-2.18.1.data}/data/execsql2_extras/pg_upsert.sql +29 -29
- {execsql2-2.17.3.data → execsql2-2.18.1.data}/data/execsql2_extras/script_template.sql +2 -2
- {execsql2-2.17.3.data → execsql2-2.18.1.data}/data/execsql2_extras/ss_compare.sql +24 -24
- {execsql2-2.17.3.data → execsql2-2.18.1.data}/data/execsql2_extras/ss_glossary.sql +6 -6
- {execsql2-2.17.3.data → execsql2-2.18.1.data}/data/execsql2_extras/ss_upsert.sql +2917 -2917
- {execsql2-2.17.3.dist-info → execsql2-2.18.1.dist-info}/METADATA +47 -40
- {execsql2-2.17.3.dist-info → execsql2-2.18.1.dist-info}/RECORD +54 -55
- execsql/cli/lint_ast.py +0 -439
- {execsql2-2.17.3.data → execsql2-2.18.1.data}/data/execsql2_extras/README.md +0 -0
- {execsql2-2.17.3.data → execsql2-2.18.1.data}/data/execsql2_extras/config_settings.sqlite +0 -0
- {execsql2-2.17.3.data → execsql2-2.18.1.data}/data/execsql2_extras/example_config_prompt.sql +0 -0
- {execsql2-2.17.3.data → execsql2-2.18.1.data}/data/execsql2_extras/execsql.conf +0 -0
- {execsql2-2.17.3.data → execsql2-2.18.1.data}/data/execsql2_extras/make_config_db.sql +0 -0
- {execsql2-2.17.3.dist-info → execsql2-2.18.1.dist-info}/WHEEL +0 -0
- {execsql2-2.17.3.dist-info → execsql2-2.18.1.dist-info}/entry_points.txt +0 -0
- {execsql2-2.17.3.dist-info → execsql2-2.18.1.dist-info}/licenses/LICENSE.txt +0 -0
- {execsql2-2.17.3.dist-info → execsql2-2.18.1.dist-info}/licenses/NOTICE +0 -0
execsql/script/variables.py
CHANGED
|
@@ -9,7 +9,6 @@ Classes:
|
|
|
9
9
|
- :class:`ScriptArgSubVarSet` — per-script ``#``-prefixed argument overlay.
|
|
10
10
|
"""
|
|
11
11
|
|
|
12
|
-
import os
|
|
13
12
|
import re
|
|
14
13
|
from typing import Any
|
|
15
14
|
|
|
@@ -244,17 +243,31 @@ class SubVarSet:
|
|
|
244
243
|
if sub is None:
|
|
245
244
|
sub = ""
|
|
246
245
|
sub = str(sub)
|
|
247
|
-
|
|
248
|
-
|
|
246
|
+
# B07a/F002: reject embedded NUL bytes regardless of quoter;
|
|
247
|
+
# most DBMS protocols truncate at NUL and PostgreSQL rejects.
|
|
248
|
+
if "\x00" in sub:
|
|
249
|
+
raise ValueError(
|
|
250
|
+
f"Substitution variable {varname!r} contains a NUL byte; refusing to interpolate.",
|
|
251
|
+
)
|
|
249
252
|
quote = m.group("q")
|
|
250
253
|
if quote == "'":
|
|
251
|
-
#
|
|
252
|
-
#
|
|
253
|
-
|
|
254
|
+
# B07a/F002: wrap in single quotes, doubling embedded
|
|
255
|
+
# apostrophes AND escaping backslashes so MySQL default
|
|
256
|
+
# mode and PostgreSQL E-string literals can't end the
|
|
257
|
+
# quoted region via ``\'``. The previous Windows-only
|
|
258
|
+
# branch (``os.name != 'posix'``) applied the escape on
|
|
259
|
+
# the wrong axis — host OS vs target DBMS.
|
|
260
|
+
sub = "'" + sub.replace("\\", "\\\\").replace("'", "''") + "'"
|
|
254
261
|
elif quote == '"':
|
|
255
|
-
#
|
|
256
|
-
#
|
|
257
|
-
|
|
262
|
+
# B07a/F001: wrap in double quotes, doubling embedded
|
|
263
|
+
# ``"`` so a value containing ``"; DROP TABLE x; --``
|
|
264
|
+
# produces a valid quoted identifier rather than a
|
|
265
|
+
# closing quote followed by a second statement.
|
|
266
|
+
sub = '"' + sub.replace('"', '""') + '"'
|
|
267
|
+
else:
|
|
268
|
+
# Bare !!var!! token — preserve the raw value verbatim
|
|
269
|
+
# but still defend against NUL bytes (handled above).
|
|
270
|
+
pass
|
|
258
271
|
return command_str[: m.start()] + sub + command_str[m.end() :], True
|
|
259
272
|
# Token found but variable not defined — skip it and keep searching.
|
|
260
273
|
m = self._TOKEN_RX.search(command_str, m.end())
|
|
@@ -270,32 +283,45 @@ class SubVarSet:
|
|
|
270
283
|
compilation to avoid O(N) ``re.compile`` calls on every invocation.
|
|
271
284
|
"""
|
|
272
285
|
cmd_lower = command_str.lower()
|
|
286
|
+
|
|
287
|
+
def _check_nul(name: str, value: str) -> None:
|
|
288
|
+
"""B07a/F002: raise when *value* (about to be interpolated)
|
|
289
|
+
contains a NUL byte. Called only after a token match so an
|
|
290
|
+
unrelated variable's NUL value never blocks substitution of
|
|
291
|
+
a different variable."""
|
|
292
|
+
if "\x00" in value:
|
|
293
|
+
raise ValueError(
|
|
294
|
+
f"Substitution variable {name!r} contains a NUL byte; refusing to interpolate.",
|
|
295
|
+
)
|
|
296
|
+
|
|
273
297
|
for varname, sub in self._subs_dict.items():
|
|
274
298
|
if sub is None:
|
|
275
299
|
sub = ""
|
|
276
300
|
sub = str(sub)
|
|
277
|
-
if os.name != "posix":
|
|
278
|
-
sub = sub.replace("\\", "\\\\")
|
|
279
301
|
# Standard token: !!varname!!
|
|
280
302
|
token = f"!!{varname}!!"
|
|
281
303
|
idx = cmd_lower.find(token)
|
|
282
304
|
if idx != -1:
|
|
305
|
+
_check_nul(varname, sub)
|
|
283
306
|
return command_str[:idx] + sub + command_str[idx + len(token) :], True
|
|
284
|
-
# Single-quote-wrapped token: !'!varname!'!
|
|
307
|
+
# Single-quote-wrapped token: !'!varname!'! — escape ``\`` and
|
|
308
|
+
# double embedded ``'`` (see substitute_one for rationale).
|
|
285
309
|
tokenq = f"!'!{varname}!'!"
|
|
286
310
|
idxq = cmd_lower.find(tokenq)
|
|
287
311
|
if idxq != -1:
|
|
288
|
-
|
|
312
|
+
_check_nul(varname, sub)
|
|
313
|
+
wrapped = "'" + sub.replace("\\", "\\\\").replace("'", "''") + "'"
|
|
289
314
|
return (
|
|
290
315
|
command_str[:idxq] + wrapped + command_str[idxq + len(tokenq) :],
|
|
291
316
|
True,
|
|
292
317
|
)
|
|
293
|
-
# Double-quote-wrapped token: !"!varname!"!
|
|
318
|
+
# Double-quote-wrapped token: !"!varname!"! — double embedded ``"``.
|
|
294
319
|
tokendq = f'!"!{varname}!"!'
|
|
295
320
|
idxdq = cmd_lower.find(tokendq)
|
|
296
321
|
if idxdq != -1:
|
|
322
|
+
_check_nul(varname, sub)
|
|
297
323
|
return (
|
|
298
|
-
command_str[:idxdq] + '"' + sub + '"' + command_str[idxdq + len(tokendq) :],
|
|
324
|
+
command_str[:idxdq] + '"' + sub.replace('"', '""') + '"' + command_str[idxdq + len(tokendq) :],
|
|
299
325
|
True,
|
|
300
326
|
)
|
|
301
327
|
return command_str, False
|
execsql/utils/auth.py
CHANGED
|
@@ -29,11 +29,56 @@ import getpass
|
|
|
29
29
|
|
|
30
30
|
import execsql.state as _state
|
|
31
31
|
|
|
32
|
-
__all__ = ["get_password", "
|
|
32
|
+
__all__ = ["clear_stored_password", "get_password", "is_plaintext_keyring", "password_from_keyring"]
|
|
33
33
|
|
|
34
34
|
# Tracks whether the most recent get_password() call returned a keyring-stored value.
|
|
35
35
|
_last_from_keyring: bool = False
|
|
36
36
|
|
|
37
|
+
# Tracks whether we've already warned about a plaintext keyring backend
|
|
38
|
+
# this process — keyring is a one-shot warning, not a per-call nag.
|
|
39
|
+
_plaintext_warned: bool = False
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def is_plaintext_keyring() -> bool:
|
|
43
|
+
"""Return True if the active keyring backend stores secrets in cleartext.
|
|
44
|
+
|
|
45
|
+
B20/F040: on headless Linux without a real Secret Service, the
|
|
46
|
+
``keyrings.alt.file.PlaintextKeyring`` (or
|
|
47
|
+
``EncryptedKeyring`` with a hard-coded passphrase) backend is
|
|
48
|
+
used silently. Detect by inspecting the active backend's module
|
|
49
|
+
path so callers can warn the user instead of pretending secrets
|
|
50
|
+
are encrypted.
|
|
51
|
+
"""
|
|
52
|
+
try:
|
|
53
|
+
import keyring
|
|
54
|
+
|
|
55
|
+
backend = keyring.get_keyring()
|
|
56
|
+
module = type(backend).__module__
|
|
57
|
+
return "keyrings.alt" in module or "fail" in module.lower()
|
|
58
|
+
except Exception:
|
|
59
|
+
return False
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _warn_if_plaintext_keyring() -> None:
|
|
63
|
+
"""Print a one-time warning if the active keyring backend is plaintext."""
|
|
64
|
+
global _plaintext_warned
|
|
65
|
+
if _plaintext_warned or not is_plaintext_keyring():
|
|
66
|
+
return
|
|
67
|
+
_plaintext_warned = True
|
|
68
|
+
try:
|
|
69
|
+
import keyring
|
|
70
|
+
import sys
|
|
71
|
+
|
|
72
|
+
backend_name = type(keyring.get_keyring()).__name__
|
|
73
|
+
print(
|
|
74
|
+
f"WARNING: active keyring backend ({backend_name}) stores secrets in "
|
|
75
|
+
f"cleartext or with a hard-coded key. Stored passwords are not "
|
|
76
|
+
f"meaningfully protected.",
|
|
77
|
+
file=sys.stderr,
|
|
78
|
+
)
|
|
79
|
+
except Exception:
|
|
80
|
+
pass
|
|
81
|
+
|
|
37
82
|
|
|
38
83
|
def _keyring_service(dbms_name: str, database_name: str, server_name: str | None) -> str:
|
|
39
84
|
"""Build a keyring service name from connection parameters."""
|
|
@@ -53,6 +98,9 @@ def _keyring_get(service: str, username: str) -> str | None:
|
|
|
53
98
|
|
|
54
99
|
def _keyring_set(service: str, username: str, password: str) -> bool:
|
|
55
100
|
"""Try to store a password in the OS keyring. Returns True on success."""
|
|
101
|
+
# B20/F040: warn before storing into a plaintext backend so the user
|
|
102
|
+
# knows their secret is not meaningfully protected at rest.
|
|
103
|
+
_warn_if_plaintext_keyring()
|
|
56
104
|
try:
|
|
57
105
|
import keyring
|
|
58
106
|
|
execsql/utils/fileio.py
CHANGED
|
@@ -84,6 +84,102 @@ def check_dir(filename: str) -> None:
|
|
|
84
84
|
raise ErrInfo(type="error", other_msg=f"The directory for file '{filename}' does not exist.")
|
|
85
85
|
|
|
86
86
|
|
|
87
|
+
def check_zip_decompression_ratio(
|
|
88
|
+
path: str | os.PathLike[str],
|
|
89
|
+
*,
|
|
90
|
+
max_uncompressed_mb: int = 500,
|
|
91
|
+
max_ratio: int = 100,
|
|
92
|
+
) -> None:
|
|
93
|
+
"""Reject a zip-based workbook that looks like a decompression bomb.
|
|
94
|
+
|
|
95
|
+
OOXML (``.xlsx``) is a zip archive. A maliciously crafted file
|
|
96
|
+
can name a 1 GB uncompressed member that compresses to a few KB,
|
|
97
|
+
so blindly handing the path to ``openpyxl.load_workbook`` lets
|
|
98
|
+
the parser allocate proportional memory. This wrapper inspects
|
|
99
|
+
the zip directory entries before any parsing happens and raises
|
|
100
|
+
:class:`ErrInfo` when either bound is exceeded:
|
|
101
|
+
|
|
102
|
+
* ``max_uncompressed_mb`` — sum of all members' uncompressed
|
|
103
|
+
sizes (default 500 MB).
|
|
104
|
+
* ``max_ratio`` — per-member uncompressed:compressed ratio
|
|
105
|
+
(default 100:1; legitimate XML-heavy XLSX rarely exceeds 30:1).
|
|
106
|
+
|
|
107
|
+
No-op when *path* is not a zipfile (e.g. legacy ``.xls`` OLE-CDF).
|
|
108
|
+
"""
|
|
109
|
+
import zipfile
|
|
110
|
+
|
|
111
|
+
try:
|
|
112
|
+
with zipfile.ZipFile(path) as zf:
|
|
113
|
+
total_uncompressed = 0
|
|
114
|
+
for info in zf.infolist():
|
|
115
|
+
total_uncompressed += info.file_size
|
|
116
|
+
if info.compress_size > 0:
|
|
117
|
+
ratio = info.file_size / info.compress_size
|
|
118
|
+
if ratio > max_ratio:
|
|
119
|
+
raise ErrInfo(
|
|
120
|
+
type="error",
|
|
121
|
+
other_msg=(
|
|
122
|
+
f"Refusing to open '{path}': member '{info.filename}' has "
|
|
123
|
+
f"compression ratio {ratio:.1f}:1 (limit {max_ratio}:1) - "
|
|
124
|
+
"possible zip-bomb."
|
|
125
|
+
),
|
|
126
|
+
)
|
|
127
|
+
limit_bytes = max_uncompressed_mb * 1024 * 1024
|
|
128
|
+
if total_uncompressed > limit_bytes:
|
|
129
|
+
raise ErrInfo(
|
|
130
|
+
type="error",
|
|
131
|
+
other_msg=(
|
|
132
|
+
f"Refusing to open '{path}': total uncompressed size "
|
|
133
|
+
f"{total_uncompressed / 1024 / 1024:.0f} MB exceeds limit "
|
|
134
|
+
f"{max_uncompressed_mb} MB - possible zip-bomb."
|
|
135
|
+
),
|
|
136
|
+
)
|
|
137
|
+
except zipfile.BadZipFile:
|
|
138
|
+
# Not a zip file (legacy .xls is OLE-CDF, not zip). Nothing to check.
|
|
139
|
+
pass
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def safe_output_path(user_path: str, root: str | os.PathLike[str] | None) -> str:
|
|
143
|
+
"""Resolve *user_path* and verify it lives under *root*.
|
|
144
|
+
|
|
145
|
+
If *root* is ``None`` or empty, returns *user_path* unchanged — the
|
|
146
|
+
caller has opted out of containment (current default behavior, no
|
|
147
|
+
regression for users who don't set the corresponding config key).
|
|
148
|
+
|
|
149
|
+
If *root* is set, *user_path* is joined to *root* (when relative) or
|
|
150
|
+
interpreted directly (when absolute), then ``.resolve()``'d and
|
|
151
|
+
verified to be at or below the resolved root. Absolute paths,
|
|
152
|
+
Windows drive letters, and UNC paths that don't fall under *root*
|
|
153
|
+
raise :class:`ErrInfo` rather than silently bypassing the
|
|
154
|
+
boundary.
|
|
155
|
+
|
|
156
|
+
Returns the resolved path as a string. Raises :class:`ErrInfo`
|
|
157
|
+
when *user_path* escapes *root*.
|
|
158
|
+
"""
|
|
159
|
+
if not root:
|
|
160
|
+
return user_path
|
|
161
|
+
root_path = Path(root).expanduser().resolve()
|
|
162
|
+
candidate = Path(user_path).expanduser()
|
|
163
|
+
# Reject UNC paths (//server/share or \\server\share) when a root
|
|
164
|
+
# is set — these always escape a local root.
|
|
165
|
+
if user_path.startswith(("//", r"\\")):
|
|
166
|
+
raise ErrInfo(
|
|
167
|
+
type="error",
|
|
168
|
+
other_msg=f"Path '{user_path}' is outside the allowed root '{root_path}'.",
|
|
169
|
+
)
|
|
170
|
+
if not candidate.is_absolute():
|
|
171
|
+
candidate = root_path / candidate
|
|
172
|
+
resolved = candidate.resolve()
|
|
173
|
+
try:
|
|
174
|
+
resolved.relative_to(root_path)
|
|
175
|
+
except ValueError:
|
|
176
|
+
raise ErrInfo(
|
|
177
|
+
type="error",
|
|
178
|
+
other_msg=f"Path '{user_path}' resolves to '{resolved}', which is outside the allowed root '{root_path}'.",
|
|
179
|
+
) from None
|
|
180
|
+
return str(resolved)
|
|
181
|
+
|
|
182
|
+
|
|
87
183
|
class FileWriter(multiprocessing.Process):
|
|
88
184
|
# An object of this class is intended to be used as a subprocess.
|
|
89
185
|
# All files that are to be written to are kept open until explicitly closed or the object is destroyed.
|
|
@@ -449,6 +545,22 @@ class EncodedFile:
|
|
|
449
545
|
def close(self) -> None:
|
|
450
546
|
if self.fo is not None:
|
|
451
547
|
self.fo.close()
|
|
548
|
+
self.fo = None
|
|
549
|
+
|
|
550
|
+
# B11/F049: context-manager protocol so callers can use ``with
|
|
551
|
+
# EncodedFile(...) as fh:`` and have the file closed automatically.
|
|
552
|
+
# The previous code required every site to remember an explicit
|
|
553
|
+
# close in a try/finally, which several call paths didn't do.
|
|
554
|
+
def __enter__(self) -> io.TextIOWrapper:
|
|
555
|
+
# Default to read mode when used directly as a context manager.
|
|
556
|
+
# Callers that need a different mode should call ``open(mode)``
|
|
557
|
+
# before entering the ``with`` block.
|
|
558
|
+
if self.fo is None:
|
|
559
|
+
self.open("r")
|
|
560
|
+
return self.fo
|
|
561
|
+
|
|
562
|
+
def __exit__(self, exc_type, exc_val, exc_tb) -> None:
|
|
563
|
+
self.close()
|
|
452
564
|
|
|
453
565
|
|
|
454
566
|
class Logger:
|
|
@@ -499,6 +611,14 @@ class Logger:
|
|
|
499
611
|
errmsg = f"Can't open log file {self.log_file_name}"
|
|
500
612
|
e = ErrInfo("exception", exception_msg=exception_desc(), other_msg=errmsg)
|
|
501
613
|
exit_now(1, e, errmsg)
|
|
614
|
+
# B12/F017: tighten log file mode to 0o600 on POSIX. The log
|
|
615
|
+
# captures substituted SQL, -a values, env vars, and DSN URLs;
|
|
616
|
+
# the default umask (typically 022) leaves it world-readable.
|
|
617
|
+
if os.name == "posix":
|
|
618
|
+
try:
|
|
619
|
+
os.chmod(self.log_file_name, 0o600)
|
|
620
|
+
except Exception:
|
|
621
|
+
pass # Best-effort hardening; don't break logging if chmod fails.
|
|
502
622
|
if not f_exists:
|
|
503
623
|
self.writelog(
|
|
504
624
|
"# Execsql log.\n# The first value on each line is the record type.\n"
|
execsql/utils/gui.py
CHANGED
|
@@ -248,11 +248,21 @@ def enable_gui() -> None:
|
|
|
248
248
|
|
|
249
249
|
framework = _state.conf.gui_framework if _state.conf else "tkinter"
|
|
250
250
|
|
|
251
|
+
# --- Headless POSIX guard ----------------------------------------------
|
|
252
|
+
# B20/F041: on POSIX without DISPLAY or WAYLAND_DISPLAY, Tkinter's
|
|
253
|
+
# ``tk.Tk()`` constructor raises a cryptic _tkinter.TclError. Skip
|
|
254
|
+
# straight to a non-GUI backend on headless systems so the fallback
|
|
255
|
+
# path is taken cleanly rather than after a confusing TclError that
|
|
256
|
+
# the broad ``except Exception`` would swallow.
|
|
257
|
+
import os as _os
|
|
258
|
+
|
|
259
|
+
headless_posix = _os.name == "posix" and not _os.environ.get("DISPLAY") and not _os.environ.get("WAYLAND_DISPLAY")
|
|
260
|
+
|
|
251
261
|
# --- Tkinter sync path --------------------------------------------------
|
|
252
262
|
# Tkinter must run on the main thread (required on macOS). Use a sync
|
|
253
263
|
# queue that dispatches dialogs directly in the calling thread instead of
|
|
254
264
|
# routing through a background manager thread.
|
|
255
|
-
if framework == "tkinter":
|
|
265
|
+
if framework == "tkinter" and not headless_posix:
|
|
256
266
|
try:
|
|
257
267
|
from execsql.gui.desktop import TkinterBackend, _TkinterSyncQueue
|
|
258
268
|
|
|
@@ -114,7 +114,7 @@ inner join information_schema.key_column_usage as k
|
|
|
114
114
|
and tc.table_name = k.table_name
|
|
115
115
|
and tc.constraint_name = k.constraint_name
|
|
116
116
|
where
|
|
117
|
-
k.table_name = '
|
|
117
|
+
k.table_name = !'!#table!'!
|
|
118
118
|
and tc.constraint_schema = '!!$db_name!!'
|
|
119
119
|
order by k.ordinal_position
|
|
120
120
|
;
|
|
@@ -137,8 +137,8 @@ from information_schema.columns as s
|
|
|
137
137
|
inner join information_schema.columns as b on s.column_name=b.column_name
|
|
138
138
|
left join cmp_primary_key_columns as pk on pk.column_name = s.column_name
|
|
139
139
|
where
|
|
140
|
-
s.table_name = '
|
|
141
|
-
and b.table_name = '
|
|
140
|
+
s.table_name = concat(!'!#stage_pfx!'!, !'!#table!'!)
|
|
141
|
+
and b.table_name = !'!#table!'!
|
|
142
142
|
and pk.column_name is null
|
|
143
143
|
!!~col_sel!!
|
|
144
144
|
order by s.ordinal_position;
|
|
@@ -251,7 +251,7 @@ inner join information_schema.key_column_usage as k
|
|
|
251
251
|
and tc.table_name = k.table_name
|
|
252
252
|
and tc.constraint_name = k.constraint_name
|
|
253
253
|
where
|
|
254
|
-
k.table_name = '
|
|
254
|
+
k.table_name = !'!#table!'!
|
|
255
255
|
and tc.constraint_schema = '!!$db_name!!'
|
|
256
256
|
order by k.ordinal_position
|
|
257
257
|
;
|
|
@@ -274,8 +274,8 @@ from information_schema.columns as s
|
|
|
274
274
|
inner join information_schema.columns as b on s.column_name=b.column_name
|
|
275
275
|
left join cmp_primary_key_columns as pk on pk.column_name = s.column_name
|
|
276
276
|
where
|
|
277
|
-
s.table_name = '
|
|
278
|
-
and b.table_name = '
|
|
277
|
+
s.table_name = concat(!'!#stage_pfx!'!, !'!#table!'!)
|
|
278
|
+
and b.table_name = !'!#table!'!
|
|
279
279
|
and pk.column_name is null
|
|
280
280
|
!!~col_sel!!
|
|
281
281
|
order by s.ordinal_position;
|
|
@@ -391,7 +391,7 @@ inner join information_schema.key_column_usage as k
|
|
|
391
391
|
and tc.table_name = k.table_name
|
|
392
392
|
and tc.constraint_name = k.constraint_name
|
|
393
393
|
where
|
|
394
|
-
k.table_name = '
|
|
394
|
+
k.table_name = !'!#table!'!
|
|
395
395
|
and tc.constraint_schema = '!!$db_name!!'
|
|
396
396
|
order by k.ordinal_position
|
|
397
397
|
;
|
|
@@ -415,8 +415,8 @@ from information_schema.columns as s
|
|
|
415
415
|
inner join information_schema.columns as b on s.column_name=b.column_name
|
|
416
416
|
left join cmp_primary_key_columns as pk on pk.column_name = s.column_name
|
|
417
417
|
where
|
|
418
|
-
s.table_name = '
|
|
419
|
-
and b.table_name = '
|
|
418
|
+
s.table_name = concat(!'!#stage_pfx!'!, !'!#table!'!)
|
|
419
|
+
and b.table_name = !'!#table!'!
|
|
420
420
|
and pk.column_name is null
|
|
421
421
|
!!~col_sel!!
|
|
422
422
|
order by s.ordinal_position;
|
|
@@ -542,7 +542,7 @@ inner join information_schema.key_column_usage as k
|
|
|
542
542
|
and tc.table_name = k.table_name
|
|
543
543
|
and tc.constraint_name = k.constraint_name
|
|
544
544
|
where
|
|
545
|
-
k.table_name = '
|
|
545
|
+
k.table_name = !'!#table!'!
|
|
546
546
|
and tc.constraint_schema = '!!$db_name!!'
|
|
547
547
|
order by k.ordinal_position
|
|
548
548
|
;
|
|
@@ -565,8 +565,8 @@ from information_schema.columns as s
|
|
|
565
565
|
inner join information_schema.columns as b on s.column_name=b.column_name
|
|
566
566
|
left join cmp_primary_key_columns as pk on pk.column_name = s.column_name
|
|
567
567
|
where
|
|
568
|
-
s.table_name = '
|
|
569
|
-
and b.table_name = '
|
|
568
|
+
s.table_name = concat(!'!#stage_pfx!'!, !'!#table!'!)
|
|
569
|
+
and b.table_name = !'!#table!'!
|
|
570
570
|
and pk.column_name is null
|
|
571
571
|
!!~col_sel!!
|
|
572
572
|
order by s.ordinal_position;
|
|
@@ -146,7 +146,7 @@ with recursive itemtable as (
|
|
|
146
146
|
select
|
|
147
147
|
trim(substring_index(data, ',', 1)) as column_name,
|
|
148
148
|
right(data, length(data) - locate(',', data, 1)) as data
|
|
149
|
-
from (select '
|
|
149
|
+
from (select !'!#column_list!'! as data) as input
|
|
150
150
|
union
|
|
151
151
|
select
|
|
152
152
|
trim(substring_index(data, ',', 1)) as column_name,
|
|
@@ -254,7 +254,7 @@ select !!gls_collist!! from gls_newglossary;
|
|
|
254
254
|
select
|
|
255
255
|
inp.item, inp.definition, inp.url
|
|
256
256
|
from
|
|
257
|
-
(select '
|
|
257
|
+
(select !'!#item!'!::text as item, !'!#definition!'!::text as definition, !'!#def_url!'!::text as url) as inp
|
|
258
258
|
left join gls_glossary as g on g.!!gls_name!! = inp.item
|
|
259
259
|
where
|
|
260
260
|
g.!!gls_name!! is null;
|
|
@@ -264,8 +264,8 @@ select !!gls_collist!! from gls_newglossary;
|
|
|
264
264
|
inp.item, inp.definition
|
|
265
265
|
from
|
|
266
266
|
(select
|
|
267
|
-
cast('
|
|
268
|
-
cast('
|
|
267
|
+
cast(!'!#item!'! as varchar(255)) as item,
|
|
268
|
+
cast(!'!#definition!'! as varchar(255)) as definition
|
|
269
269
|
) as inp
|
|
270
270
|
left join gls_glossary as g on g.!!gls_name!! = inp.item
|
|
271
271
|
where
|
|
@@ -307,7 +307,7 @@ select
|
|
|
307
307
|
from
|
|
308
308
|
information_schema.columns
|
|
309
309
|
where
|
|
310
|
-
table_name = '
|
|
310
|
+
table_name = !'!#table!'!
|
|
311
311
|
;
|
|
312
312
|
-- !x! subdata ~collist gls_collist
|
|
313
313
|
|
|
@@ -182,12 +182,12 @@ from
|
|
|
182
182
|
(
|
|
183
183
|
select
|
|
184
184
|
'base' as schema_type,
|
|
185
|
-
'
|
|
185
|
+
!'!#table!'! as table_name
|
|
186
186
|
union
|
|
187
187
|
select
|
|
188
188
|
|
|
189
189
|
'staging' as schema_type,
|
|
190
|
-
'
|
|
190
|
+
concat(!'!#stage_pfx!'!, !'!#table!'!) as table_name
|
|
191
191
|
) as tt
|
|
192
192
|
left join information_schema.tables as iss on tt.table_name=iss.table_name
|
|
193
193
|
where
|
|
@@ -276,7 +276,7 @@ where
|
|
|
276
276
|
update ups_validate_control as vc, information_schema.tables as st
|
|
277
277
|
set vc.staging_exists = True
|
|
278
278
|
where
|
|
279
|
-
st.table_name= concat('
|
|
279
|
+
st.table_name= concat(!'!#stage_pfx!'!, vc.table_name)
|
|
280
280
|
and st.table_type='BASE TABLE'
|
|
281
281
|
and st.table_schema = '!!$DB_NAME!!'
|
|
282
282
|
;
|
|
@@ -293,7 +293,7 @@ from
|
|
|
293
293
|
from ups_validate_control
|
|
294
294
|
where not base_exists
|
|
295
295
|
union
|
|
296
|
-
select concat('
|
|
296
|
+
select concat(!'!#stage_pfx!'!, table_name) as schema_table
|
|
297
297
|
from ups_validate_control
|
|
298
298
|
where not staging_exists
|
|
299
299
|
) as it
|
|
@@ -427,7 +427,7 @@ with recursive itemtable as (
|
|
|
427
427
|
select
|
|
428
428
|
trim(substring_index(data, ',', 1)) as table_name,
|
|
429
429
|
right(data, length(data) - locate(',', data, 1)) as data
|
|
430
|
-
from (select '
|
|
430
|
+
from (select !'!#table_list!'! as data) as input
|
|
431
431
|
union
|
|
432
432
|
select
|
|
433
433
|
trim(substring_index(data, ',', 1)) as table_name,
|
|
@@ -624,7 +624,7 @@ from
|
|
|
624
624
|
information_schema.columns
|
|
625
625
|
where
|
|
626
626
|
table_schema = '!!$DB_NAME!!'
|
|
627
|
-
and table_name = '
|
|
627
|
+
and table_name = !'!#table!'!
|
|
628
628
|
and is_nullable = 'NO'
|
|
629
629
|
and column_default is null
|
|
630
630
|
!!~omitnull!!
|
|
@@ -783,7 +783,7 @@ inner join information_schema.key_column_usage as k
|
|
|
783
783
|
and tc.table_name = k.table_name
|
|
784
784
|
and tc.constraint_name = k.constraint_name
|
|
785
785
|
where
|
|
786
|
-
k.table_name = '
|
|
786
|
+
k.table_name = !'!#table!'!
|
|
787
787
|
and tc.constraint_schema = '!!$db_name!!'
|
|
788
788
|
order by k.ordinal_position
|
|
789
789
|
;
|
|
@@ -998,7 +998,7 @@ from
|
|
|
998
998
|
ups_foreign_key_columns
|
|
999
999
|
where
|
|
1000
1000
|
table_schema = '!!$DB_NAME!!'
|
|
1001
|
-
and table_name = '
|
|
1001
|
+
and table_name = !'!#table!'!;
|
|
1002
1002
|
|
|
1003
1003
|
-- Create a table of all unique constraint names for
|
|
1004
1004
|
-- this table, with an integer column to be populated with the
|
|
@@ -1257,7 +1257,7 @@ create table ups_cols
|
|
|
1257
1257
|
select column_name
|
|
1258
1258
|
from information_schema.columns
|
|
1259
1259
|
where
|
|
1260
|
-
table_name = '
|
|
1260
|
+
table_name = concat(!'!#stage_pfx!'!, !'!#table!'!)
|
|
1261
1261
|
and table_schema = '!!$DB_NAME!!'
|
|
1262
1262
|
!!~col_excl!!
|
|
1263
1263
|
order by ordinal_position;
|
|
@@ -1280,7 +1280,7 @@ inner join information_schema.key_column_usage as k
|
|
|
1280
1280
|
and tc.table_name = k.table_name
|
|
1281
1281
|
and tc.constraint_name = k.constraint_name
|
|
1282
1282
|
where
|
|
1283
|
-
k.table_name = '
|
|
1283
|
+
k.table_name = !'!#table!'!
|
|
1284
1284
|
and k.table_schema = '!!$DB_NAME!!'
|
|
1285
1285
|
order by k.ordinal_position;
|
|
1286
1286
|
|
|
@@ -2103,7 +2103,7 @@ inner join information_schema.key_column_usage as k
|
|
|
2103
2103
|
and tc.table_name = k.table_name
|
|
2104
2104
|
and tc.constraint_name = k.constraint_name
|
|
2105
2105
|
where
|
|
2106
|
-
k.table_name = '
|
|
2106
|
+
k.table_name = !'!#table!'!
|
|
2107
2107
|
and k.table_schema = '!!$DB_NAME!!'
|
|
2108
2108
|
;
|
|
2109
2109
|
|
|
@@ -2335,7 +2335,7 @@ where
|
|
|
2335
2335
|
from information_schema.columns
|
|
2336
2336
|
where
|
|
2337
2337
|
table_schema = '!!$DB_NAME!!'
|
|
2338
|
-
and table_name = '
|
|
2338
|
+
and table_name = concat(!'!#stage_pfx!'!, !'!#table!'!)
|
|
2339
2339
|
) as stag on pk.newpk_col=stag.column_name
|
|
2340
2340
|
where
|
|
2341
2341
|
stag.column_name is null
|
|
@@ -2731,7 +2731,7 @@ where
|
|
|
2731
2731
|
and cu_uq.table_name = tc_uq.table_name
|
|
2732
2732
|
and cu_uq.ordinal_position = cu.ordinal_position
|
|
2733
2733
|
where
|
|
2734
|
-
rc.table_name = '
|
|
2734
|
+
rc.table_name = !'!#table!'!
|
|
2735
2735
|
;
|
|
2736
2736
|
|
|
2737
2737
|
-- Narrow the list down to ONLY dependencies that affect PK columns
|
|
@@ -118,8 +118,8 @@ inner join information_schema.key_column_usage as k
|
|
|
118
118
|
and tc.table_name = k.table_name
|
|
119
119
|
and tc.constraint_name = k.constraint_name
|
|
120
120
|
where
|
|
121
|
-
k.table_name = '
|
|
122
|
-
and k.table_schema = '
|
|
121
|
+
k.table_name = !'!#table!'!
|
|
122
|
+
and k.table_schema = !'!#base_schema!'!
|
|
123
123
|
order by k.ordinal_position
|
|
124
124
|
;
|
|
125
125
|
|
|
@@ -141,10 +141,10 @@ from information_schema.columns as s
|
|
|
141
141
|
inner join information_schema.columns as b on s.column_name=b.column_name
|
|
142
142
|
left join cmp_primary_key_columns as pk on pk.column_name = s.column_name
|
|
143
143
|
where
|
|
144
|
-
s.table_schema = '
|
|
145
|
-
and s.table_name = '
|
|
146
|
-
and b.table_schema = '
|
|
147
|
-
and b.table_name = '
|
|
144
|
+
s.table_schema = !'!#staging!'!
|
|
145
|
+
and s.table_name = !'!#table!'!
|
|
146
|
+
and b.table_schema = !'!#base_schema!'!
|
|
147
|
+
and b.table_name = !'!#table!'!
|
|
148
148
|
and pk.column_name is null
|
|
149
149
|
!!~col_sel!!
|
|
150
150
|
order by s.ordinal_position;
|
|
@@ -255,8 +255,8 @@ inner join information_schema.key_column_usage as k
|
|
|
255
255
|
and tc.table_name = k.table_name
|
|
256
256
|
and tc.constraint_name = k.constraint_name
|
|
257
257
|
where
|
|
258
|
-
k.table_name = '
|
|
259
|
-
and k.table_schema = '
|
|
258
|
+
k.table_name = !'!#table!'!
|
|
259
|
+
and k.table_schema = !'!#base_schema!'!
|
|
260
260
|
order by k.ordinal_position
|
|
261
261
|
;
|
|
262
262
|
|
|
@@ -278,10 +278,10 @@ from information_schema.columns as s
|
|
|
278
278
|
inner join information_schema.columns as b on s.column_name=b.column_name
|
|
279
279
|
left join cmp_primary_key_columns as pk on pk.column_name = s.column_name
|
|
280
280
|
where
|
|
281
|
-
s.table_schema = '
|
|
282
|
-
and s.table_name = '
|
|
283
|
-
and b.table_schema = '
|
|
284
|
-
and b.table_name = '
|
|
281
|
+
s.table_schema = !'!#staging!'!
|
|
282
|
+
and s.table_name = !'!#table!'!
|
|
283
|
+
and b.table_schema = !'!#base_schema!'!
|
|
284
|
+
and b.table_name = !'!#table!'!
|
|
285
285
|
and pk.column_name is null
|
|
286
286
|
!!~col_sel!!
|
|
287
287
|
order by s.ordinal_position;
|
|
@@ -393,8 +393,8 @@ inner join information_schema.key_column_usage as k
|
|
|
393
393
|
and tc.table_name = k.table_name
|
|
394
394
|
and tc.constraint_name = k.constraint_name
|
|
395
395
|
where
|
|
396
|
-
k.table_name = '
|
|
397
|
-
and k.table_schema = '
|
|
396
|
+
k.table_name = !'!#table!'!
|
|
397
|
+
and k.table_schema = !'!#base_schema!'!
|
|
398
398
|
order by k.ordinal_position
|
|
399
399
|
;
|
|
400
400
|
|
|
@@ -416,10 +416,10 @@ from information_schema.columns as s
|
|
|
416
416
|
inner join information_schema.columns as b on s.column_name=b.column_name
|
|
417
417
|
left join cmp_primary_key_columns as pk on pk.column_name = s.column_name
|
|
418
418
|
where
|
|
419
|
-
s.table_schema = '
|
|
420
|
-
and s.table_name = '
|
|
421
|
-
and b.table_schema = '
|
|
422
|
-
and b.table_name = '
|
|
419
|
+
s.table_schema = !'!#staging!'!
|
|
420
|
+
and s.table_name = !'!#table!'!
|
|
421
|
+
and b.table_schema = !'!#base_schema!'!
|
|
422
|
+
and b.table_name = !'!#table!'!
|
|
423
423
|
!!~col_sel!!
|
|
424
424
|
order by s.ordinal_position;
|
|
425
425
|
|
|
@@ -540,8 +540,8 @@ inner join information_schema.key_column_usage as k
|
|
|
540
540
|
and tc.table_name = k.table_name
|
|
541
541
|
and tc.constraint_name = k.constraint_name
|
|
542
542
|
where
|
|
543
|
-
k.table_name = '
|
|
544
|
-
and k.table_schema = '
|
|
543
|
+
k.table_name = !'!#table!'!
|
|
544
|
+
and k.table_schema = !'!#base_schema!'!
|
|
545
545
|
order by k.ordinal_position
|
|
546
546
|
;
|
|
547
547
|
|
|
@@ -563,10 +563,10 @@ from information_schema.columns as s
|
|
|
563
563
|
inner join information_schema.columns as b on s.column_name=b.column_name
|
|
564
564
|
left join cmp_primary_key_columns as pk on pk.column_name = s.column_name
|
|
565
565
|
where
|
|
566
|
-
s.table_schema = '
|
|
567
|
-
and s.table_name = '
|
|
568
|
-
and b.table_schema = '
|
|
569
|
-
and b.table_name = '
|
|
566
|
+
s.table_schema = !'!#staging!'!
|
|
567
|
+
and s.table_name = !'!#table!'!
|
|
568
|
+
and b.table_schema = !'!#base_schema!'!
|
|
569
|
+
and b.table_name = !'!#table!'!
|
|
570
570
|
!!~col_sel!!
|
|
571
571
|
order by s.ordinal_position;
|
|
572
572
|
|