velocity-python 0.0.35__py3-none-any.whl → 0.0.65__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.
- velocity/__init__.py +1 -1
- velocity/db/core/column.py +25 -105
- velocity/db/core/database.py +79 -23
- velocity/db/core/decorators.py +84 -47
- velocity/db/core/engine.py +190 -189
- velocity/db/core/result.py +94 -49
- velocity/db/core/row.py +81 -46
- velocity/db/core/sequence.py +112 -22
- velocity/db/core/table.py +660 -243
- velocity/db/core/transaction.py +75 -77
- velocity/db/servers/mysql.py +4 -0
- velocity/db/servers/postgres/__init__.py +19 -0
- velocity/db/servers/postgres/operators.py +23 -0
- velocity/db/servers/{postgres.py → postgres/sql.py} +508 -589
- velocity/db/servers/postgres/types.py +109 -0
- velocity/db/servers/tablehelper.py +277 -0
- velocity/misc/conv/iconv.py +277 -91
- velocity/misc/conv/oconv.py +5 -4
- velocity/misc/db.py +2 -2
- velocity/misc/format.py +2 -2
- {velocity_python-0.0.35.dist-info → velocity_python-0.0.65.dist-info}/METADATA +7 -6
- velocity_python-0.0.65.dist-info/RECORD +47 -0
- {velocity_python-0.0.35.dist-info → velocity_python-0.0.65.dist-info}/WHEEL +1 -1
- velocity_python-0.0.35.dist-info/RECORD +0 -43
- /velocity/db/servers/{postgres_reserved.py → postgres/reserved.py} +0 -0
- {velocity_python-0.0.35.dist-info → velocity_python-0.0.65.dist-info/licenses}/LICENSE +0 -0
- {velocity_python-0.0.35.dist-info → velocity_python-0.0.65.dist-info}/top_level.txt +0 -0
|
@@ -1,13 +1,20 @@
|
|
|
1
|
-
import psycopg2
|
|
2
1
|
import re
|
|
3
|
-
import os
|
|
4
2
|
import hashlib
|
|
5
|
-
import
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
from
|
|
9
|
-
|
|
10
|
-
from .
|
|
3
|
+
import sqlparse
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
from velocity.db.core import exceptions
|
|
7
|
+
|
|
8
|
+
from .reserved import reserved_words
|
|
9
|
+
from .types import TYPES
|
|
10
|
+
from .operators import OPERATORS
|
|
11
|
+
from ..tablehelper import TableHelper
|
|
12
|
+
from collections.abc import Mapping, Sequence
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
TableHelper.reserved = reserved_words
|
|
16
|
+
TableHelper.operators = OPERATORS
|
|
17
|
+
|
|
11
18
|
|
|
12
19
|
system_fields = [
|
|
13
20
|
"sys_id",
|
|
@@ -19,162 +26,8 @@ system_fields = [
|
|
|
19
26
|
"description",
|
|
20
27
|
]
|
|
21
28
|
|
|
22
|
-
default_config = {
|
|
23
|
-
"database": os.environ["DBDatabase"],
|
|
24
|
-
"host": os.environ["DBHost"],
|
|
25
|
-
"port": os.environ["DBPort"],
|
|
26
|
-
"user": os.environ["DBUser"],
|
|
27
|
-
"password": os.environ["DBPassword"],
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
def initialize(config=None, **kwargs):
|
|
31
|
-
if not config:
|
|
32
|
-
config = default_config.copy()
|
|
33
|
-
config.update(kwargs)
|
|
34
|
-
return engine.Engine(psycopg2, config, SQL)
|
|
35
|
-
|
|
36
|
-
def make_where(where, sql, vals, is_join=False):
|
|
37
|
-
if not where:
|
|
38
|
-
return
|
|
39
|
-
sql.append("WHERE")
|
|
40
|
-
if isinstance(where, str):
|
|
41
|
-
sql.append(where)
|
|
42
|
-
return
|
|
43
|
-
if isinstance(where, dict):
|
|
44
|
-
where = list(where.items())
|
|
45
|
-
if not isinstance(where, list):
|
|
46
|
-
raise Exception("Parameter `where` is not a valid datatype.")
|
|
47
|
-
alias = "A"
|
|
48
|
-
if is_join and isinstance(is_join, str):
|
|
49
|
-
alias = is_join
|
|
50
|
-
connect = ""
|
|
51
|
-
for key, val in where:
|
|
52
|
-
if connect:
|
|
53
|
-
sql.append(connect)
|
|
54
|
-
if is_join:
|
|
55
|
-
if "." not in key:
|
|
56
|
-
key = f"{alias}.{quote(key.lower())}"
|
|
57
|
-
if val is None:
|
|
58
|
-
if "!" in key:
|
|
59
|
-
key = key.replace("!", "")
|
|
60
|
-
sql.append(f"{key} is not NULL")
|
|
61
|
-
else:
|
|
62
|
-
sql.append(f"{key} is NULL")
|
|
63
|
-
elif isinstance(val, (list, tuple)) and "><" not in key:
|
|
64
|
-
if "!" in key:
|
|
65
|
-
key = key.replace("!", "")
|
|
66
|
-
sql.append(f"{key} not in %s")
|
|
67
|
-
vals.append(tuple(val))
|
|
68
|
-
else:
|
|
69
|
-
sql.append(f"{key} in %s")
|
|
70
|
-
vals.append(tuple(val))
|
|
71
|
-
elif isinstance(val, Query):
|
|
72
|
-
sql.append(f"{key} in ({val})")
|
|
73
|
-
else:
|
|
74
|
-
case = None
|
|
75
|
-
if "<>" in key:
|
|
76
|
-
key = key.replace("<>", "")
|
|
77
|
-
op = "<>"
|
|
78
|
-
elif "!=" in key:
|
|
79
|
-
key = key.replace("!=", "")
|
|
80
|
-
op = "<>"
|
|
81
|
-
elif "!><" in key:
|
|
82
|
-
key = key.replace("!><", "")
|
|
83
|
-
op = "not between"
|
|
84
|
-
elif "><" in key:
|
|
85
|
-
key = key.replace("><", "")
|
|
86
|
-
op = "between"
|
|
87
|
-
elif "!%" in key:
|
|
88
|
-
key = key.replace("!%", "")
|
|
89
|
-
op = "not like"
|
|
90
|
-
elif "%%" in key:
|
|
91
|
-
key = key.replace("%%", "")
|
|
92
|
-
op = "%"
|
|
93
|
-
elif "%>" in key:
|
|
94
|
-
key = key.replace("%>", "")
|
|
95
|
-
op = "%>"
|
|
96
|
-
elif "<%" in key:
|
|
97
|
-
key = key.replace("<%", "")
|
|
98
|
-
op = "<%"
|
|
99
|
-
elif "==" in key:
|
|
100
|
-
key = key.replace("==", "")
|
|
101
|
-
op = "="
|
|
102
|
-
elif "<=" in key:
|
|
103
|
-
key = key.replace("<=", "")
|
|
104
|
-
op = "<="
|
|
105
|
-
elif ">=" in key:
|
|
106
|
-
key = key.replace(">=", "")
|
|
107
|
-
op = ">="
|
|
108
|
-
elif "<" in key:
|
|
109
|
-
key = key.replace("<", "")
|
|
110
|
-
op = "<"
|
|
111
|
-
elif ">" in key:
|
|
112
|
-
key = key.replace(">", "")
|
|
113
|
-
op = ">"
|
|
114
|
-
elif "%" in key:
|
|
115
|
-
key = key.replace("%", "")
|
|
116
|
-
op = "ilike"
|
|
117
|
-
elif "!~*" in key:
|
|
118
|
-
key = key.replace("!~*", "")
|
|
119
|
-
op = "!~*"
|
|
120
|
-
elif "~*" in key:
|
|
121
|
-
key = key.replace("~*", "")
|
|
122
|
-
op = "~*"
|
|
123
|
-
elif "!~" in key:
|
|
124
|
-
key = key.replace("!~", "")
|
|
125
|
-
op = "!~"
|
|
126
|
-
elif "~" in key:
|
|
127
|
-
key = key.replace("~", "")
|
|
128
|
-
op = "~"
|
|
129
|
-
elif "!" in key:
|
|
130
|
-
key = key.replace("!", "")
|
|
131
|
-
op = "<>"
|
|
132
|
-
elif "=" in key:
|
|
133
|
-
key = key.replace("=", "")
|
|
134
|
-
op = "="
|
|
135
|
-
else:
|
|
136
|
-
op = "="
|
|
137
|
-
if "#" in key:
|
|
138
|
-
key = key.replace("#", "")
|
|
139
|
-
op = "="
|
|
140
|
-
case = "lower"
|
|
141
|
-
if isinstance(val, str) and val[:2] == "@@" and val[2:]:
|
|
142
|
-
sql.append(f"{key} {op} {val[2:]}")
|
|
143
|
-
elif op in ["between", "not between"]:
|
|
144
|
-
sql.append(f"{key} {op} %s and %s")
|
|
145
|
-
vals.extend(val)
|
|
146
|
-
else:
|
|
147
|
-
if case:
|
|
148
|
-
sql.append(f"{case}({key}) {op} {case}(%s)")
|
|
149
|
-
else:
|
|
150
|
-
sql.append(f"{key} {op} %s")
|
|
151
|
-
vals.append(val)
|
|
152
|
-
connect = "AND"
|
|
153
|
-
|
|
154
|
-
def quote(data):
|
|
155
|
-
if isinstance(data, list):
|
|
156
|
-
new = []
|
|
157
|
-
for item in data:
|
|
158
|
-
if "@@" in item:
|
|
159
|
-
new.append(item[2:])
|
|
160
|
-
else:
|
|
161
|
-
new.append(quote(item))
|
|
162
|
-
return new
|
|
163
|
-
else:
|
|
164
|
-
parts = data.split(".")
|
|
165
|
-
new = []
|
|
166
|
-
for part in parts:
|
|
167
|
-
if '"' in part:
|
|
168
|
-
new.append(part)
|
|
169
|
-
elif part.upper() in reserved_words:
|
|
170
|
-
new.append(f'"{part}"')
|
|
171
|
-
elif re.findall("[/]", part):
|
|
172
|
-
new.append(f'"{part}"')
|
|
173
|
-
else:
|
|
174
|
-
new.append(part)
|
|
175
|
-
return ".".join(new)
|
|
176
29
|
|
|
177
|
-
class SQL
|
|
30
|
+
class SQL:
|
|
178
31
|
server = "PostGreSQL"
|
|
179
32
|
type_column_identifier = "data_type"
|
|
180
33
|
is_nullable = "is_nullable"
|
|
@@ -188,7 +41,7 @@ class SQL(object):
|
|
|
188
41
|
ColumnMissingErrorCodes = ["42703"]
|
|
189
42
|
ForeignKeyMissingErrorCodes = ["42704"]
|
|
190
43
|
|
|
191
|
-
ConnectionErrorCodes = ["08001", "08S01","57P03", "08006", "53300"]
|
|
44
|
+
ConnectionErrorCodes = ["08001", "08S01", "57P03", "08006", "53300"]
|
|
192
45
|
DuplicateKeyErrorCodes = [] # Handled in regex check.
|
|
193
46
|
RetryTransactionCodes = []
|
|
194
47
|
TruncationErrorCodes = ["22001"]
|
|
@@ -196,6 +49,388 @@ class SQL(object):
|
|
|
196
49
|
DatabaseObjectExistsErrorCodes = ["42710", "42P07", "42P04"]
|
|
197
50
|
DataIntegrityErrorCodes = ["23503"]
|
|
198
51
|
|
|
52
|
+
@classmethod
|
|
53
|
+
def get_error(self, e):
|
|
54
|
+
error_code = getattr(e, "pgcode", None)
|
|
55
|
+
error_mesg = getattr(e, "pgerror", None)
|
|
56
|
+
return error_code, error_mesg
|
|
57
|
+
|
|
58
|
+
types = TYPES
|
|
59
|
+
|
|
60
|
+
@classmethod
|
|
61
|
+
def select(
|
|
62
|
+
cls,
|
|
63
|
+
tx,
|
|
64
|
+
columns=None,
|
|
65
|
+
table=None,
|
|
66
|
+
where=None,
|
|
67
|
+
orderby=None,
|
|
68
|
+
groupby=None,
|
|
69
|
+
having=None,
|
|
70
|
+
start=None,
|
|
71
|
+
qty=None,
|
|
72
|
+
lock=None,
|
|
73
|
+
skip_locked=None,
|
|
74
|
+
):
|
|
75
|
+
|
|
76
|
+
if not table:
|
|
77
|
+
raise ValueError("Table name is required.")
|
|
78
|
+
|
|
79
|
+
sql_parts = {
|
|
80
|
+
"SELECT": [],
|
|
81
|
+
"FROM": [],
|
|
82
|
+
"WHERE": [],
|
|
83
|
+
"GROUP BY": [],
|
|
84
|
+
"HAVING": [],
|
|
85
|
+
"ORDER BY": [],
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
sql = []
|
|
89
|
+
vals = []
|
|
90
|
+
|
|
91
|
+
# Assume these helpers and functions exist externally
|
|
92
|
+
th = TableHelper(tx, table)
|
|
93
|
+
|
|
94
|
+
# Handle columns and DISTINCT before aliasing
|
|
95
|
+
if columns is None:
|
|
96
|
+
# No columns specified - select all
|
|
97
|
+
columns = ["*"]
|
|
98
|
+
elif isinstance(columns, str):
|
|
99
|
+
columns = th.split_columns(columns)
|
|
100
|
+
|
|
101
|
+
if not isinstance(columns, Sequence):
|
|
102
|
+
raise Exception(
|
|
103
|
+
f"variable `columns` must be a sequence, but {type(columns)} was found"
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
columns = [c.strip() for c in columns] # Preserve original case
|
|
107
|
+
distinct = False
|
|
108
|
+
|
|
109
|
+
if any(
|
|
110
|
+
"distinct" in c.lower() for c in columns
|
|
111
|
+
): # Check if "distinct" exists in any entry
|
|
112
|
+
distinct = True
|
|
113
|
+
columns = [
|
|
114
|
+
c.replace("distinct", "", 1).strip() if "distinct" in c.lower() else c
|
|
115
|
+
for c in columns
|
|
116
|
+
]
|
|
117
|
+
|
|
118
|
+
processed_columns = []
|
|
119
|
+
for col in columns:
|
|
120
|
+
processed_columns.append(
|
|
121
|
+
th.resolve_references(
|
|
122
|
+
col, options={"alias_column": True, "alias_table": True}
|
|
123
|
+
)
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
columns = processed_columns
|
|
127
|
+
|
|
128
|
+
# Handle WHERE conditions
|
|
129
|
+
if isinstance(where, Mapping):
|
|
130
|
+
new_where = []
|
|
131
|
+
for key, val in where.items():
|
|
132
|
+
new_where.append(th.make_predicate(key, val))
|
|
133
|
+
where = new_where
|
|
134
|
+
|
|
135
|
+
new_orderby = []
|
|
136
|
+
if isinstance(orderby, str):
|
|
137
|
+
orderby = th.split_columns(orderby)
|
|
138
|
+
# Handle orderby references
|
|
139
|
+
if isinstance(orderby, (Sequence)):
|
|
140
|
+
for column in orderby:
|
|
141
|
+
if " " in column:
|
|
142
|
+
col_name, direction = column.split(" ", 1)
|
|
143
|
+
col_name = th.resolve_references(
|
|
144
|
+
col_name, options={"alias_only": True}
|
|
145
|
+
)
|
|
146
|
+
new_orderby.append(f"{col_name} {direction}")
|
|
147
|
+
else:
|
|
148
|
+
new_orderby.append(
|
|
149
|
+
th.resolve_references(
|
|
150
|
+
column.strip(), options={"alias_only": True}
|
|
151
|
+
)
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
if isinstance(orderby, Mapping):
|
|
155
|
+
for key, val in orderby.items():
|
|
156
|
+
parsed_key = th.resolve_references(key, options={"alias_only": True})
|
|
157
|
+
new_orderby.append(f"{parsed_key} {val}")
|
|
158
|
+
orderby = new_orderby
|
|
159
|
+
|
|
160
|
+
# Handle groupby
|
|
161
|
+
if isinstance(groupby, str):
|
|
162
|
+
groupby = th.split_columns(groupby)
|
|
163
|
+
if isinstance(groupby, (Sequence)):
|
|
164
|
+
new_groupby = []
|
|
165
|
+
for gcol in groupby:
|
|
166
|
+
new_groupby.append(
|
|
167
|
+
th.resolve_references(gcol, options={"alias_only": True})
|
|
168
|
+
)
|
|
169
|
+
groupby = new_groupby
|
|
170
|
+
|
|
171
|
+
# Handle having
|
|
172
|
+
if isinstance(having, Mapping):
|
|
173
|
+
new_having = []
|
|
174
|
+
for key, val in having.items():
|
|
175
|
+
new_having.append(th.make_predicate(key, val))
|
|
176
|
+
having = new_having
|
|
177
|
+
|
|
178
|
+
# SELECT clause
|
|
179
|
+
# columns is a list/tuple of already processed references
|
|
180
|
+
sql_parts["SELECT"].extend(columns)
|
|
181
|
+
alias = th.get_table_alias("current_table")
|
|
182
|
+
if not alias:
|
|
183
|
+
raise ValueError("Main table alias resolution failed.")
|
|
184
|
+
|
|
185
|
+
# FROM clause
|
|
186
|
+
if th.foreign_keys:
|
|
187
|
+
sql_parts["FROM"].append(f"{th.quote(table)} AS {th.quote(alias)}")
|
|
188
|
+
# Handle joins
|
|
189
|
+
done = []
|
|
190
|
+
for key, ref_info in th.foreign_keys.items():
|
|
191
|
+
ref_table = ref_info["ref_table"]
|
|
192
|
+
if ref_table in done:
|
|
193
|
+
continue
|
|
194
|
+
done.append(ref_table)
|
|
195
|
+
if not all(
|
|
196
|
+
k in ref_info
|
|
197
|
+
for k in ("alias", "local_column", "ref_table", "ref_column")
|
|
198
|
+
):
|
|
199
|
+
raise ValueError(f"Invalid table alias info for {ref_table}.")
|
|
200
|
+
sql_parts["FROM"].append(
|
|
201
|
+
f"LEFT JOIN {th.quote(ref_table)} AS {th.quote(ref_info['alias'])} "
|
|
202
|
+
f"ON {th.quote(alias)}.{th.quote(ref_info['local_column'])} = {th.quote(ref_info['alias'])}.{th.quote(ref_info['ref_column'])}"
|
|
203
|
+
)
|
|
204
|
+
else:
|
|
205
|
+
sql_parts["FROM"].append(th.quote(table))
|
|
206
|
+
|
|
207
|
+
# WHERE
|
|
208
|
+
if where:
|
|
209
|
+
if isinstance(where, str):
|
|
210
|
+
sql_parts["WHERE"].append(where)
|
|
211
|
+
else:
|
|
212
|
+
for pred, val in where:
|
|
213
|
+
sql_parts["WHERE"].append(pred)
|
|
214
|
+
if val is None:
|
|
215
|
+
pass
|
|
216
|
+
elif isinstance(val, tuple):
|
|
217
|
+
vals.extend(val)
|
|
218
|
+
else:
|
|
219
|
+
vals.append(val)
|
|
220
|
+
|
|
221
|
+
# GROUP BY
|
|
222
|
+
if groupby:
|
|
223
|
+
sql_parts["GROUP BY"].append(",".join(groupby))
|
|
224
|
+
|
|
225
|
+
# HAVING
|
|
226
|
+
if having:
|
|
227
|
+
if isinstance(having, str):
|
|
228
|
+
sql_parts["HAVING"].append(having)
|
|
229
|
+
else:
|
|
230
|
+
for pred, val in having:
|
|
231
|
+
sql_parts["HAVING"].append(pred)
|
|
232
|
+
if val is None:
|
|
233
|
+
pass
|
|
234
|
+
elif isinstance(val, tuple):
|
|
235
|
+
vals.extend(val)
|
|
236
|
+
else:
|
|
237
|
+
vals.append(val)
|
|
238
|
+
|
|
239
|
+
# ORDER BY
|
|
240
|
+
if orderby:
|
|
241
|
+
sql_parts["ORDER BY"].append(",".join(orderby))
|
|
242
|
+
|
|
243
|
+
# Construct final SQL
|
|
244
|
+
if sql_parts["SELECT"]:
|
|
245
|
+
sql.append("SELECT")
|
|
246
|
+
if distinct:
|
|
247
|
+
sql.append("DISTINCT")
|
|
248
|
+
sql.append(", ".join(sql_parts["SELECT"]))
|
|
249
|
+
|
|
250
|
+
if sql_parts["FROM"]:
|
|
251
|
+
sql.append("FROM")
|
|
252
|
+
sql.append(" ".join(sql_parts["FROM"]))
|
|
253
|
+
|
|
254
|
+
if sql_parts["WHERE"]:
|
|
255
|
+
sql.append("WHERE " + " AND ".join(sql_parts["WHERE"]))
|
|
256
|
+
|
|
257
|
+
if sql_parts["GROUP BY"]:
|
|
258
|
+
sql.append("GROUP BY " + " ".join(sql_parts["GROUP BY"]))
|
|
259
|
+
|
|
260
|
+
if sql_parts["HAVING"]:
|
|
261
|
+
sql.append("HAVING " + " AND ".join(sql_parts["HAVING"]))
|
|
262
|
+
|
|
263
|
+
if sql_parts["ORDER BY"]:
|
|
264
|
+
sql.append("ORDER BY " + " ".join(sql_parts["ORDER BY"]))
|
|
265
|
+
|
|
266
|
+
# OFFSET/FETCH
|
|
267
|
+
if start is not None:
|
|
268
|
+
if not isinstance(start, int):
|
|
269
|
+
raise ValueError("Start (OFFSET) must be an integer.")
|
|
270
|
+
sql.append(f"OFFSET {start} ROWS")
|
|
271
|
+
|
|
272
|
+
if qty is not None:
|
|
273
|
+
if not isinstance(qty, int):
|
|
274
|
+
raise ValueError("Qty (FETCH) must be an integer.")
|
|
275
|
+
sql.append(f"FETCH NEXT {qty} ROWS ONLY")
|
|
276
|
+
|
|
277
|
+
# FOR UPDATE and SKIP LOCKED
|
|
278
|
+
if lock or skip_locked:
|
|
279
|
+
sql.append("FOR UPDATE")
|
|
280
|
+
if skip_locked:
|
|
281
|
+
sql.append("SKIP LOCKED")
|
|
282
|
+
|
|
283
|
+
sql = sqlparse.format(" ".join(sql), reindent=True, keyword_case="upper")
|
|
284
|
+
return sql, tuple(vals)
|
|
285
|
+
|
|
286
|
+
@classmethod
|
|
287
|
+
def update(cls, tx, table, data, where=None, pk=None, excluded=False):
|
|
288
|
+
if not table:
|
|
289
|
+
raise ValueError("Table name is required.")
|
|
290
|
+
if not pk and not where:
|
|
291
|
+
raise ValueError("Where clause (where) or primary key (pk) is required.")
|
|
292
|
+
if not isinstance(data, Mapping) or not data:
|
|
293
|
+
raise ValueError("data must be a non-empty mapping of column-value pairs.")
|
|
294
|
+
|
|
295
|
+
th = TableHelper(tx, table)
|
|
296
|
+
|
|
297
|
+
set_clauses = []
|
|
298
|
+
vals = []
|
|
299
|
+
|
|
300
|
+
if pk:
|
|
301
|
+
if where:
|
|
302
|
+
where.update(pk)
|
|
303
|
+
else:
|
|
304
|
+
where = pk
|
|
305
|
+
|
|
306
|
+
# Handle data columns (SET clause)
|
|
307
|
+
for col, val in data.items():
|
|
308
|
+
col = th.resolve_references(
|
|
309
|
+
col, options={"alias_column": False, "alias_table": False}
|
|
310
|
+
)
|
|
311
|
+
|
|
312
|
+
# Normal column
|
|
313
|
+
if excluded:
|
|
314
|
+
set_clauses.append(f"{col} = EXCLUDED.{col}")
|
|
315
|
+
else:
|
|
316
|
+
set_clauses.append(f"{col} = %s")
|
|
317
|
+
vals.append(val)
|
|
318
|
+
|
|
319
|
+
# Extract the final where conditions and values
|
|
320
|
+
where_clauses = []
|
|
321
|
+
if not excluded:
|
|
322
|
+
# First handle user-provided WHERE conditions
|
|
323
|
+
if isinstance(where, Mapping):
|
|
324
|
+
for key, val in where.items():
|
|
325
|
+
col, value = th.make_predicate(key, val)
|
|
326
|
+
where_clauses.append(col)
|
|
327
|
+
if isinstance(value, tuple):
|
|
328
|
+
vals.extend(value)
|
|
329
|
+
else:
|
|
330
|
+
vals.append(value)
|
|
331
|
+
|
|
332
|
+
# Final assembly of SQL
|
|
333
|
+
sql = []
|
|
334
|
+
sql.append("UPDATE")
|
|
335
|
+
if not excluded:
|
|
336
|
+
if th.foreign_keys:
|
|
337
|
+
sql.append(
|
|
338
|
+
f"{th.quote(table)} AS {th.quote(th.get_table_alias('current_table'))}"
|
|
339
|
+
)
|
|
340
|
+
else:
|
|
341
|
+
sql.append(TableHelper.quote(table))
|
|
342
|
+
sql.append("SET " + ", ".join(set_clauses))
|
|
343
|
+
if not excluded:
|
|
344
|
+
if th.foreign_keys:
|
|
345
|
+
for key, ref_info in th.foreign_keys.items():
|
|
346
|
+
ref_table = ref_info["ref_table"]
|
|
347
|
+
sql.append(
|
|
348
|
+
f"LEFT JOIN {th.quote(ref_table)} AS {th.quote(ref_info['alias'])} "
|
|
349
|
+
)
|
|
350
|
+
where_clauses.append(
|
|
351
|
+
f"{th.quote(th.get_table_alias('current_table'))}.{th.quote(ref_info['local_column'])} = {th.quote(ref_info['alias'])}.{th.quote(ref_info['ref_column'])}"
|
|
352
|
+
)
|
|
353
|
+
|
|
354
|
+
if not excluded:
|
|
355
|
+
if where_clauses:
|
|
356
|
+
sql.append("WHERE " + " AND ".join(where_clauses))
|
|
357
|
+
else:
|
|
358
|
+
# Without a WHERE, this updates all rows.
|
|
359
|
+
# If this is not desired, raise an error.
|
|
360
|
+
if not excluded:
|
|
361
|
+
raise ValueError(
|
|
362
|
+
"No WHERE clause could be constructed. Update would affect all rows."
|
|
363
|
+
)
|
|
364
|
+
|
|
365
|
+
# Final assembled query
|
|
366
|
+
sql = sqlparse.format(" ".join(sql), reindent=True, keyword_case="upper")
|
|
367
|
+
return sql, tuple(vals)
|
|
368
|
+
|
|
369
|
+
@classmethod
|
|
370
|
+
def insert(cls, table, data):
|
|
371
|
+
keys = []
|
|
372
|
+
vals = []
|
|
373
|
+
args = []
|
|
374
|
+
for key, val in data.items():
|
|
375
|
+
keys.append(TableHelper.quote(key.lower()))
|
|
376
|
+
if isinstance(val, str) and len(val) > 2 and val[:2] == "@@" and val[2:]:
|
|
377
|
+
vals.append(val[2:])
|
|
378
|
+
else:
|
|
379
|
+
vals.append("%s")
|
|
380
|
+
args.append(val)
|
|
381
|
+
|
|
382
|
+
sql = ["INSERT INTO"]
|
|
383
|
+
sql.append(TableHelper.quote(table))
|
|
384
|
+
sql.append("(")
|
|
385
|
+
sql.append(",".join(keys))
|
|
386
|
+
sql.append(")")
|
|
387
|
+
sql.append("VALUES")
|
|
388
|
+
sql.append("(")
|
|
389
|
+
sql.append(",".join(vals))
|
|
390
|
+
sql.append(")")
|
|
391
|
+
sql = sqlparse.format(" ".join(sql), reindent=True, keyword_case="upper")
|
|
392
|
+
return sql, tuple(args)
|
|
393
|
+
|
|
394
|
+
@classmethod
|
|
395
|
+
def merge(cls, tx, table, data, pk, on_conflict_do_nothing, on_conflict_update):
|
|
396
|
+
if pk is None:
|
|
397
|
+
pkeys = tx.table(table).primary_keys()
|
|
398
|
+
if not pkeys:
|
|
399
|
+
raise ValueError("Primary key required for merge.")
|
|
400
|
+
# If there are multiple primary keys, we need to use all of them
|
|
401
|
+
if len(pkeys) > 1:
|
|
402
|
+
pk = {pk: data[pk] for pk in pkeys}
|
|
403
|
+
else:
|
|
404
|
+
pk = {pkeys[0]: data[pkeys[0]]}
|
|
405
|
+
# remove primary key from data
|
|
406
|
+
data = {k: v for k, v in data.items() if k not in pk}
|
|
407
|
+
|
|
408
|
+
full_data = {}
|
|
409
|
+
full_data.update(data)
|
|
410
|
+
full_data.update(pk)
|
|
411
|
+
|
|
412
|
+
sql, vals = cls.insert(table, full_data)
|
|
413
|
+
sql = [sql]
|
|
414
|
+
vals = list(vals)
|
|
415
|
+
if on_conflict_do_nothing != on_conflict_update:
|
|
416
|
+
sql.append("ON CONFLICT")
|
|
417
|
+
sql.append("(")
|
|
418
|
+
sql.append(",".join(pk.keys()))
|
|
419
|
+
sql.append(")")
|
|
420
|
+
sql.append("DO")
|
|
421
|
+
if on_conflict_do_nothing:
|
|
422
|
+
sql.append("NOTHING")
|
|
423
|
+
elif on_conflict_update:
|
|
424
|
+
sql2, vals2 = cls.update(tx, table, data, pk, excluded=True)
|
|
425
|
+
sql.append(sql2)
|
|
426
|
+
vals.extend(vals2)
|
|
427
|
+
else:
|
|
428
|
+
raise Exception(
|
|
429
|
+
"Update on conflict must have one and only one option to complete on conflict."
|
|
430
|
+
)
|
|
431
|
+
sql = sqlparse.format(" ".join(sql), reindent=True, keyword_case="upper")
|
|
432
|
+
return sql, tuple(vals)
|
|
433
|
+
|
|
199
434
|
@classmethod
|
|
200
435
|
def version(cls):
|
|
201
436
|
return "select version()", tuple()
|
|
@@ -250,143 +485,6 @@ class SQL(object):
|
|
|
250
485
|
tuple(),
|
|
251
486
|
)
|
|
252
487
|
|
|
253
|
-
@classmethod
|
|
254
|
-
def __has_pointer(cls, columns):
|
|
255
|
-
if isinstance(columns, str):
|
|
256
|
-
columns = columns.split(",")
|
|
257
|
-
if isinstance(columns, list):
|
|
258
|
-
for column in columns:
|
|
259
|
-
if "@@" in column:
|
|
260
|
-
continue
|
|
261
|
-
if ">" in column:
|
|
262
|
-
return True
|
|
263
|
-
return False
|
|
264
|
-
|
|
265
|
-
@classmethod
|
|
266
|
-
def select(
|
|
267
|
-
cls,
|
|
268
|
-
columns=None,
|
|
269
|
-
table=None,
|
|
270
|
-
where=None,
|
|
271
|
-
orderby=None,
|
|
272
|
-
groupby=None,
|
|
273
|
-
having=None,
|
|
274
|
-
start=None,
|
|
275
|
-
qty=None,
|
|
276
|
-
tbl=None,
|
|
277
|
-
lock=None,
|
|
278
|
-
skip_locked=None,
|
|
279
|
-
):
|
|
280
|
-
if not table:
|
|
281
|
-
raise Exception("Table name required")
|
|
282
|
-
is_join = False
|
|
283
|
-
|
|
284
|
-
if isinstance(columns, str) and "distinct" in columns.lower():
|
|
285
|
-
sql = [
|
|
286
|
-
"SELECT",
|
|
287
|
-
columns,
|
|
288
|
-
"FROM",
|
|
289
|
-
quote(table),
|
|
290
|
-
]
|
|
291
|
-
elif cls.__has_pointer(columns):
|
|
292
|
-
is_join = True
|
|
293
|
-
if isinstance(columns, str):
|
|
294
|
-
columns = columns.split(",")
|
|
295
|
-
letter = 65
|
|
296
|
-
tables = {table: chr(letter)}
|
|
297
|
-
letter += 1
|
|
298
|
-
__select = []
|
|
299
|
-
__from = [f"{quote(table)} AS {tables.get(table)}"]
|
|
300
|
-
__left_join = []
|
|
301
|
-
|
|
302
|
-
for column in columns:
|
|
303
|
-
if "@@" in column:
|
|
304
|
-
__select.append(column[2:])
|
|
305
|
-
elif ">" in column:
|
|
306
|
-
parts = column.split(">")
|
|
307
|
-
foreign = tbl.foreign_key_info(parts[0])
|
|
308
|
-
if not foreign:
|
|
309
|
-
raise exceptions.DbApplicationError("Foreign key not defined")
|
|
310
|
-
ref_table = foreign["referenced_table_name"]
|
|
311
|
-
ref_schema = foreign["referenced_table_schema"]
|
|
312
|
-
ref_column = foreign["referenced_column_name"]
|
|
313
|
-
lookup = f"{ref_table}:{parts[0]}"
|
|
314
|
-
if lookup in tables:
|
|
315
|
-
__select.append(
|
|
316
|
-
f'{tables.get(lookup)}."{parts[1]}" as "{'_'.join(parts)}"'
|
|
317
|
-
)
|
|
318
|
-
else:
|
|
319
|
-
tables[lookup] = chr(letter)
|
|
320
|
-
letter += 1
|
|
321
|
-
__select.append(
|
|
322
|
-
f'{tables.get(lookup)}."{parts[1]}" as "{'_'.join(parts)}"'
|
|
323
|
-
)
|
|
324
|
-
__left_join.append(
|
|
325
|
-
f'LEFT OUTER JOIN "{ref_schema}"."{ref_table}" AS {tables.get(lookup)}'
|
|
326
|
-
)
|
|
327
|
-
__left_join.append(
|
|
328
|
-
f'ON {tables.get(table)}."{parts[0]}" = {tables.get(lookup)}."{ref_column}"'
|
|
329
|
-
)
|
|
330
|
-
if orderby and column in orderby:
|
|
331
|
-
orderby = orderby.replace(
|
|
332
|
-
column, f"{tables.get(lookup)}.{parts[1]}"
|
|
333
|
-
)
|
|
334
|
-
|
|
335
|
-
else:
|
|
336
|
-
if "(" in column:
|
|
337
|
-
__select.append(column)
|
|
338
|
-
else:
|
|
339
|
-
__select.append(f"{tables.get(table)}.{column}")
|
|
340
|
-
sql = ["SELECT"]
|
|
341
|
-
sql.append(",".join(__select))
|
|
342
|
-
sql.append("FROM")
|
|
343
|
-
sql.extend(__from)
|
|
344
|
-
sql.extend(__left_join)
|
|
345
|
-
else:
|
|
346
|
-
if columns:
|
|
347
|
-
if isinstance(columns, str):
|
|
348
|
-
columns = columns.split(",")
|
|
349
|
-
if isinstance(columns, list):
|
|
350
|
-
columns = quote(columns)
|
|
351
|
-
columns = ",".join(columns)
|
|
352
|
-
else:
|
|
353
|
-
columns = "*"
|
|
354
|
-
sql = [
|
|
355
|
-
"SELECT",
|
|
356
|
-
columns,
|
|
357
|
-
"FROM",
|
|
358
|
-
quote(table),
|
|
359
|
-
]
|
|
360
|
-
vals = []
|
|
361
|
-
make_where(where, sql, vals, is_join)
|
|
362
|
-
if groupby:
|
|
363
|
-
sql.append("GROUP BY")
|
|
364
|
-
if isinstance(groupby, (list, tuple)):
|
|
365
|
-
groupby = ",".join(groupby)
|
|
366
|
-
sql.append(groupby)
|
|
367
|
-
if having:
|
|
368
|
-
sql.append("HAVING")
|
|
369
|
-
if isinstance(having, (list, tuple)):
|
|
370
|
-
having = ",".join(having)
|
|
371
|
-
sql.append(having)
|
|
372
|
-
if orderby:
|
|
373
|
-
sql.append("ORDER BY")
|
|
374
|
-
if isinstance(orderby, (list, tuple)):
|
|
375
|
-
orderby = ",".join(orderby)
|
|
376
|
-
sql.append(orderby)
|
|
377
|
-
if start and qty:
|
|
378
|
-
sql.append(f"OFFSET {start} ROWS FETCH NEXT {qty} ROWS ONLY")
|
|
379
|
-
elif start:
|
|
380
|
-
sql.append(f"OFFSET {start} ROWS")
|
|
381
|
-
elif qty:
|
|
382
|
-
sql.append(f"FETCH NEXT {qty} ROWS ONLY")
|
|
383
|
-
if lock or skip_locked:
|
|
384
|
-
sql.append("FOR UPDATE")
|
|
385
|
-
if skip_locked:
|
|
386
|
-
sql.append("SKIP LOCKED")
|
|
387
|
-
sql = " ".join(sql)
|
|
388
|
-
return sql, tuple(vals)
|
|
389
|
-
|
|
390
488
|
@classmethod
|
|
391
489
|
def create_database(cls, name):
|
|
392
490
|
return f"create database {name}", tuple()
|
|
@@ -469,24 +567,26 @@ class SQL(object):
|
|
|
469
567
|
if key in system_fields:
|
|
470
568
|
continue
|
|
471
569
|
sql.append(
|
|
472
|
-
f"ALTER TABLE {quote(fqtn)} ADD COLUMN {quote(key)} {
|
|
570
|
+
f"ALTER TABLE {TableHelper.quote(fqtn)} ADD COLUMN {TableHelper.quote(key)} {TYPES.get_type(val)};"
|
|
473
571
|
)
|
|
474
|
-
|
|
572
|
+
|
|
573
|
+
sql = sqlparse.format(" ".join(sql), reindent=True, keyword_case="upper")
|
|
574
|
+
return sql, tuple()
|
|
475
575
|
|
|
476
576
|
@classmethod
|
|
477
577
|
def drop_table(cls, name):
|
|
478
|
-
return f"drop table if exists {quote(name)} cascade;", tuple()
|
|
578
|
+
return f"drop table if exists {TableHelper.quote(name)} cascade;", tuple()
|
|
479
579
|
|
|
480
580
|
@classmethod
|
|
481
581
|
def drop_column(cls, table, name, cascade=True):
|
|
482
582
|
if cascade:
|
|
483
583
|
return (
|
|
484
|
-
f"ALTER TABLE {quote(table)} DROP COLUMN {quote(name)} CASCADE",
|
|
584
|
+
f"ALTER TABLE {TableHelper.quote(table)} DROP COLUMN {TableHelper.quote(name)} CASCADE",
|
|
485
585
|
tuple(),
|
|
486
586
|
)
|
|
487
587
|
else:
|
|
488
588
|
return (
|
|
489
|
-
f"ALTER TABLE {quote(table)} DROP COLUMN {quote(name)}",
|
|
589
|
+
f"ALTER TABLE {TableHelper.quote(table)} DROP COLUMN {TableHelper.quote(name)}",
|
|
490
590
|
tuple(),
|
|
491
591
|
)
|
|
492
592
|
|
|
@@ -616,8 +716,17 @@ class SQL(object):
|
|
|
616
716
|
where["LOWER(KCU1.TABLE_NAME)"] = table.lower()
|
|
617
717
|
if column:
|
|
618
718
|
where["LOWER(KCU1.COLUMN_NAME)"] = column.lower()
|
|
619
|
-
|
|
620
|
-
|
|
719
|
+
sql.append("WHERE")
|
|
720
|
+
connect = ""
|
|
721
|
+
for key, val in where.items():
|
|
722
|
+
if connect:
|
|
723
|
+
sql.append(connect)
|
|
724
|
+
sql.append(f"{key} = %s")
|
|
725
|
+
vals.append(val)
|
|
726
|
+
connect = "AND"
|
|
727
|
+
|
|
728
|
+
sql = sqlparse.format(" ".join(sql), reindent=True, keyword_case="upper")
|
|
729
|
+
return sql, tuple(vals)
|
|
621
730
|
|
|
622
731
|
@classmethod
|
|
623
732
|
def create_foreign_key(
|
|
@@ -637,7 +746,7 @@ class SQL(object):
|
|
|
637
746
|
m.update(" ".join(key_to_columns).encode("utf-8"))
|
|
638
747
|
name = f"FK_{m.hexdigest()}"
|
|
639
748
|
sql = f"ALTER TABLE {table} ADD CONSTRAINT {name} FOREIGN KEY ({','.join(columns)}) REFERENCES {key_to_table} ({','.join(key_to_columns)});"
|
|
640
|
-
|
|
749
|
+
sql = sqlparse.format(sql, reindent=True, keyword_case="upper")
|
|
641
750
|
return sql, tuple()
|
|
642
751
|
|
|
643
752
|
@classmethod
|
|
@@ -669,6 +778,7 @@ class SQL(object):
|
|
|
669
778
|
@classmethod
|
|
670
779
|
def create_index(
|
|
671
780
|
cls,
|
|
781
|
+
tx,
|
|
672
782
|
table=None,
|
|
673
783
|
columns=None,
|
|
674
784
|
unique=False,
|
|
@@ -677,7 +787,6 @@ class SQL(object):
|
|
|
677
787
|
name=None,
|
|
678
788
|
schema=None,
|
|
679
789
|
trigram=None,
|
|
680
|
-
tbl=None,
|
|
681
790
|
lower=None,
|
|
682
791
|
):
|
|
683
792
|
"""
|
|
@@ -688,14 +797,14 @@ class SQL(object):
|
|
|
688
797
|
if "." not in table and schema:
|
|
689
798
|
table = f"{schema}.{table}"
|
|
690
799
|
if isinstance(columns, (list, set)):
|
|
691
|
-
columns = ",".join([quote(c.lower()) for c in columns])
|
|
800
|
+
columns = ",".join([TableHelper.quote(c.lower()) for c in columns])
|
|
692
801
|
else:
|
|
693
|
-
columns = quote(columns)
|
|
802
|
+
columns = TableHelper.quote(columns)
|
|
694
803
|
sql = ["CREATE"]
|
|
695
804
|
if unique:
|
|
696
805
|
sql.append("UNIQUE")
|
|
697
806
|
sql.append("INDEX")
|
|
698
|
-
tablename = quote(table)
|
|
807
|
+
tablename = TableHelper.quote(table)
|
|
699
808
|
if not name:
|
|
700
809
|
name = re.sub(
|
|
701
810
|
r"\([^)]*\)",
|
|
@@ -703,54 +812,58 @@ class SQL(object):
|
|
|
703
812
|
columns.replace(" ", "").replace(",", "_").replace('"', ""),
|
|
704
813
|
)
|
|
705
814
|
if trigram:
|
|
706
|
-
sql.append(
|
|
707
|
-
f"IDX__TRGM_{table.replace('.', '_')}_{trigram.upper()}__{name}"
|
|
708
|
-
)
|
|
815
|
+
sql.append(f"IDX__TRGM_{table.replace('.', '_')}_{trigram}__{name}".upper())
|
|
709
816
|
else:
|
|
710
|
-
sql.append(f"IDX__{table.replace('.', '_')}__{name}")
|
|
817
|
+
sql.append(f"IDX__{table.replace('.', '_')}__{name}".upper())
|
|
711
818
|
sql.append("ON")
|
|
712
|
-
sql.append(quote(tablename))
|
|
819
|
+
sql.append(TableHelper.quote(tablename))
|
|
713
820
|
|
|
714
821
|
if trigram:
|
|
715
822
|
sql.append("USING")
|
|
716
823
|
sql.append(trigram)
|
|
717
824
|
sql.append("(")
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
825
|
+
join = ""
|
|
826
|
+
for column_name in columns.split(","):
|
|
827
|
+
column_name = column_name.replace('"', "")
|
|
828
|
+
if join:
|
|
829
|
+
sql.append(join)
|
|
830
|
+
column = tx.table(table).column(column_name)
|
|
831
|
+
print(column)
|
|
832
|
+
if not column.exists():
|
|
833
|
+
raise Exception(
|
|
834
|
+
f"Column {column_name} does not exist in table {table}."
|
|
835
|
+
)
|
|
836
|
+
if column.py_type == str:
|
|
837
|
+
if lower:
|
|
838
|
+
sql.append(f"lower({TableHelper.quote(column_name)})")
|
|
730
839
|
else:
|
|
731
|
-
sql.append(quote(column_name))
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
840
|
+
sql.append(TableHelper.quote(column_name))
|
|
841
|
+
else:
|
|
842
|
+
sql.append(TableHelper.quote(column_name))
|
|
843
|
+
join = ","
|
|
844
|
+
|
|
735
845
|
if trigram:
|
|
736
846
|
sql.append(f"{trigram.lower()}_trgm_ops")
|
|
737
847
|
sql.append(")")
|
|
738
848
|
vals = []
|
|
849
|
+
s, v = TableHelper(tx, table).make_where(where)
|
|
850
|
+
sql.append(s)
|
|
851
|
+
vals.extend(v)
|
|
739
852
|
|
|
740
|
-
|
|
741
|
-
return
|
|
853
|
+
sql = sqlparse.format(" ".join(sql), reindent=True, keyword_case="upper")
|
|
854
|
+
return sql, tuple(vals)
|
|
742
855
|
|
|
743
856
|
@classmethod
|
|
744
857
|
def drop_index(cls, table=None, columns=None, name=None, schema=None, trigram=None):
|
|
745
858
|
if "." not in table and schema:
|
|
746
859
|
table = f"{schema}.{table}"
|
|
747
860
|
if isinstance(columns, (list, set)):
|
|
748
|
-
columns = ",".join([quote(c.lower()) for c in columns])
|
|
861
|
+
columns = ",".join([TableHelper.quote(c.lower()) for c in columns])
|
|
749
862
|
else:
|
|
750
|
-
columns = quote(columns)
|
|
863
|
+
columns = TableHelper.quote(columns)
|
|
751
864
|
sql = ["DROP"]
|
|
752
865
|
sql.append("INDEX IF EXISTS")
|
|
753
|
-
tablename = quote(table)
|
|
866
|
+
tablename = TableHelper.quote(table)
|
|
754
867
|
if not name:
|
|
755
868
|
name = re.sub(
|
|
756
869
|
r"\([^)]*\)",
|
|
@@ -758,209 +871,12 @@ class SQL(object):
|
|
|
758
871
|
columns.replace(" ", "").replace(",", "_").replace('"', ""),
|
|
759
872
|
)
|
|
760
873
|
if trigram:
|
|
761
|
-
sql.append(
|
|
762
|
-
f"IDX__TRGM_{table.replace('.', '_')}_{trigram.upper()}__{name}"
|
|
763
|
-
)
|
|
874
|
+
sql.append(f"IDX__TRGM_{table.replace('.', '_')}_{trigram.upper()}__{name}")
|
|
764
875
|
else:
|
|
765
876
|
sql.append(f"IDX__{table.replace('.', '_')}__{name}")
|
|
766
|
-
return " ".join(sql), tuple()
|
|
767
877
|
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
d = {}
|
|
771
|
-
d.update(data)
|
|
772
|
-
d.update(pk)
|
|
773
|
-
sql, vals = cls.insert(table, d)
|
|
774
|
-
sql = [sql]
|
|
775
|
-
vals = list(vals)
|
|
776
|
-
if on_conflict_do_nothing != on_conflict_update:
|
|
777
|
-
sql.append("ON CONFLICT")
|
|
778
|
-
sql.append("(")
|
|
779
|
-
sql.append(",".join(pk.keys()))
|
|
780
|
-
sql.append(")")
|
|
781
|
-
sql.append("DO")
|
|
782
|
-
if on_conflict_do_nothing:
|
|
783
|
-
sql.append("NOTHING")
|
|
784
|
-
elif on_conflict_update:
|
|
785
|
-
sql2, vals2 = cls.update(table, data, pk, excluded=True)
|
|
786
|
-
sql.append(sql2)
|
|
787
|
-
vals.extend(vals2)
|
|
788
|
-
else:
|
|
789
|
-
raise Exception(
|
|
790
|
-
"Update on conflict must have one and only one option to complete on conflict."
|
|
791
|
-
)
|
|
792
|
-
return " ".join(sql), tuple(vals)
|
|
793
|
-
|
|
794
|
-
@classmethod
|
|
795
|
-
def insert(cls, table, data):
|
|
796
|
-
keys = []
|
|
797
|
-
vals = []
|
|
798
|
-
args = []
|
|
799
|
-
for key, val in data.items():
|
|
800
|
-
keys.append(quote(key.lower()))
|
|
801
|
-
if isinstance(val, str) and len(val) > 2 and val[:2] == "@@" and val[2:]:
|
|
802
|
-
vals.append(val[2:])
|
|
803
|
-
else:
|
|
804
|
-
vals.append("%s")
|
|
805
|
-
args.append(val)
|
|
806
|
-
|
|
807
|
-
sql = ["INSERT INTO"]
|
|
808
|
-
sql.append(quote(table))
|
|
809
|
-
sql.append("(")
|
|
810
|
-
sql.append(",".join(keys))
|
|
811
|
-
sql.append(")")
|
|
812
|
-
sql.append("VALUES")
|
|
813
|
-
sql.append("(")
|
|
814
|
-
sql.append(",".join(vals))
|
|
815
|
-
sql.append(")")
|
|
816
|
-
sql = " ".join(sql)
|
|
817
|
-
return sql, tuple(args)
|
|
818
|
-
|
|
819
|
-
@classmethod
|
|
820
|
-
def update(
|
|
821
|
-
cls,
|
|
822
|
-
table,
|
|
823
|
-
data,
|
|
824
|
-
pk,
|
|
825
|
-
left_join=None,
|
|
826
|
-
inner_join=None,
|
|
827
|
-
outer_join=None,
|
|
828
|
-
excluded=False,
|
|
829
|
-
):
|
|
830
|
-
alias = "A"
|
|
831
|
-
if " " in table:
|
|
832
|
-
alias, table = table.split(" ")
|
|
833
|
-
is_join = bool(left_join or inner_join or outer_join)
|
|
834
|
-
sql = ["UPDATE"]
|
|
835
|
-
if not excluded:
|
|
836
|
-
sql.append(quote(table))
|
|
837
|
-
sql.append("SET")
|
|
838
|
-
vals = []
|
|
839
|
-
connect = ""
|
|
840
|
-
for key, val in data.items():
|
|
841
|
-
if connect:
|
|
842
|
-
sql.append(connect)
|
|
843
|
-
if isinstance(val, str) and val[:2] == "@@" and val[2:]:
|
|
844
|
-
sql.append(f"{key} = {val[2:]}")
|
|
845
|
-
else:
|
|
846
|
-
if excluded:
|
|
847
|
-
sql.append(f"{key} = EXCLUDED.{key}")
|
|
848
|
-
else:
|
|
849
|
-
sql.append(f"{key} = %s")
|
|
850
|
-
vals.append(val)
|
|
851
|
-
connect = ","
|
|
852
|
-
if is_join:
|
|
853
|
-
sql.append("FROM")
|
|
854
|
-
sql.append(table)
|
|
855
|
-
sql.append("AS")
|
|
856
|
-
sql.append(alias)
|
|
857
|
-
if left_join:
|
|
858
|
-
for k, v in left_join.items():
|
|
859
|
-
sql.append("LEFT JOIN")
|
|
860
|
-
sql.append(k)
|
|
861
|
-
sql.append("ON")
|
|
862
|
-
sql.append(v)
|
|
863
|
-
if outer_join:
|
|
864
|
-
for k, v in outer_join.items():
|
|
865
|
-
sql.append("OUTER JOIN")
|
|
866
|
-
sql.append(k)
|
|
867
|
-
sql.append("ON")
|
|
868
|
-
sql.append(v)
|
|
869
|
-
if inner_join:
|
|
870
|
-
for k, v in inner_join.items():
|
|
871
|
-
sql.append("INNER JOIN")
|
|
872
|
-
sql.append(k)
|
|
873
|
-
sql.append("ON")
|
|
874
|
-
sql.append(v)
|
|
875
|
-
if not excluded:
|
|
876
|
-
make_where(pk, sql, vals, is_join)
|
|
877
|
-
return " ".join(sql), tuple(vals)
|
|
878
|
-
|
|
879
|
-
@classmethod
|
|
880
|
-
def get_type(cls, v):
|
|
881
|
-
if isinstance(v, str):
|
|
882
|
-
if v[:2] == "@@":
|
|
883
|
-
return v[2:] or cls.TYPES.TEXT
|
|
884
|
-
elif isinstance(v, str) or v is str:
|
|
885
|
-
return cls.TYPES.TEXT
|
|
886
|
-
elif isinstance(v, bool) or v is bool:
|
|
887
|
-
return cls.TYPES.BOOLEAN
|
|
888
|
-
elif isinstance(v, int) or v is int:
|
|
889
|
-
return cls.TYPES.BIGINT
|
|
890
|
-
elif isinstance(v, float) or v is float:
|
|
891
|
-
return f"{cls.TYPES.NUMERIC}(19, 6)"
|
|
892
|
-
elif isinstance(v, decimal.Decimal) or v is decimal.Decimal:
|
|
893
|
-
return f"{cls.TYPES.NUMERIC}(19, 6)"
|
|
894
|
-
elif isinstance(v, datetime.datetime) or v is datetime.datetime:
|
|
895
|
-
return cls.TYPES.DATETIME
|
|
896
|
-
elif isinstance(v, datetime.date) or v is datetime.date:
|
|
897
|
-
return cls.TYPES.DATE
|
|
898
|
-
elif isinstance(v, datetime.time) or v is datetime.time:
|
|
899
|
-
return cls.TYPES.TIME
|
|
900
|
-
elif isinstance(v, datetime.timedelta) or v is datetime.timedelta:
|
|
901
|
-
return cls.TYPES.INTERVAL
|
|
902
|
-
elif isinstance(v, bytes) or v is bytes:
|
|
903
|
-
return cls.TYPES.BINARY
|
|
904
|
-
return cls.TYPES.TEXT
|
|
905
|
-
|
|
906
|
-
@classmethod
|
|
907
|
-
def get_conv(cls, v):
|
|
908
|
-
if isinstance(v, str):
|
|
909
|
-
if v[:2] == "@@":
|
|
910
|
-
return v[2:] or cls.TYPES.TEXT
|
|
911
|
-
elif isinstance(v, str) or v is str:
|
|
912
|
-
return cls.TYPES.TEXT
|
|
913
|
-
elif isinstance(v, bool) or v is bool:
|
|
914
|
-
return cls.TYPES.BOOLEAN
|
|
915
|
-
elif isinstance(v, int) or v is int:
|
|
916
|
-
return cls.TYPES.BIGINT
|
|
917
|
-
elif isinstance(v, float) or v is float:
|
|
918
|
-
return cls.TYPES.NUMERIC
|
|
919
|
-
elif isinstance(v, decimal.Decimal) or v is decimal.Decimal:
|
|
920
|
-
return cls.TYPES.NUMERIC
|
|
921
|
-
elif isinstance(v, datetime.datetime) or v is datetime.datetime:
|
|
922
|
-
return cls.TYPES.DATETIME
|
|
923
|
-
elif isinstance(v, datetime.date) or v is datetime.date:
|
|
924
|
-
return cls.TYPES.DATE
|
|
925
|
-
elif isinstance(v, datetime.time) or v is datetime.time:
|
|
926
|
-
return cls.TYPES.TIME
|
|
927
|
-
elif isinstance(v, datetime.timedelta) or v is datetime.timedelta:
|
|
928
|
-
return cls.TYPES.INTERVAL
|
|
929
|
-
elif isinstance(v, bytes) or v is bytes:
|
|
930
|
-
return cls.TYPES.BINARY
|
|
931
|
-
return cls.TYPES.TEXT
|
|
932
|
-
|
|
933
|
-
@classmethod
|
|
934
|
-
def py_type(cls, v):
|
|
935
|
-
v = str(v).upper()
|
|
936
|
-
if v == cls.TYPES.INTEGER:
|
|
937
|
-
return int
|
|
938
|
-
elif v == cls.TYPES.SMALLINT:
|
|
939
|
-
return int
|
|
940
|
-
elif v == cls.TYPES.BIGINT:
|
|
941
|
-
return int
|
|
942
|
-
elif v == cls.TYPES.NUMERIC:
|
|
943
|
-
return decimal.Decimal
|
|
944
|
-
elif v == cls.TYPES.TEXT:
|
|
945
|
-
return str
|
|
946
|
-
elif v == cls.TYPES.BOOLEAN:
|
|
947
|
-
return bool
|
|
948
|
-
elif v == cls.TYPES.DATE:
|
|
949
|
-
return datetime.date
|
|
950
|
-
elif v == cls.TYPES.TIME:
|
|
951
|
-
return datetime.time
|
|
952
|
-
elif v == cls.TYPES.TIME_TZ:
|
|
953
|
-
return datetime.time
|
|
954
|
-
elif v == cls.TYPES.DATETIME:
|
|
955
|
-
return datetime.datetime
|
|
956
|
-
elif v == cls.TYPES.INTERVAL:
|
|
957
|
-
return datetime.timedelta
|
|
958
|
-
elif v == cls.TYPES.DATETIME_TZ:
|
|
959
|
-
return datetime.datetime
|
|
960
|
-
elif v == cls.TYPES.INTERVAL_TZ:
|
|
961
|
-
return datetime.timedelta
|
|
962
|
-
else:
|
|
963
|
-
raise Exception(f"unmapped type {v}")
|
|
878
|
+
sql = sqlparse.format(" ".join(sql), reindent=True, keyword_case="upper")
|
|
879
|
+
return sql, tuple()
|
|
964
880
|
|
|
965
881
|
@classmethod
|
|
966
882
|
def massage_data(cls, data):
|
|
@@ -985,46 +901,53 @@ class SQL(object):
|
|
|
985
901
|
for key, val in columns.items():
|
|
986
902
|
key = re.sub("<>!=%", "", key.lower())
|
|
987
903
|
sql.append(
|
|
988
|
-
f"ALTER TABLE {quote(table)} ADD {quote(key)} {
|
|
904
|
+
f"ALTER TABLE {TableHelper.quote(table)} ADD {TableHelper.quote(key)} {TYPES.get_type(val)} {null};"
|
|
989
905
|
)
|
|
990
|
-
|
|
906
|
+
sql = sqlparse.format(" ".join(sql), reindent=True, keyword_case="upper")
|
|
907
|
+
return sql, tuple()
|
|
991
908
|
|
|
992
909
|
@classmethod
|
|
993
910
|
def alter_drop(cls, table, columns):
|
|
994
|
-
sql = [f"ALTER TABLE {quote(table)} DROP COLUMN"]
|
|
911
|
+
sql = [f"ALTER TABLE {TableHelper.quote(table)} DROP COLUMN"]
|
|
995
912
|
if isinstance(columns, dict):
|
|
996
913
|
for key, val in columns.items():
|
|
997
914
|
key = re.sub("<>!=%", "", key.lower())
|
|
998
915
|
sql.append(f"{key},")
|
|
999
916
|
if sql[-1][-1] == ",":
|
|
1000
917
|
sql[-1] = sql[-1][:-1]
|
|
1001
|
-
|
|
918
|
+
sql = sqlparse.format(" ".join(sql), reindent=True, keyword_case="upper")
|
|
919
|
+
return sql, tuple()
|
|
1002
920
|
|
|
1003
921
|
@classmethod
|
|
1004
922
|
def alter_column_by_type(cls, table, column, value, nullable=True):
|
|
1005
|
-
sql = [f"ALTER TABLE {quote(table)} ALTER COLUMN"]
|
|
1006
|
-
sql.append(f"{quote(column)} TYPE {
|
|
1007
|
-
sql.append(f"USING {quote(column)}::{
|
|
923
|
+
sql = [f"ALTER TABLE {TableHelper.quote(table)} ALTER COLUMN"]
|
|
924
|
+
sql.append(f"{TableHelper.quote(column)} TYPE {TYPES.get_type(value)}")
|
|
925
|
+
sql.append(f"USING {TableHelper.quote(column)}::{TYPES.get_conv(value)}")
|
|
1008
926
|
if not nullable:
|
|
1009
927
|
sql.append("NOT NULL")
|
|
1010
|
-
|
|
928
|
+
sql = sqlparse.format(" ".join(sql), reindent=True, keyword_case="upper")
|
|
929
|
+
return sql, tuple()
|
|
1011
930
|
|
|
1012
931
|
@classmethod
|
|
1013
932
|
def alter_column_by_sql(cls, table, column, value):
|
|
1014
|
-
sql = [f"ALTER TABLE {quote(table)} ALTER COLUMN"]
|
|
1015
|
-
sql.append(f"{quote(column)} {value}")
|
|
1016
|
-
|
|
933
|
+
sql = [f"ALTER TABLE {TableHelper.quote(table)} ALTER COLUMN"]
|
|
934
|
+
sql.append(f"{TableHelper.quote(column)} {value}")
|
|
935
|
+
sql = sqlparse.format(" ".join(sql), reindent=True, keyword_case="upper")
|
|
936
|
+
return sql, tuple()
|
|
1017
937
|
|
|
1018
938
|
@classmethod
|
|
1019
939
|
def rename_column(cls, table, orig, new):
|
|
1020
940
|
return (
|
|
1021
|
-
f"ALTER TABLE {quote(table)} RENAME COLUMN {quote(orig)} TO {quote(new)};",
|
|
941
|
+
f"ALTER TABLE {TableHelper.quote(table)} RENAME COLUMN {TableHelper.quote(orig)} TO {TableHelper.quote(new)};",
|
|
1022
942
|
tuple(),
|
|
1023
943
|
)
|
|
1024
944
|
|
|
1025
945
|
@classmethod
|
|
1026
946
|
def rename_table(cls, table, new):
|
|
1027
|
-
return
|
|
947
|
+
return (
|
|
948
|
+
f"ALTER TABLE {TableHelper.quote(table)} RENAME TO {TableHelper.quote(new)};",
|
|
949
|
+
tuple(),
|
|
950
|
+
)
|
|
1028
951
|
|
|
1029
952
|
@classmethod
|
|
1030
953
|
def create_savepoint(cls, sp):
|
|
@@ -1039,26 +962,19 @@ class SQL(object):
|
|
|
1039
962
|
return f'ROLLBACK TO SAVEPOINT "{sp}"', tuple()
|
|
1040
963
|
|
|
1041
964
|
@classmethod
|
|
1042
|
-
def
|
|
1043
|
-
return cls.select(
|
|
1044
|
-
columns,
|
|
1045
|
-
table,
|
|
1046
|
-
where,
|
|
1047
|
-
orderby=columns,
|
|
1048
|
-
groupby=columns,
|
|
1049
|
-
having="count(*) > 2",
|
|
1050
|
-
)
|
|
1051
|
-
|
|
1052
|
-
@classmethod
|
|
1053
|
-
def delete(cls, table, where):
|
|
965
|
+
def delete(cls, tx, table, where):
|
|
1054
966
|
sql = [f"DELETE FROM {table}"]
|
|
1055
967
|
vals = []
|
|
1056
|
-
|
|
1057
|
-
|
|
968
|
+
if where:
|
|
969
|
+
s, v = TableHelper(tx, table).make_where(where)
|
|
970
|
+
sql.append(s)
|
|
971
|
+
vals.extend(v)
|
|
972
|
+
sql = sqlparse.format(" ".join(sql), reindent=True, keyword_case="upper")
|
|
973
|
+
return sql, tuple(vals)
|
|
1058
974
|
|
|
1059
975
|
@classmethod
|
|
1060
976
|
def truncate(cls, table):
|
|
1061
|
-
return f"truncate table {quote(table)}", tuple()
|
|
977
|
+
return f"truncate table {TableHelper.quote(table)}", tuple()
|
|
1062
978
|
|
|
1063
979
|
@classmethod
|
|
1064
980
|
def create_view(cls, name, query, temp=False, silent=True):
|
|
@@ -1071,7 +987,8 @@ class SQL(object):
|
|
|
1071
987
|
sql.append(name)
|
|
1072
988
|
sql.append("AS")
|
|
1073
989
|
sql.append(query)
|
|
1074
|
-
|
|
990
|
+
sql = sqlparse.format(" ".join(sql), reindent=True, keyword_case="upper")
|
|
991
|
+
return sql, tuple()
|
|
1075
992
|
|
|
1076
993
|
@classmethod
|
|
1077
994
|
def drop_view(cls, name, silent=True):
|
|
@@ -1079,7 +996,8 @@ class SQL(object):
|
|
|
1079
996
|
if silent:
|
|
1080
997
|
sql.append("IF EXISTS")
|
|
1081
998
|
sql.append(name)
|
|
1082
|
-
|
|
999
|
+
sql = sqlparse.format(" ".join(sql), reindent=True, keyword_case="upper")
|
|
1000
|
+
return sql, tuple()
|
|
1083
1001
|
|
|
1084
1002
|
@classmethod
|
|
1085
1003
|
def alter_trigger(cls, table, state="ENABLE", name="USER"):
|
|
@@ -1093,7 +1011,7 @@ class SQL(object):
|
|
|
1093
1011
|
)
|
|
1094
1012
|
|
|
1095
1013
|
@classmethod
|
|
1096
|
-
def missing(cls, table, list, column="SYS_ID", where=None):
|
|
1014
|
+
def missing(cls, tx, table, list, column="SYS_ID", where=None):
|
|
1097
1015
|
sql = [
|
|
1098
1016
|
f"SELECT * FROM",
|
|
1099
1017
|
f"UNNEST('{{{','.join([str(x) for x in list])}}}'::int[]) id",
|
|
@@ -1101,22 +1019,23 @@ class SQL(object):
|
|
|
1101
1019
|
f"SELECT {column} FROM {table}",
|
|
1102
1020
|
]
|
|
1103
1021
|
vals = []
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1022
|
+
if where:
|
|
1023
|
+
s, v = TableHelper(tx, table).make_where(where)
|
|
1024
|
+
sql.append(s)
|
|
1025
|
+
vals.extend(v)
|
|
1026
|
+
sql = sqlparse.format(" ".join(sql), reindent=True, keyword_case="upper")
|
|
1027
|
+
return sql, tuple(vals)
|
|
1028
|
+
|
|
1029
|
+
@classmethod
|
|
1030
|
+
def indexes(cls, table):
|
|
1031
|
+
"""
|
|
1032
|
+
Returns SQL for retrieving all indexes on a given table with detailed attributes.
|
|
1033
|
+
"""
|
|
1034
|
+
return (
|
|
1035
|
+
"""
|
|
1036
|
+
SELECT indexname, tablename, schemaname, indexdef
|
|
1037
|
+
FROM pg_indexes
|
|
1038
|
+
WHERE tablename = %s
|
|
1039
|
+
""",
|
|
1040
|
+
(table,),
|
|
1041
|
+
)
|