velocity-python 0.0.34__py3-none-any.whl → 0.0.64__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.
Potentially problematic release.
This version of velocity-python might be problematic. Click here for more details.
- 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 +179 -184
- 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 +5 -237
- velocity/db/servers/mysql_reserved.py +237 -0
- velocity/db/servers/postgres/__init__.py +19 -0
- velocity/db/servers/postgres/operators.py +23 -0
- velocity/db/servers/postgres/reserved.py +254 -0
- velocity/db/servers/postgres/sql.py +1041 -0
- velocity/db/servers/postgres/types.py +109 -0
- velocity/db/servers/sqlite.py +1 -210
- velocity/db/servers/sqlite_reserved.py +208 -0
- velocity/db/servers/sqlserver.py +1 -316
- velocity/db/servers/sqlserver_reserved.py +314 -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.34.dist-info → velocity_python-0.0.64.dist-info}/METADATA +6 -6
- velocity_python-0.0.64.dist-info/RECORD +47 -0
- {velocity_python-0.0.34.dist-info → velocity_python-0.0.64.dist-info}/WHEEL +1 -1
- velocity/db/servers/postgres.py +0 -1396
- velocity_python-0.0.34.dist-info/RECORD +0 -39
- {velocity_python-0.0.34.dist-info → velocity_python-0.0.64.dist-info}/LICENSE +0 -0
- {velocity_python-0.0.34.dist-info → velocity_python-0.0.64.dist-info}/top_level.txt +0 -0
velocity/db/servers/postgres.py
DELETED
|
@@ -1,1396 +0,0 @@
|
|
|
1
|
-
import psycopg2
|
|
2
|
-
import re
|
|
3
|
-
import os
|
|
4
|
-
import hashlib
|
|
5
|
-
import decimal
|
|
6
|
-
import datetime
|
|
7
|
-
from ..core import exceptions
|
|
8
|
-
from ..core import engine
|
|
9
|
-
from ..core.table import Query
|
|
10
|
-
|
|
11
|
-
system_fields = [
|
|
12
|
-
"sys_id",
|
|
13
|
-
"sys_created",
|
|
14
|
-
"sys_modified",
|
|
15
|
-
"sys_modified_by",
|
|
16
|
-
"sys_dirty",
|
|
17
|
-
"sys_table",
|
|
18
|
-
"description",
|
|
19
|
-
]
|
|
20
|
-
|
|
21
|
-
default_config = {
|
|
22
|
-
"database": os.environ["DBDatabase"],
|
|
23
|
-
"host": os.environ["DBHost"],
|
|
24
|
-
"port": os.environ["DBPort"],
|
|
25
|
-
"user": os.environ["DBUser"],
|
|
26
|
-
"password": os.environ["DBPassword"],
|
|
27
|
-
}
|
|
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
|
-
|
|
37
|
-
def make_where(where, sql, vals, is_join=False):
|
|
38
|
-
if not where:
|
|
39
|
-
return
|
|
40
|
-
sql.append("WHERE")
|
|
41
|
-
if isinstance(where, str):
|
|
42
|
-
sql.append(where)
|
|
43
|
-
return
|
|
44
|
-
if isinstance(where, dict):
|
|
45
|
-
where = list(where.items())
|
|
46
|
-
if not isinstance(where, list):
|
|
47
|
-
raise Exception("Parameter `where` is not a valid datatype.")
|
|
48
|
-
alias = "A"
|
|
49
|
-
if is_join and isinstance(is_join, str):
|
|
50
|
-
alias = is_join
|
|
51
|
-
connect = ""
|
|
52
|
-
for key, val in where:
|
|
53
|
-
if connect:
|
|
54
|
-
sql.append(connect)
|
|
55
|
-
if is_join:
|
|
56
|
-
if "." not in key:
|
|
57
|
-
key = alias + "." + quote(key.lower())
|
|
58
|
-
if val == None:
|
|
59
|
-
if "!" in key:
|
|
60
|
-
key = key.replace("!", "")
|
|
61
|
-
sql.append("{} is not NULL".format(key))
|
|
62
|
-
else:
|
|
63
|
-
sql.append("{} is NULL".format(key))
|
|
64
|
-
elif isinstance(val, (list, tuple)) and "><" not in key:
|
|
65
|
-
if "!" in key:
|
|
66
|
-
key = key.replace("!", "")
|
|
67
|
-
sql.append("{} not in %s".format(key))
|
|
68
|
-
vals.append(tuple(val))
|
|
69
|
-
else:
|
|
70
|
-
sql.append("{} in %s".format(key))
|
|
71
|
-
vals.append(tuple(val))
|
|
72
|
-
elif isinstance(val, Query):
|
|
73
|
-
sql.append("{} in ({})".format(key, val))
|
|
74
|
-
else:
|
|
75
|
-
case = None
|
|
76
|
-
if "<>" in key:
|
|
77
|
-
key = key.replace("<>", "")
|
|
78
|
-
op = "<>"
|
|
79
|
-
elif "!=" in key:
|
|
80
|
-
key = key.replace("!=", "")
|
|
81
|
-
op = "<>"
|
|
82
|
-
elif "!><" in key:
|
|
83
|
-
key = key.replace("!><", "")
|
|
84
|
-
op = "not between"
|
|
85
|
-
elif "><" in key:
|
|
86
|
-
key = key.replace("><", "")
|
|
87
|
-
op = "between"
|
|
88
|
-
elif "!%" in key:
|
|
89
|
-
key = key.replace("!%", "")
|
|
90
|
-
op = "not like"
|
|
91
|
-
elif "%%" in key:
|
|
92
|
-
key = key.replace("%%", "")
|
|
93
|
-
op = "%"
|
|
94
|
-
elif "%>" in key:
|
|
95
|
-
key = key.replace("%>", "")
|
|
96
|
-
op = "%>"
|
|
97
|
-
elif "<%" in key:
|
|
98
|
-
key = key.replace("<%", "")
|
|
99
|
-
op = "<%"
|
|
100
|
-
elif "==" in key:
|
|
101
|
-
key = key.replace("==", "")
|
|
102
|
-
op = "="
|
|
103
|
-
elif "<=" in key:
|
|
104
|
-
key = key.replace("<=", "")
|
|
105
|
-
op = "<="
|
|
106
|
-
elif ">=" in key:
|
|
107
|
-
key = key.replace(">=", "")
|
|
108
|
-
op = ">="
|
|
109
|
-
elif "<" in key:
|
|
110
|
-
key = key.replace("<", "")
|
|
111
|
-
op = "<"
|
|
112
|
-
elif ">" in key:
|
|
113
|
-
key = key.replace(">", "")
|
|
114
|
-
op = ">"
|
|
115
|
-
elif "%" in key:
|
|
116
|
-
key = key.replace("%", "")
|
|
117
|
-
op = "ilike"
|
|
118
|
-
elif "!~*" in key:
|
|
119
|
-
# String does not match regular expression, case insensitively
|
|
120
|
-
# 'thomas' !~* 'T.*ma' → False
|
|
121
|
-
key = key.replace("!~*", "")
|
|
122
|
-
op = "!~*"
|
|
123
|
-
elif "~*" in key:
|
|
124
|
-
# String matches regular expression, case insensitively
|
|
125
|
-
# 'thomas' ~* 'T.*ma' → True
|
|
126
|
-
key = key.replace("~*", "")
|
|
127
|
-
op = "~*"
|
|
128
|
-
elif "!~" in key:
|
|
129
|
-
# String does not match regular expression, case sensitively
|
|
130
|
-
# 'thomas' !~ 't.*max' → True
|
|
131
|
-
key = key.replace("!~", "")
|
|
132
|
-
op = "!~"
|
|
133
|
-
elif "~" in key:
|
|
134
|
-
# String matches regular expression, case sensitively
|
|
135
|
-
# 'thomas' ~ 't.*ma' → True
|
|
136
|
-
key = key.replace("~", "")
|
|
137
|
-
op = "~"
|
|
138
|
-
elif "!" in key:
|
|
139
|
-
key = key.replace("!", "")
|
|
140
|
-
op = "<>"
|
|
141
|
-
elif "=" in key:
|
|
142
|
-
key = key.replace("=", "")
|
|
143
|
-
op = "="
|
|
144
|
-
else:
|
|
145
|
-
op = "="
|
|
146
|
-
if "#" in key:
|
|
147
|
-
key = key.replace("#", "")
|
|
148
|
-
op = "="
|
|
149
|
-
case = "lower"
|
|
150
|
-
if isinstance(val, str) and val[:2] == "@@" and val[2:]:
|
|
151
|
-
sql.append("{} {} {}".format(key, op, val[2:]))
|
|
152
|
-
elif op in ["between", "not between"]:
|
|
153
|
-
sql.append("{} {} %s and %s".format(key, op))
|
|
154
|
-
vals.extend(val)
|
|
155
|
-
else:
|
|
156
|
-
if case:
|
|
157
|
-
sql.append("{2}({0}) {1} {2}(%s)".format(key, op, case))
|
|
158
|
-
else:
|
|
159
|
-
sql.append("{0} {1} %s".format(key, op))
|
|
160
|
-
vals.append(val)
|
|
161
|
-
connect = "AND"
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
def quote(data):
|
|
165
|
-
if isinstance(data, list):
|
|
166
|
-
new = []
|
|
167
|
-
for item in data:
|
|
168
|
-
if "@@" in item:
|
|
169
|
-
new.append(item[2:])
|
|
170
|
-
else:
|
|
171
|
-
new.append(quote(item))
|
|
172
|
-
return new
|
|
173
|
-
else:
|
|
174
|
-
parts = data.split(".")
|
|
175
|
-
new = []
|
|
176
|
-
for part in parts:
|
|
177
|
-
if '"' in part:
|
|
178
|
-
new.append(part)
|
|
179
|
-
elif part.upper() in reserved_words:
|
|
180
|
-
new.append('"' + part + '"')
|
|
181
|
-
elif re.findall("[/]", part):
|
|
182
|
-
new.append('"' + part + '"')
|
|
183
|
-
else:
|
|
184
|
-
new.append(part)
|
|
185
|
-
return ".".join(new)
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
class SQL(object):
|
|
189
|
-
server = "PostGreSQL"
|
|
190
|
-
type_column_identifier = "data_type"
|
|
191
|
-
is_nullable = "is_nullable"
|
|
192
|
-
|
|
193
|
-
default_schema = "public"
|
|
194
|
-
|
|
195
|
-
ApplicationErrorCodes = ["22P02", "42883"]
|
|
196
|
-
|
|
197
|
-
DatabaseMissingErrorCodes = []
|
|
198
|
-
TableMissingErrorCodes = ["42P01"]
|
|
199
|
-
ColumnMissingErrorCodes = ["42703"]
|
|
200
|
-
ForeignKeyMissingErrorCodes = ["42704"]
|
|
201
|
-
|
|
202
|
-
ConnectionErrorCodes = ["08001", "08S01"]
|
|
203
|
-
DuplicateKeyErrorCodes = [] # Handled in regex check.
|
|
204
|
-
RetryTransactionCodes = []
|
|
205
|
-
TruncationErrorCodes = []
|
|
206
|
-
LockTimeoutErrorCodes = ["55P03"]
|
|
207
|
-
DatabaseObjectExistsErrorCodes = ["42710", "42P07", "42P04"]
|
|
208
|
-
DataIntegrityErrorCodes = ["23503"]
|
|
209
|
-
|
|
210
|
-
@classmethod
|
|
211
|
-
def version(cls):
|
|
212
|
-
return "select version()", tuple()
|
|
213
|
-
|
|
214
|
-
@classmethod
|
|
215
|
-
def timestamp(cls):
|
|
216
|
-
return "select current_timestamp", tuple()
|
|
217
|
-
|
|
218
|
-
@classmethod
|
|
219
|
-
def user(cls):
|
|
220
|
-
return "select current_user", tuple()
|
|
221
|
-
|
|
222
|
-
@classmethod
|
|
223
|
-
def databases(cls):
|
|
224
|
-
return "select datname from pg_database where datistemplate = false", tuple()
|
|
225
|
-
|
|
226
|
-
@classmethod
|
|
227
|
-
def schemas(cls):
|
|
228
|
-
return "select schema_name from information_schema.schemata", tuple()
|
|
229
|
-
|
|
230
|
-
@classmethod
|
|
231
|
-
def current_schema(cls):
|
|
232
|
-
return "select current_schema", tuple()
|
|
233
|
-
|
|
234
|
-
@classmethod
|
|
235
|
-
def current_database(cls):
|
|
236
|
-
return "select current_database()", tuple()
|
|
237
|
-
|
|
238
|
-
@classmethod
|
|
239
|
-
def tables(cls, system=False):
|
|
240
|
-
if system:
|
|
241
|
-
return (
|
|
242
|
-
"select table_schema,table_name from information_schema.tables where table_type = 'BASE TABLE' order by table_schema,table_name",
|
|
243
|
-
tuple(),
|
|
244
|
-
)
|
|
245
|
-
else:
|
|
246
|
-
return (
|
|
247
|
-
"select table_schema, table_name from information_schema.tables where table_type = 'BASE TABLE' and table_schema NOT IN ('pg_catalog', 'information_schema')",
|
|
248
|
-
tuple(),
|
|
249
|
-
)
|
|
250
|
-
|
|
251
|
-
@classmethod
|
|
252
|
-
def views(cls, system=False):
|
|
253
|
-
if system:
|
|
254
|
-
return (
|
|
255
|
-
"select table_schema, table_name from information_schema.views order by table_schema,table_name",
|
|
256
|
-
tuple(),
|
|
257
|
-
)
|
|
258
|
-
else:
|
|
259
|
-
return (
|
|
260
|
-
"select table_schema, table_name from information_schema.views where table_schema = any (current_schemas(false)) order by table_schema,table_name",
|
|
261
|
-
tuple(),
|
|
262
|
-
)
|
|
263
|
-
|
|
264
|
-
@classmethod
|
|
265
|
-
def __has_pointer(cls, columns):
|
|
266
|
-
if isinstance(columns, str):
|
|
267
|
-
columns = columns.split(",")
|
|
268
|
-
if isinstance(columns, list):
|
|
269
|
-
for column in columns:
|
|
270
|
-
if "@@" in column:
|
|
271
|
-
continue
|
|
272
|
-
if ">" in column:
|
|
273
|
-
return True
|
|
274
|
-
return False
|
|
275
|
-
|
|
276
|
-
@classmethod
|
|
277
|
-
def select(
|
|
278
|
-
cls,
|
|
279
|
-
columns=None,
|
|
280
|
-
table=None,
|
|
281
|
-
where=None,
|
|
282
|
-
orderby=None,
|
|
283
|
-
groupby=None,
|
|
284
|
-
having=None,
|
|
285
|
-
start=None,
|
|
286
|
-
qty=None,
|
|
287
|
-
tbl=None,
|
|
288
|
-
lock=None,
|
|
289
|
-
skip_locked=None,
|
|
290
|
-
):
|
|
291
|
-
if not table:
|
|
292
|
-
raise Exception("Table name required")
|
|
293
|
-
is_join = False
|
|
294
|
-
|
|
295
|
-
if isinstance(columns, str) and "distinct" in columns.lower():
|
|
296
|
-
sql = [
|
|
297
|
-
"SELECT",
|
|
298
|
-
columns,
|
|
299
|
-
"FROM",
|
|
300
|
-
quote(table),
|
|
301
|
-
]
|
|
302
|
-
elif cls.__has_pointer(columns):
|
|
303
|
-
is_join = True
|
|
304
|
-
if isinstance(columns, str):
|
|
305
|
-
columns = columns.split(",")
|
|
306
|
-
letter = 65
|
|
307
|
-
tables = {table: chr(letter)}
|
|
308
|
-
letter += 1
|
|
309
|
-
__select = []
|
|
310
|
-
__from = ["{} AS {}".format(quote(table), tables.get(table))]
|
|
311
|
-
__left_join = []
|
|
312
|
-
|
|
313
|
-
for column in columns:
|
|
314
|
-
if "@@" in column:
|
|
315
|
-
__select.append(column[2:])
|
|
316
|
-
elif ">" in column:
|
|
317
|
-
parts = column.split(">")
|
|
318
|
-
foreign = tbl.foreign_key_info(parts[0])
|
|
319
|
-
if not foreign:
|
|
320
|
-
raise exceptions.DbApplicationError("Foreign key not defined")
|
|
321
|
-
ref_table = foreign["referenced_table_name"]
|
|
322
|
-
ref_schema = foreign["referenced_table_schema"]
|
|
323
|
-
ref_column = foreign["referenced_column_name"]
|
|
324
|
-
lookup = "{}:{}".format(ref_table, parts[0])
|
|
325
|
-
if lookup in tables:
|
|
326
|
-
__select.append(
|
|
327
|
-
'{}."{}" as "{}"'.format(
|
|
328
|
-
tables.get(lookup), parts[1], "_".join(parts)
|
|
329
|
-
)
|
|
330
|
-
)
|
|
331
|
-
else:
|
|
332
|
-
tables[lookup] = chr(letter)
|
|
333
|
-
letter += 1
|
|
334
|
-
__select.append(
|
|
335
|
-
'{}."{}" as "{}"'.format(
|
|
336
|
-
tables.get(lookup), parts[1], "_".join(parts)
|
|
337
|
-
)
|
|
338
|
-
)
|
|
339
|
-
__left_join.append(
|
|
340
|
-
'LEFT OUTER JOIN "{}"."{}" AS {}'.format(
|
|
341
|
-
ref_schema, ref_table, tables.get(lookup)
|
|
342
|
-
)
|
|
343
|
-
)
|
|
344
|
-
__left_join.append(
|
|
345
|
-
'ON {}."{}" = {}."{}"'.format(
|
|
346
|
-
tables.get(table),
|
|
347
|
-
parts[0],
|
|
348
|
-
tables.get(lookup),
|
|
349
|
-
ref_column,
|
|
350
|
-
)
|
|
351
|
-
)
|
|
352
|
-
if orderby and column in orderby:
|
|
353
|
-
orderby = orderby.replace(
|
|
354
|
-
column, "{}.{}".format(tables.get(lookup), parts[1])
|
|
355
|
-
)
|
|
356
|
-
|
|
357
|
-
else:
|
|
358
|
-
if "(" in column:
|
|
359
|
-
__select.append(column)
|
|
360
|
-
else:
|
|
361
|
-
__select.append("{}.{}".format(tables.get(table), column))
|
|
362
|
-
sql = ["SELECT"]
|
|
363
|
-
sql.append(",".join(__select))
|
|
364
|
-
sql.append("FROM")
|
|
365
|
-
sql.extend(__from)
|
|
366
|
-
sql.extend(__left_join)
|
|
367
|
-
else:
|
|
368
|
-
if columns:
|
|
369
|
-
if isinstance(columns, str):
|
|
370
|
-
columns = columns.split(",")
|
|
371
|
-
if isinstance(columns, list):
|
|
372
|
-
columns = quote(columns)
|
|
373
|
-
columns = ",".join(columns)
|
|
374
|
-
else:
|
|
375
|
-
columns = "*"
|
|
376
|
-
sql = [
|
|
377
|
-
"SELECT",
|
|
378
|
-
columns,
|
|
379
|
-
"FROM",
|
|
380
|
-
quote(table),
|
|
381
|
-
]
|
|
382
|
-
vals = []
|
|
383
|
-
make_where(where, sql, vals, is_join)
|
|
384
|
-
if groupby:
|
|
385
|
-
sql.append("GROUP BY")
|
|
386
|
-
if isinstance(groupby, (list, tuple)):
|
|
387
|
-
groupby = ",".join(groupby)
|
|
388
|
-
sql.append(groupby)
|
|
389
|
-
if having:
|
|
390
|
-
sql.append("HAVING")
|
|
391
|
-
if isinstance(having, (list, tuple)):
|
|
392
|
-
having = ",".join(having)
|
|
393
|
-
sql.append(having)
|
|
394
|
-
if orderby:
|
|
395
|
-
sql.append("ORDER BY")
|
|
396
|
-
if isinstance(orderby, (list, tuple)):
|
|
397
|
-
orderby = ",".join(orderby)
|
|
398
|
-
sql.append(orderby)
|
|
399
|
-
if start and qty:
|
|
400
|
-
sql.append("OFFSET {} ROWS FETCH NEXT {} ROWS ONLY".format(start, qty))
|
|
401
|
-
elif start:
|
|
402
|
-
sql.append("OFFSET {} ROWS".format(start))
|
|
403
|
-
elif qty:
|
|
404
|
-
sql.append("FETCH NEXT {} ROWS ONLY".format(qty))
|
|
405
|
-
if lock or skip_locked:
|
|
406
|
-
sql.append("FOR UPDATE")
|
|
407
|
-
if skip_locked:
|
|
408
|
-
sql.append("SKIP LOCKED")
|
|
409
|
-
sql = " ".join(sql)
|
|
410
|
-
return sql, tuple(vals)
|
|
411
|
-
|
|
412
|
-
@classmethod
|
|
413
|
-
def create_database(cls, name):
|
|
414
|
-
return "create database " + name, tuple()
|
|
415
|
-
|
|
416
|
-
@classmethod
|
|
417
|
-
def last_id(cls, table):
|
|
418
|
-
return "SELECT CURRVAL(PG_GET_SERIAL_SEQUENCE(%s, 'sys_id'))", tuple([table])
|
|
419
|
-
|
|
420
|
-
@classmethod
|
|
421
|
-
def current_id(cls, table):
|
|
422
|
-
return (
|
|
423
|
-
"SELECT pg_sequence_last_value(PG_GET_SERIAL_SEQUENCE(%s, 'sys_id'))",
|
|
424
|
-
tuple([table]),
|
|
425
|
-
)
|
|
426
|
-
|
|
427
|
-
@classmethod
|
|
428
|
-
def set_id(cls, table, start):
|
|
429
|
-
return "SELECT SETVAL(PG_GET_SERIAL_SEQUENCE(%s, 'sys_id'), %s)", tuple(
|
|
430
|
-
[table, start]
|
|
431
|
-
)
|
|
432
|
-
|
|
433
|
-
@classmethod
|
|
434
|
-
def drop_database(cls, name):
|
|
435
|
-
return "drop database if exists " + name, tuple()
|
|
436
|
-
|
|
437
|
-
@classmethod
|
|
438
|
-
def create_table(cls, name, columns={}, drop=False):
|
|
439
|
-
if "." in name:
|
|
440
|
-
fqtn = name
|
|
441
|
-
else:
|
|
442
|
-
fqtn = "public." + name
|
|
443
|
-
schema, table = fqtn.split(".")
|
|
444
|
-
name = fqtn.replace(".", "_")
|
|
445
|
-
trigger = "".format(name)
|
|
446
|
-
sql = []
|
|
447
|
-
if drop:
|
|
448
|
-
sql.append(cls.drop_table(fqtn)[0])
|
|
449
|
-
sql.append(
|
|
450
|
-
"""
|
|
451
|
-
CREATE TABLE {0} (
|
|
452
|
-
sys_id BIGSERIAL PRIMARY KEY,
|
|
453
|
-
sys_modified TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
454
|
-
sys_created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
455
|
-
sys_modified_by TEXT,
|
|
456
|
-
sys_dirty BOOLEAN NOT NULL DEFAULT FALSE,
|
|
457
|
-
sys_table TEXT,
|
|
458
|
-
description TEXT
|
|
459
|
-
);
|
|
460
|
-
|
|
461
|
-
SELECT SETVAL(PG_GET_SERIAL_SEQUENCE('{0}', 'sys_id'),1000,TRUE);
|
|
462
|
-
|
|
463
|
-
CREATE OR REPLACE FUNCTION {1}.on_sys_modified()
|
|
464
|
-
RETURNS TRIGGER AS
|
|
465
|
-
$BODY$
|
|
466
|
-
BEGIN
|
|
467
|
-
-- update sys_modified on each insert/update.
|
|
468
|
-
NEW.sys_modified := now();
|
|
469
|
-
if (TG_OP = 'INSERT') THEN
|
|
470
|
-
NEW.sys_created :=now();
|
|
471
|
-
ELSEIF (TG_OP = 'UDPATE') THEN
|
|
472
|
-
-- Do not allow sys_created to be modified.
|
|
473
|
-
NEW.sys_created := OLD.sys_created;
|
|
474
|
-
END IF;
|
|
475
|
-
-- Insert table name to row
|
|
476
|
-
NEW.sys_table := TG_TABLE_NAME;
|
|
477
|
-
RETURN NEW;
|
|
478
|
-
END;
|
|
479
|
-
$BODY$
|
|
480
|
-
LANGUAGE plpgsql VOLATILE
|
|
481
|
-
COST 100;
|
|
482
|
-
|
|
483
|
-
CREATE TRIGGER on_update_row_{3}
|
|
484
|
-
BEFORE INSERT OR UPDATE ON {0}
|
|
485
|
-
FOR EACH ROW EXECUTE PROCEDURE {1}.on_sys_modified();
|
|
486
|
-
|
|
487
|
-
""".format(
|
|
488
|
-
fqtn, schema, table, fqtn.replace(".", "_")
|
|
489
|
-
)
|
|
490
|
-
)
|
|
491
|
-
|
|
492
|
-
for key, val in columns.items():
|
|
493
|
-
key = re.sub("<>!=%", "", key.lower())
|
|
494
|
-
if key in system_fields:
|
|
495
|
-
continue
|
|
496
|
-
sql.append(
|
|
497
|
-
"ALTER TABLE {} ADD COLUMN {} {};".format(
|
|
498
|
-
quote(fqtn), quote(key), cls.get_type(val)
|
|
499
|
-
)
|
|
500
|
-
)
|
|
501
|
-
return "\n\t".join(sql), tuple()
|
|
502
|
-
|
|
503
|
-
@classmethod
|
|
504
|
-
def drop_table(cls, name):
|
|
505
|
-
return "drop table if exists %s cascade;" % quote(name), tuple()
|
|
506
|
-
|
|
507
|
-
@classmethod
|
|
508
|
-
def drop_column(cls, table, name, cascade=True):
|
|
509
|
-
if cascade:
|
|
510
|
-
return (
|
|
511
|
-
"ALTER TABLE %s DROP COLUMN %s CASCADE" % (quote(table), quote(name)),
|
|
512
|
-
tuple(),
|
|
513
|
-
)
|
|
514
|
-
else:
|
|
515
|
-
return (
|
|
516
|
-
"ALTER TABLE %s DROP COLUMN %s " % (quote(table), quote(name)),
|
|
517
|
-
tuple(),
|
|
518
|
-
)
|
|
519
|
-
|
|
520
|
-
@classmethod
|
|
521
|
-
def columns(cls, name):
|
|
522
|
-
if "." in name:
|
|
523
|
-
return """
|
|
524
|
-
select column_name
|
|
525
|
-
from information_schema.columns
|
|
526
|
-
where UPPER(table_schema) = UPPER(%s)
|
|
527
|
-
and UPPER(table_name) = UPPER(%s)
|
|
528
|
-
""", tuple(
|
|
529
|
-
name.split(".")
|
|
530
|
-
)
|
|
531
|
-
else:
|
|
532
|
-
return """
|
|
533
|
-
select column_name
|
|
534
|
-
from information_schema.columns
|
|
535
|
-
where UPPER(table_name) = UPPER(%s)
|
|
536
|
-
""", tuple(
|
|
537
|
-
[
|
|
538
|
-
name,
|
|
539
|
-
]
|
|
540
|
-
)
|
|
541
|
-
|
|
542
|
-
@classmethod
|
|
543
|
-
def column_info(cls, table, name):
|
|
544
|
-
params = table.split(".")
|
|
545
|
-
params.append(name)
|
|
546
|
-
if "." in table:
|
|
547
|
-
return """
|
|
548
|
-
select *
|
|
549
|
-
from information_schema.columns
|
|
550
|
-
where UPPER(table_schema ) = UPPER(%s)
|
|
551
|
-
and UPPER(table_name) = UPPER(%s)
|
|
552
|
-
and UPPER(column_name) = UPPER(%s)
|
|
553
|
-
""", tuple(
|
|
554
|
-
params
|
|
555
|
-
)
|
|
556
|
-
else:
|
|
557
|
-
return """
|
|
558
|
-
select *
|
|
559
|
-
from information_schema.columns
|
|
560
|
-
where UPPER(table_name) = UPPER(%s)
|
|
561
|
-
and UPPER(column_name) = UPPER(%s)
|
|
562
|
-
""", tuple(
|
|
563
|
-
params
|
|
564
|
-
)
|
|
565
|
-
|
|
566
|
-
@classmethod
|
|
567
|
-
def primary_keys(cls, table):
|
|
568
|
-
params = table.split(".")
|
|
569
|
-
params.reverse()
|
|
570
|
-
if "." in table:
|
|
571
|
-
return """
|
|
572
|
-
SELECT
|
|
573
|
-
pg_attribute.attname
|
|
574
|
-
FROM pg_index, pg_class, pg_attribute, pg_namespace
|
|
575
|
-
WHERE
|
|
576
|
-
pg_class.oid = %s::regclass AND
|
|
577
|
-
indrelid = pg_class.oid AND
|
|
578
|
-
nspname = %s AND
|
|
579
|
-
pg_class.relnamespace = pg_namespace.oid AND
|
|
580
|
-
pg_attribute.attrelid = pg_class.oid AND
|
|
581
|
-
pg_attribute.attnum = any(pg_index.indkey)
|
|
582
|
-
AND indisprimary
|
|
583
|
-
""", tuple(
|
|
584
|
-
params
|
|
585
|
-
)
|
|
586
|
-
else:
|
|
587
|
-
return """
|
|
588
|
-
SELECT
|
|
589
|
-
pg_attribute.attname
|
|
590
|
-
FROM pg_index, pg_class, pg_attribute, pg_namespace
|
|
591
|
-
WHERE
|
|
592
|
-
pg_class.oid = %s::regclass AND
|
|
593
|
-
indrelid = pg_class.oid AND
|
|
594
|
-
pg_class.relnamespace = pg_namespace.oid AND
|
|
595
|
-
pg_attribute.attrelid = pg_class.oid AND
|
|
596
|
-
pg_attribute.attnum = any(pg_index.indkey)
|
|
597
|
-
AND indisprimary
|
|
598
|
-
""", tuple(
|
|
599
|
-
params
|
|
600
|
-
)
|
|
601
|
-
|
|
602
|
-
@classmethod
|
|
603
|
-
def foreign_key_info(cls, table=None, column=None, schema=None):
|
|
604
|
-
if "." in table:
|
|
605
|
-
schema, table = table.split(".")
|
|
606
|
-
|
|
607
|
-
sql = [
|
|
608
|
-
"""
|
|
609
|
-
SELECT
|
|
610
|
-
KCU1.CONSTRAINT_NAME AS "FK_CONSTRAINT_NAME"
|
|
611
|
-
, KCU1.CONSTRAINT_SCHEMA AS "FK_CONSTRAINT_SCHEMA"
|
|
612
|
-
, KCU1.CONSTRAINT_CATALOG AS "FK_CONSTRAINT_CATALOG"
|
|
613
|
-
, KCU1.TABLE_NAME AS "FK_TABLE_NAME"
|
|
614
|
-
, KCU1.COLUMN_NAME AS "FK_COLUMN_NAME"
|
|
615
|
-
, KCU1.ORDINAL_POSITION AS "FK_ORDINAL_POSITION"
|
|
616
|
-
, KCU2.CONSTRAINT_NAME AS "UQ_CONSTRAINT_NAME"
|
|
617
|
-
, KCU2.CONSTRAINT_SCHEMA AS "UQ_CONSTRAINT_SCHEMA"
|
|
618
|
-
, KCU2.CONSTRAINT_CATALOG AS "UQ_CONSTRAINT_CATALOG"
|
|
619
|
-
, KCU2.TABLE_NAME AS "UQ_TABLE_NAME"
|
|
620
|
-
, KCU2.COLUMN_NAME AS "UQ_COLUMN_NAME"
|
|
621
|
-
, KCU2.ORDINAL_POSITION AS "UQ_ORDINAL_POSITION"
|
|
622
|
-
, KCU1.CONSTRAINT_NAME AS "CONSTRAINT_NAME"
|
|
623
|
-
, KCU2.CONSTRAINT_SCHEMA AS "REFERENCED_TABLE_SCHEMA"
|
|
624
|
-
, KCU2.TABLE_NAME AS "REFERENCED_TABLE_NAME"
|
|
625
|
-
, KCU2.COLUMN_NAME AS "REFERENCED_COLUMN_NAME"
|
|
626
|
-
FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS RC
|
|
627
|
-
JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE KCU1
|
|
628
|
-
ON KCU1.CONSTRAINT_CATALOG = RC.CONSTRAINT_CATALOG
|
|
629
|
-
AND KCU1.CONSTRAINT_SCHEMA = RC.CONSTRAINT_SCHEMA
|
|
630
|
-
AND KCU1.CONSTRAINT_NAME = RC.CONSTRAINT_NAME
|
|
631
|
-
JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE KCU2
|
|
632
|
-
ON KCU2.CONSTRAINT_CATALOG = RC.UNIQUE_CONSTRAINT_CATALOG
|
|
633
|
-
AND KCU2.CONSTRAINT_SCHEMA = RC.UNIQUE_CONSTRAINT_SCHEMA
|
|
634
|
-
AND KCU2.CONSTRAINT_NAME = RC.UNIQUE_CONSTRAINT_NAME
|
|
635
|
-
AND KCU2.ORDINAL_POSITION = KCU1.ORDINAL_POSITION
|
|
636
|
-
"""
|
|
637
|
-
]
|
|
638
|
-
vals = []
|
|
639
|
-
where = {}
|
|
640
|
-
if schema:
|
|
641
|
-
where["LOWER(KCU1.CONSTRAINT_SCHEMA)"] = schema.lower()
|
|
642
|
-
if table:
|
|
643
|
-
where["LOWER(KCU1.TABLE_NAME)"] = table.lower()
|
|
644
|
-
if column:
|
|
645
|
-
where["LOWER(KCU1.COLUMN_NAME)"] = column.lower()
|
|
646
|
-
make_where(where, sql, vals)
|
|
647
|
-
return " ".join(sql), tuple(vals)
|
|
648
|
-
|
|
649
|
-
@classmethod
|
|
650
|
-
def create_foreign_key(
|
|
651
|
-
cls, table, columns, key_to_table, key_to_columns, name=None, schema=None
|
|
652
|
-
):
|
|
653
|
-
if "." not in table and schema:
|
|
654
|
-
table = "{}.{}".format(schema, table)
|
|
655
|
-
if isinstance(key_to_columns, str):
|
|
656
|
-
key_to_columns = [key_to_columns]
|
|
657
|
-
if isinstance(columns, str):
|
|
658
|
-
columns = [columns]
|
|
659
|
-
if not name:
|
|
660
|
-
m = hashlib.md5()
|
|
661
|
-
m.update(table.encode("utf-8"))
|
|
662
|
-
m.update(" ".join(columns).encode("utf-8"))
|
|
663
|
-
m.update(key_to_table.encode("utf-8"))
|
|
664
|
-
m.update(" ".join(key_to_columns).encode("utf-8"))
|
|
665
|
-
name = "FK_" + m.hexdigest()
|
|
666
|
-
sql = "ALTER TABLE {} ADD CONSTRAINT {} FOREIGN KEY ({}) REFERENCES {} ({});".format(
|
|
667
|
-
table, name, ",".join(columns), key_to_table, ",".join(key_to_columns)
|
|
668
|
-
)
|
|
669
|
-
|
|
670
|
-
return sql, tuple()
|
|
671
|
-
|
|
672
|
-
@classmethod
|
|
673
|
-
def drop_foreign_key(
|
|
674
|
-
cls,
|
|
675
|
-
table,
|
|
676
|
-
columns,
|
|
677
|
-
key_to_table=None,
|
|
678
|
-
key_to_columns=None,
|
|
679
|
-
name=None,
|
|
680
|
-
schema=None,
|
|
681
|
-
):
|
|
682
|
-
if "." not in table and schema:
|
|
683
|
-
table = "{}.{}".format(schema, table)
|
|
684
|
-
if isinstance(key_to_columns, str):
|
|
685
|
-
key_to_columns = [key_to_columns]
|
|
686
|
-
if isinstance(columns, str):
|
|
687
|
-
columns = [columns]
|
|
688
|
-
if not name:
|
|
689
|
-
m = hashlib.md5()
|
|
690
|
-
m.update(table.encode("utf-8"))
|
|
691
|
-
m.update(" ".join(columns).encode("utf-8"))
|
|
692
|
-
m.update(key_to_table.encode("utf-8"))
|
|
693
|
-
m.update(" ".join(key_to_columns).encode("utf-8"))
|
|
694
|
-
name = "FK_" + m.hexdigest()
|
|
695
|
-
sql = "ALTER TABLE {} DROP CONSTRAINT {};".format(table, name)
|
|
696
|
-
return sql, tuple()
|
|
697
|
-
|
|
698
|
-
@classmethod
|
|
699
|
-
def create_index(
|
|
700
|
-
cls,
|
|
701
|
-
table=None,
|
|
702
|
-
columns=None,
|
|
703
|
-
unique=False,
|
|
704
|
-
direction=None,
|
|
705
|
-
where=None,
|
|
706
|
-
name=None,
|
|
707
|
-
schema=None,
|
|
708
|
-
trigram=None,
|
|
709
|
-
tbl=None,
|
|
710
|
-
lower=None,
|
|
711
|
-
):
|
|
712
|
-
"""
|
|
713
|
-
The following statements must be executed on the database instance once to enable respective trigram features.
|
|
714
|
-
CREATE EXTENSION pg_trgm; is required to use gin.
|
|
715
|
-
CREATE EXTENSION btree_gist; is required to use gist
|
|
716
|
-
"""
|
|
717
|
-
if "." not in table and schema:
|
|
718
|
-
table = "{}.{}".format(schema, table)
|
|
719
|
-
if isinstance(columns, (list, set)):
|
|
720
|
-
columns = ",".join([quote(c.lower()) for c in columns])
|
|
721
|
-
else:
|
|
722
|
-
columns = quote(columns)
|
|
723
|
-
sql = ["CREATE"]
|
|
724
|
-
if unique:
|
|
725
|
-
sql.append("UNIQUE")
|
|
726
|
-
sql.append("INDEX")
|
|
727
|
-
tablename = quote(table)
|
|
728
|
-
if not name:
|
|
729
|
-
name = re.sub(
|
|
730
|
-
r"\([^)]*\)",
|
|
731
|
-
"",
|
|
732
|
-
columns.replace(" ", "").replace(",", "_").replace('"', ""),
|
|
733
|
-
)
|
|
734
|
-
if trigram:
|
|
735
|
-
sql.append(
|
|
736
|
-
"IDX__TRGM_{}_{}__{}".format(
|
|
737
|
-
table.replace(".", "_"), trigram.upper(), name
|
|
738
|
-
)
|
|
739
|
-
)
|
|
740
|
-
else:
|
|
741
|
-
sql.append("IDX__{}__{}".format(table.replace(".", "_"), name))
|
|
742
|
-
sql.append("ON")
|
|
743
|
-
sql.append(quote(tablename))
|
|
744
|
-
|
|
745
|
-
if trigram:
|
|
746
|
-
sql.append("USING")
|
|
747
|
-
sql.append(trigram)
|
|
748
|
-
sql.append("(")
|
|
749
|
-
if tbl:
|
|
750
|
-
join = ""
|
|
751
|
-
for column_name in columns.split(","):
|
|
752
|
-
column_name = column_name.replace('"', "")
|
|
753
|
-
if join:
|
|
754
|
-
sql.append(join)
|
|
755
|
-
column = tbl.column(column_name)
|
|
756
|
-
if column.py_type == str:
|
|
757
|
-
if lower:
|
|
758
|
-
sql.append("lower({})".format(quote(column_name)))
|
|
759
|
-
else:
|
|
760
|
-
sql.append(quote(column_name))
|
|
761
|
-
else:
|
|
762
|
-
sql.append(quote(column_name))
|
|
763
|
-
join = ","
|
|
764
|
-
else:
|
|
765
|
-
sql.append(columns)
|
|
766
|
-
if trigram:
|
|
767
|
-
sql.append("{}_trgm_ops".format(trigram.lower()))
|
|
768
|
-
sql.append(")")
|
|
769
|
-
vals = []
|
|
770
|
-
|
|
771
|
-
make_where(where, sql, vals)
|
|
772
|
-
return " ".join(sql), tuple(vals)
|
|
773
|
-
|
|
774
|
-
@classmethod
|
|
775
|
-
def drop_index(cls, table=None, columns=None, name=None, schema=None, trigram=None):
|
|
776
|
-
if "." not in table and schema:
|
|
777
|
-
table = "{}.{}".format(schema, table)
|
|
778
|
-
if isinstance(columns, (list, set)):
|
|
779
|
-
columns = ",".join([quote(c.lower()) for c in columns])
|
|
780
|
-
else:
|
|
781
|
-
columns = quote(columns)
|
|
782
|
-
sql = ["DROP"]
|
|
783
|
-
sql.append("INDEX IF EXISTS")
|
|
784
|
-
tablename = quote(table)
|
|
785
|
-
if not name:
|
|
786
|
-
name = re.sub(
|
|
787
|
-
r"\([^)]*\)",
|
|
788
|
-
"",
|
|
789
|
-
columns.replace(" ", "").replace(",", "_").replace('"', ""),
|
|
790
|
-
)
|
|
791
|
-
if trigram:
|
|
792
|
-
sql.append(
|
|
793
|
-
"IDX__TRGM_{}_{}__{}".format(
|
|
794
|
-
table.replace(".", "_"), trigram.upper(), name
|
|
795
|
-
)
|
|
796
|
-
)
|
|
797
|
-
else:
|
|
798
|
-
sql.append("IDX__{}__{}".format(table.replace(".", "_"), name))
|
|
799
|
-
return " ".join(sql), tuple()
|
|
800
|
-
|
|
801
|
-
@classmethod
|
|
802
|
-
def merge(cls, table, data, pk, on_conflict_do_nothing, on_conflict_update):
|
|
803
|
-
d = {}
|
|
804
|
-
d.update(data)
|
|
805
|
-
d.update(pk)
|
|
806
|
-
sql, vals = cls.insert(table, d)
|
|
807
|
-
sql = [sql]
|
|
808
|
-
vals = list(vals)
|
|
809
|
-
if on_conflict_do_nothing != on_conflict_update:
|
|
810
|
-
sql.append("ON CONFLICT")
|
|
811
|
-
sql.append("(")
|
|
812
|
-
sql.append(",".join(pk.keys()))
|
|
813
|
-
sql.append(")")
|
|
814
|
-
sql.append("DO")
|
|
815
|
-
if on_conflict_do_nothing:
|
|
816
|
-
sql.append("NOTHING")
|
|
817
|
-
elif on_conflict_update:
|
|
818
|
-
sql2, vals2 = cls.update(table, data, pk, excluded=True)
|
|
819
|
-
sql.append(sql2)
|
|
820
|
-
vals.extend(vals2)
|
|
821
|
-
else:
|
|
822
|
-
raise Exception(
|
|
823
|
-
"Update on conflict must have one and only one option to complete on conflict."
|
|
824
|
-
)
|
|
825
|
-
return " ".join(sql), tuple(vals)
|
|
826
|
-
|
|
827
|
-
@classmethod
|
|
828
|
-
def insert(cls, table, data):
|
|
829
|
-
keys = []
|
|
830
|
-
vals = []
|
|
831
|
-
args = []
|
|
832
|
-
for key, val in data.items():
|
|
833
|
-
keys.append(quote(key.lower()))
|
|
834
|
-
if isinstance(val, str) and len(val) > 2 and val[:2] == "@@" and val[2:]:
|
|
835
|
-
vals.append(val[2:])
|
|
836
|
-
else:
|
|
837
|
-
vals.append("%s")
|
|
838
|
-
args.append(val)
|
|
839
|
-
|
|
840
|
-
sql = ["INSERT INTO"]
|
|
841
|
-
sql.append(quote(table))
|
|
842
|
-
sql.append("(")
|
|
843
|
-
sql.append(",".join(keys))
|
|
844
|
-
sql.append(")")
|
|
845
|
-
sql.append("VALUES")
|
|
846
|
-
sql.append("(")
|
|
847
|
-
sql.append(",".join(vals))
|
|
848
|
-
sql.append(")")
|
|
849
|
-
sql = " ".join(sql)
|
|
850
|
-
return sql, tuple(args)
|
|
851
|
-
|
|
852
|
-
@classmethod
|
|
853
|
-
def update(
|
|
854
|
-
cls,
|
|
855
|
-
table,
|
|
856
|
-
data,
|
|
857
|
-
pk,
|
|
858
|
-
left_join=None,
|
|
859
|
-
inner_join=None,
|
|
860
|
-
outer_join=None,
|
|
861
|
-
excluded=False,
|
|
862
|
-
):
|
|
863
|
-
alias = "A"
|
|
864
|
-
if " " in table:
|
|
865
|
-
alias, table = table.split(" ")
|
|
866
|
-
is_join = bool(left_join or inner_join or outer_join)
|
|
867
|
-
sql = ["UPDATE"]
|
|
868
|
-
if not excluded:
|
|
869
|
-
sql.append(quote(table))
|
|
870
|
-
sql.append("SET")
|
|
871
|
-
vals = []
|
|
872
|
-
connect = ""
|
|
873
|
-
for key, val in data.items():
|
|
874
|
-
if connect:
|
|
875
|
-
sql.append(connect)
|
|
876
|
-
if isinstance(val, str) and val[:2] == "@@" and val[2:]:
|
|
877
|
-
sql.append("{} = {}".format(key, val[2:]))
|
|
878
|
-
else:
|
|
879
|
-
if excluded:
|
|
880
|
-
sql.append("{} = EXCLUDED.{}".format(key, key))
|
|
881
|
-
else:
|
|
882
|
-
sql.append("{} = %s".format(key))
|
|
883
|
-
vals.append(val)
|
|
884
|
-
connect = ","
|
|
885
|
-
if is_join:
|
|
886
|
-
sql.append("FROM")
|
|
887
|
-
sql.append(table)
|
|
888
|
-
sql.append("AS")
|
|
889
|
-
sql.append(alias)
|
|
890
|
-
if left_join:
|
|
891
|
-
for k, v in left_join.items():
|
|
892
|
-
sql.append("LEFT JOIN")
|
|
893
|
-
sql.append(k)
|
|
894
|
-
sql.append("ON")
|
|
895
|
-
sql.append(v)
|
|
896
|
-
if outer_join:
|
|
897
|
-
for k, v in outer_join.items():
|
|
898
|
-
sql.append("OUTER JOIN")
|
|
899
|
-
sql.append(k)
|
|
900
|
-
sql.append("ON")
|
|
901
|
-
sql.append(v)
|
|
902
|
-
if inner_join:
|
|
903
|
-
for k, v in inner_join.items():
|
|
904
|
-
sql.append("INNER JOIN")
|
|
905
|
-
sql.append(k)
|
|
906
|
-
sql.append("ON")
|
|
907
|
-
sql.append(v)
|
|
908
|
-
if not excluded:
|
|
909
|
-
make_where(pk, sql, vals, is_join)
|
|
910
|
-
return " ".join(sql), tuple(vals)
|
|
911
|
-
|
|
912
|
-
@classmethod
|
|
913
|
-
def get_type(cls, v):
|
|
914
|
-
if isinstance(v, str):
|
|
915
|
-
if v[:2] == "@@":
|
|
916
|
-
return v[2:] or cls.TYPES.TEXT
|
|
917
|
-
elif isinstance(v, str) or v is str:
|
|
918
|
-
return cls.TYPES.TEXT
|
|
919
|
-
elif isinstance(v, bool) or v is bool:
|
|
920
|
-
return cls.TYPES.BOOLEAN
|
|
921
|
-
elif isinstance(v, int) or v is int:
|
|
922
|
-
return cls.TYPES.BIGINT
|
|
923
|
-
# elif isinstance(v, int) \
|
|
924
|
-
# or v is int:
|
|
925
|
-
# if v is int:
|
|
926
|
-
# return cls.TYPES.INTEGER
|
|
927
|
-
# if v > 2147483647 or v < -2147483648:
|
|
928
|
-
# return cls.TYPES.BIGINT
|
|
929
|
-
# else:
|
|
930
|
-
# return cls.TYPES.INTEGER
|
|
931
|
-
elif isinstance(v, float) or v is float:
|
|
932
|
-
return cls.TYPES.NUMERIC + "(19, 6)"
|
|
933
|
-
elif isinstance(v, decimal.Decimal) or v is decimal.Decimal:
|
|
934
|
-
return cls.TYPES.NUMERIC + "(19, 6)"
|
|
935
|
-
elif isinstance(v, datetime.datetime) or v is datetime.datetime:
|
|
936
|
-
return cls.TYPES.DATETIME
|
|
937
|
-
elif isinstance(v, datetime.date) or v is datetime.date:
|
|
938
|
-
return cls.TYPES.DATE
|
|
939
|
-
elif isinstance(v, datetime.time) or v is datetime.time:
|
|
940
|
-
return cls.TYPES.TIME
|
|
941
|
-
elif isinstance(v, datetime.timedelta) or v is datetime.timedelta:
|
|
942
|
-
return cls.TYPES.INTERVAL
|
|
943
|
-
elif isinstance(v, bytes) or v is bytes:
|
|
944
|
-
return cls.TYPES.BINARY
|
|
945
|
-
# Everything else defaults to TEXT, incl. None
|
|
946
|
-
return cls.TYPES.TEXT
|
|
947
|
-
|
|
948
|
-
@classmethod
|
|
949
|
-
def get_conv(cls, v):
|
|
950
|
-
if isinstance(v, str):
|
|
951
|
-
if v[:2] == "@@":
|
|
952
|
-
return v[2:] or cls.TYPES.TEXT
|
|
953
|
-
elif isinstance(v, str) or v is str:
|
|
954
|
-
return cls.TYPES.TEXT
|
|
955
|
-
elif isinstance(v, bool) or v is bool:
|
|
956
|
-
return cls.TYPES.BOOLEAN
|
|
957
|
-
elif isinstance(v, int) or v is int:
|
|
958
|
-
return cls.TYPES.BIGINT
|
|
959
|
-
# elif isinstance(v, int) \
|
|
960
|
-
# or v is int:
|
|
961
|
-
# if v is int:
|
|
962
|
-
# return cls.TYPES.INTEGER
|
|
963
|
-
# if v > 2147483647 or v < -2147483648:
|
|
964
|
-
# return cls.TYPES.BIGINT
|
|
965
|
-
# else:
|
|
966
|
-
# return cls.TYPES.INTEGER
|
|
967
|
-
elif isinstance(v, float) or v is float:
|
|
968
|
-
return cls.TYPES.NUMERIC
|
|
969
|
-
elif isinstance(v, decimal.Decimal) or v is decimal.Decimal:
|
|
970
|
-
return cls.TYPES.NUMERIC
|
|
971
|
-
elif isinstance(v, datetime.datetime) or v is datetime.datetime:
|
|
972
|
-
return cls.TYPES.DATETIME
|
|
973
|
-
elif isinstance(v, datetime.date) or v is datetime.date:
|
|
974
|
-
return cls.TYPES.DATE
|
|
975
|
-
elif isinstance(v, datetime.time) or v is datetime.time:
|
|
976
|
-
return cls.TYPES.TIME
|
|
977
|
-
elif isinstance(v, datetime.timedelta) or v is datetime.timedelta:
|
|
978
|
-
return cls.TYPES.INTERVAL
|
|
979
|
-
elif isinstance(v, bytes) or v is bytes:
|
|
980
|
-
return cls.TYPES.BINARY
|
|
981
|
-
# Everything else defaults to TEXT, incl. None
|
|
982
|
-
return cls.TYPES.TEXT
|
|
983
|
-
|
|
984
|
-
@classmethod
|
|
985
|
-
def py_type(cls, v):
|
|
986
|
-
v = str(v).upper()
|
|
987
|
-
if v == cls.TYPES.INTEGER:
|
|
988
|
-
return int
|
|
989
|
-
elif v == cls.TYPES.SMALLINT:
|
|
990
|
-
return int
|
|
991
|
-
elif v == cls.TYPES.BIGINT:
|
|
992
|
-
return int
|
|
993
|
-
elif v == cls.TYPES.NUMERIC:
|
|
994
|
-
return decimal.Decimal
|
|
995
|
-
elif v == cls.TYPES.TEXT:
|
|
996
|
-
return str
|
|
997
|
-
elif v == cls.TYPES.BOOLEAN:
|
|
998
|
-
return bool
|
|
999
|
-
elif v == cls.TYPES.DATE:
|
|
1000
|
-
return datetime.date
|
|
1001
|
-
elif v == cls.TYPES.TIME:
|
|
1002
|
-
return datetime.time
|
|
1003
|
-
elif v == cls.TYPES.TIME_TZ:
|
|
1004
|
-
return datetime.time
|
|
1005
|
-
elif v == cls.TYPES.DATETIME:
|
|
1006
|
-
return datetime.datetime
|
|
1007
|
-
elif v == cls.TYPES.INTERVAL:
|
|
1008
|
-
return datetime.timedelta
|
|
1009
|
-
elif v == cls.TYPES.DATETIME_TZ:
|
|
1010
|
-
return datetime.datetime
|
|
1011
|
-
elif v == cls.TYPES.INTERVAL_TZ:
|
|
1012
|
-
return datetime.timedelta
|
|
1013
|
-
else:
|
|
1014
|
-
raise Exception("unmapped type %s" % v)
|
|
1015
|
-
|
|
1016
|
-
@classmethod
|
|
1017
|
-
def massage_data(cls, data):
|
|
1018
|
-
"""
|
|
1019
|
-
|
|
1020
|
-
:param :
|
|
1021
|
-
:param :
|
|
1022
|
-
:param :
|
|
1023
|
-
:returns:
|
|
1024
|
-
"""
|
|
1025
|
-
data = {key.lower(): val for key, val in data.items()}
|
|
1026
|
-
primaryKey = set(cls.GetPrimaryKeyColumnNames())
|
|
1027
|
-
if not primaryKey:
|
|
1028
|
-
if not cls.Exists():
|
|
1029
|
-
raise DbTableMissingError
|
|
1030
|
-
dataKeys = set(data.keys()).intersection(primaryKey)
|
|
1031
|
-
dataColumns = set(data.keys()).difference(primaryKey)
|
|
1032
|
-
pk = {}
|
|
1033
|
-
pk.update([(k, data[k]) for k in dataKeys])
|
|
1034
|
-
d = {}
|
|
1035
|
-
d.update([(k, data[k]) for k in dataColumns])
|
|
1036
|
-
return d, pk
|
|
1037
|
-
|
|
1038
|
-
@classmethod
|
|
1039
|
-
def alter_add(cls, table, columns, null_allowed=True):
|
|
1040
|
-
sql = []
|
|
1041
|
-
null = "NOT NULL" if not null_allowed else ""
|
|
1042
|
-
if isinstance(columns, dict):
|
|
1043
|
-
for key, val in columns.items():
|
|
1044
|
-
key = re.sub("<>!=%", "", key.lower())
|
|
1045
|
-
sql.append(
|
|
1046
|
-
"ALTER TABLE {} ADD {} {} {};".format(
|
|
1047
|
-
quote(table), quote(key), cls.get_type(val), null
|
|
1048
|
-
)
|
|
1049
|
-
)
|
|
1050
|
-
return "\n\t".join(sql), tuple()
|
|
1051
|
-
|
|
1052
|
-
@classmethod
|
|
1053
|
-
def alter_drop(cls, table, columns):
|
|
1054
|
-
sql = ["ALTER TABLE {} DROP COLUMN".format(quote(table))]
|
|
1055
|
-
if isinstance(columns, dict):
|
|
1056
|
-
for key, val in columns.items():
|
|
1057
|
-
key = re.sub("<>!=%", "", key.lower())
|
|
1058
|
-
sql.append("{},".format(key))
|
|
1059
|
-
if sql[-1][-1] == ",":
|
|
1060
|
-
sql[-1] = sql[-1][:-1]
|
|
1061
|
-
return "\n\t".join(sql), tuple()
|
|
1062
|
-
|
|
1063
|
-
@classmethod
|
|
1064
|
-
def alter_column_by_type(cls, table, column, value, nullable=True):
|
|
1065
|
-
sql = ["ALTER TABLE {} ALTER COLUMN".format(quote(table))]
|
|
1066
|
-
sql.append("{} TYPE {}".format(quote(column), cls.get_type(value)))
|
|
1067
|
-
sql.append("USING {}::{}".format(quote(column), cls.get_conv(value)))
|
|
1068
|
-
if not nullable:
|
|
1069
|
-
sql.append("NOT NULL")
|
|
1070
|
-
return "\n\t".join(sql), tuple()
|
|
1071
|
-
|
|
1072
|
-
@classmethod
|
|
1073
|
-
def alter_column_by_sql(cls, table, column, value):
|
|
1074
|
-
sql = ["ALTER TABLE {} ALTER COLUMN".format(quote(table))]
|
|
1075
|
-
sql.append("{} {}".format(quote(column), value))
|
|
1076
|
-
return " ".join(sql), tuple()
|
|
1077
|
-
|
|
1078
|
-
@classmethod
|
|
1079
|
-
def rename_column(cls, table, orig, new):
|
|
1080
|
-
return (
|
|
1081
|
-
"ALTER TABLE {} RENAME COLUMN {} TO {};".format(
|
|
1082
|
-
quote(table), quote(orig), quote(new)
|
|
1083
|
-
),
|
|
1084
|
-
tuple(),
|
|
1085
|
-
)
|
|
1086
|
-
|
|
1087
|
-
@classmethod
|
|
1088
|
-
def rename_table(cls, table, new):
|
|
1089
|
-
return "ALTER TABLE {} RENAME TO {};".format(quote(table), quote(new)), tuple()
|
|
1090
|
-
|
|
1091
|
-
@classmethod
|
|
1092
|
-
def create_savepoint(cls, sp):
|
|
1093
|
-
return 'SAVEPOINT "{}"'.format(sp), tuple()
|
|
1094
|
-
|
|
1095
|
-
@classmethod
|
|
1096
|
-
def release_savepoint(cls, sp):
|
|
1097
|
-
return 'RELEASE SAVEPOINT "{}"'.format(sp), tuple()
|
|
1098
|
-
|
|
1099
|
-
@classmethod
|
|
1100
|
-
def rollback_savepoint(cls, sp):
|
|
1101
|
-
return 'ROLLBACK TO SAVEPOINT "{}"'.format(sp), tuple()
|
|
1102
|
-
|
|
1103
|
-
@classmethod
|
|
1104
|
-
def duplicate_rows(cls, table, columns, where={}):
|
|
1105
|
-
return cls.select(
|
|
1106
|
-
columns,
|
|
1107
|
-
table,
|
|
1108
|
-
where,
|
|
1109
|
-
orderby=columns,
|
|
1110
|
-
groupby=columns,
|
|
1111
|
-
having="count(*) > 2",
|
|
1112
|
-
)
|
|
1113
|
-
|
|
1114
|
-
@classmethod
|
|
1115
|
-
def delete(cls, table, where):
|
|
1116
|
-
sql = ["DELETE FROM {}".format(table)]
|
|
1117
|
-
vals = []
|
|
1118
|
-
make_where(where, sql, vals)
|
|
1119
|
-
return " ".join(sql), tuple(vals)
|
|
1120
|
-
|
|
1121
|
-
@classmethod
|
|
1122
|
-
def truncate(cls, table):
|
|
1123
|
-
return "truncate table {}".format(quote(table)), tuple()
|
|
1124
|
-
|
|
1125
|
-
@classmethod
|
|
1126
|
-
def create_view(cls, name, query, temp=False, silent=True):
|
|
1127
|
-
sql = ["CREATE"]
|
|
1128
|
-
if silent:
|
|
1129
|
-
sql.append("OR REPLACE")
|
|
1130
|
-
if temp:
|
|
1131
|
-
sql.append("TEMPORARY")
|
|
1132
|
-
sql.append("VIEW")
|
|
1133
|
-
sql.append(name)
|
|
1134
|
-
sql.append("AS")
|
|
1135
|
-
sql.append(query)
|
|
1136
|
-
return " ".join(sql), tuple()
|
|
1137
|
-
|
|
1138
|
-
@classmethod
|
|
1139
|
-
def drop_view(cls, name, silent=True):
|
|
1140
|
-
sql = ["DROP VIEW"]
|
|
1141
|
-
if silent:
|
|
1142
|
-
sql.append("IF EXISTS")
|
|
1143
|
-
sql.append(name)
|
|
1144
|
-
return " ".join(sql), tuple()
|
|
1145
|
-
|
|
1146
|
-
@classmethod
|
|
1147
|
-
def alter_trigger(cls, table, state="ENABLE", name="USER"):
|
|
1148
|
-
return "ALTER TABLE {} {} TRIGGER {}".format(table, state, name), tuple()
|
|
1149
|
-
|
|
1150
|
-
@classmethod
|
|
1151
|
-
def set_sequence(cls, table, next_value):
|
|
1152
|
-
return (
|
|
1153
|
-
"SELECT SETVAL(PG_GET_SERIAL_SEQUENCE('{0}', 'sys_id'),{1},FALSE)".format(
|
|
1154
|
-
table, next_value
|
|
1155
|
-
),
|
|
1156
|
-
tuple(),
|
|
1157
|
-
)
|
|
1158
|
-
|
|
1159
|
-
@classmethod
|
|
1160
|
-
def missing(cls, table, list, column="SYS_ID", where=None):
|
|
1161
|
-
sql = [
|
|
1162
|
-
"SELECT * FROM",
|
|
1163
|
-
f"UNNEST('{{{','.join([str(x) for x in list])}}}'::int[]) id",
|
|
1164
|
-
"EXCEPT ALL",
|
|
1165
|
-
f"SELECT {column} FROM {table}",
|
|
1166
|
-
]
|
|
1167
|
-
vals = []
|
|
1168
|
-
make_where(where, sql, vals)
|
|
1169
|
-
return " ".join(sql), tuple(vals)
|
|
1170
|
-
|
|
1171
|
-
class TYPES(object):
|
|
1172
|
-
TEXT = "TEXT"
|
|
1173
|
-
INTEGER = "INTEGER"
|
|
1174
|
-
NUMERIC = "NUMERIC"
|
|
1175
|
-
DATETIME_TZ = "TIMESTAMP WITH TIME ZONE"
|
|
1176
|
-
TIMESTAMP_TZ = "TIMESTAMP WITH TIME ZONE"
|
|
1177
|
-
DATETIME = "TIMESTAMP WITHOUT TIME ZONE"
|
|
1178
|
-
TIMESTAMP = "TIMESTAMP WITHOUT TIME ZONE"
|
|
1179
|
-
DATE = "DATE"
|
|
1180
|
-
TIME_TZ = "TIME WITH TIME ZONE"
|
|
1181
|
-
TIME = "TIME WITHOUT TIME ZONE"
|
|
1182
|
-
BIGINT = "BIGINT"
|
|
1183
|
-
SMALLINT = "SMALLINT"
|
|
1184
|
-
BOOLEAN = "BOOLEAN"
|
|
1185
|
-
BINARY = "BYTEA"
|
|
1186
|
-
INTERVAL = "INTERVAL"
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
reserved_words = [
|
|
1190
|
-
"ADMIN",
|
|
1191
|
-
"ALIAS",
|
|
1192
|
-
"ALL",
|
|
1193
|
-
"ALLOCATE",
|
|
1194
|
-
"ANALYSE",
|
|
1195
|
-
"ANALYZE",
|
|
1196
|
-
"AND",
|
|
1197
|
-
"ANY",
|
|
1198
|
-
"ARE",
|
|
1199
|
-
"ARRAY",
|
|
1200
|
-
"AS",
|
|
1201
|
-
"ASC",
|
|
1202
|
-
"AUTHORIZATION",
|
|
1203
|
-
"BETWEEN",
|
|
1204
|
-
"BINARY",
|
|
1205
|
-
"BLOB",
|
|
1206
|
-
"BOTH",
|
|
1207
|
-
"BREADTH",
|
|
1208
|
-
"CALL",
|
|
1209
|
-
"CASCADED",
|
|
1210
|
-
"CASE",
|
|
1211
|
-
"CAST",
|
|
1212
|
-
"CATALOG",
|
|
1213
|
-
"CHECK",
|
|
1214
|
-
"CLOB",
|
|
1215
|
-
"COLLATE",
|
|
1216
|
-
"COLLATION",
|
|
1217
|
-
"COLUMN",
|
|
1218
|
-
"COMPLETION",
|
|
1219
|
-
"CONNECT",
|
|
1220
|
-
"CONNECTION",
|
|
1221
|
-
"CONSTRAINT",
|
|
1222
|
-
"CONSTRUCTOR",
|
|
1223
|
-
"CONTINUE",
|
|
1224
|
-
"CORRESPONDING",
|
|
1225
|
-
"CREATE",
|
|
1226
|
-
"CROSS",
|
|
1227
|
-
"CUBE",
|
|
1228
|
-
"CURRENT",
|
|
1229
|
-
"CURRENT_DATE",
|
|
1230
|
-
"CURRENT_PATH",
|
|
1231
|
-
"CURRENT_ROLE",
|
|
1232
|
-
"CURRENT_TIME",
|
|
1233
|
-
"CURRENT_TIMESTAMP",
|
|
1234
|
-
"CURRENT_USER",
|
|
1235
|
-
"DATA",
|
|
1236
|
-
"DATE",
|
|
1237
|
-
"DEFAULT",
|
|
1238
|
-
"DEFERRABLE",
|
|
1239
|
-
"DEPTH",
|
|
1240
|
-
"DEREF",
|
|
1241
|
-
"DESC",
|
|
1242
|
-
"DESCRIBE",
|
|
1243
|
-
"DESCRIPTOR",
|
|
1244
|
-
"DESTROY",
|
|
1245
|
-
"DESTRUCTOR",
|
|
1246
|
-
"DETERMINISTIC",
|
|
1247
|
-
"DIAGNOSTICS",
|
|
1248
|
-
"DICTIONARY",
|
|
1249
|
-
"DISCONNECT",
|
|
1250
|
-
"DISTINCT",
|
|
1251
|
-
"DO",
|
|
1252
|
-
"DYNAMIC",
|
|
1253
|
-
"ELSE",
|
|
1254
|
-
"END",
|
|
1255
|
-
"END-EXEC",
|
|
1256
|
-
"EQUALS",
|
|
1257
|
-
"EVERY",
|
|
1258
|
-
"EXCEPT",
|
|
1259
|
-
"EXCEPTION",
|
|
1260
|
-
"EXEC",
|
|
1261
|
-
"FALSE",
|
|
1262
|
-
"FIRST",
|
|
1263
|
-
"FOR",
|
|
1264
|
-
"FOREIGN",
|
|
1265
|
-
"FOUND",
|
|
1266
|
-
"FREE",
|
|
1267
|
-
"FREEZE",
|
|
1268
|
-
"FROM",
|
|
1269
|
-
"FULL",
|
|
1270
|
-
"GENERAL",
|
|
1271
|
-
"GO",
|
|
1272
|
-
"GOTO",
|
|
1273
|
-
"GRANT",
|
|
1274
|
-
"GROUP",
|
|
1275
|
-
"GROUPING",
|
|
1276
|
-
"HAVING",
|
|
1277
|
-
"HOST",
|
|
1278
|
-
"IDENTITY",
|
|
1279
|
-
"IGNORE",
|
|
1280
|
-
"ILIKE",
|
|
1281
|
-
"IN",
|
|
1282
|
-
"INDICATOR",
|
|
1283
|
-
"INITIALIZE",
|
|
1284
|
-
"INITIALLY",
|
|
1285
|
-
"INNER",
|
|
1286
|
-
"INTERSECT",
|
|
1287
|
-
"INTO",
|
|
1288
|
-
"IS",
|
|
1289
|
-
"ISNULL",
|
|
1290
|
-
"ITERATE",
|
|
1291
|
-
"JOIN",
|
|
1292
|
-
"LARGE",
|
|
1293
|
-
"LAST",
|
|
1294
|
-
"LATERAL",
|
|
1295
|
-
"LEADING",
|
|
1296
|
-
"LEFT",
|
|
1297
|
-
"LESS",
|
|
1298
|
-
"LIKE",
|
|
1299
|
-
"LIMIT",
|
|
1300
|
-
"LOCALTIME",
|
|
1301
|
-
"LOCALTIMESTAMP",
|
|
1302
|
-
"LOCATOR",
|
|
1303
|
-
"MAP",
|
|
1304
|
-
"MODIFIES",
|
|
1305
|
-
"MODIFY",
|
|
1306
|
-
"MODULE",
|
|
1307
|
-
"NAME",
|
|
1308
|
-
"NATURAL",
|
|
1309
|
-
"NCLOB",
|
|
1310
|
-
"NEW",
|
|
1311
|
-
"NOT",
|
|
1312
|
-
"NOTNULL",
|
|
1313
|
-
"NULL",
|
|
1314
|
-
"OBJECT",
|
|
1315
|
-
"OFF",
|
|
1316
|
-
"OFFSET",
|
|
1317
|
-
"OLD",
|
|
1318
|
-
"ON",
|
|
1319
|
-
"ONLY",
|
|
1320
|
-
"OPEN",
|
|
1321
|
-
"OPERATION",
|
|
1322
|
-
"OR",
|
|
1323
|
-
"ORDER",
|
|
1324
|
-
"ORDINALITY",
|
|
1325
|
-
"OUTER",
|
|
1326
|
-
"OUTPUT",
|
|
1327
|
-
"OVERLAPS",
|
|
1328
|
-
"PAD",
|
|
1329
|
-
"PARAMETER",
|
|
1330
|
-
"PARAMETERS",
|
|
1331
|
-
"PLACING",
|
|
1332
|
-
"POSTFIX",
|
|
1333
|
-
"PREFIX",
|
|
1334
|
-
"PREORDER",
|
|
1335
|
-
"PRESERVE",
|
|
1336
|
-
"PRIMARY",
|
|
1337
|
-
"PUBLIC",
|
|
1338
|
-
"READS",
|
|
1339
|
-
"RECURSIVE",
|
|
1340
|
-
"REF",
|
|
1341
|
-
"REFERENCES",
|
|
1342
|
-
"REFERENCING",
|
|
1343
|
-
"RESULT",
|
|
1344
|
-
"RETURN",
|
|
1345
|
-
"RIGHT",
|
|
1346
|
-
"ROLE",
|
|
1347
|
-
"ROLLUP",
|
|
1348
|
-
"ROUTINE",
|
|
1349
|
-
"ROWS",
|
|
1350
|
-
"SAVEPOINT",
|
|
1351
|
-
"SCOPE",
|
|
1352
|
-
"SEARCH",
|
|
1353
|
-
"SECTION",
|
|
1354
|
-
"SELECT",
|
|
1355
|
-
"SESSION_USER",
|
|
1356
|
-
"SETS",
|
|
1357
|
-
"SIMILAR",
|
|
1358
|
-
"SIZE",
|
|
1359
|
-
"SOME",
|
|
1360
|
-
"SPACE",
|
|
1361
|
-
"SPECIFIC",
|
|
1362
|
-
"SPECIFICTYPE",
|
|
1363
|
-
"SQL",
|
|
1364
|
-
"SQLCODE",
|
|
1365
|
-
"SQLERROR",
|
|
1366
|
-
"SQLEXCEPTION",
|
|
1367
|
-
"SQLSTATE",
|
|
1368
|
-
"SQLWARNING",
|
|
1369
|
-
"STATE",
|
|
1370
|
-
"STATIC",
|
|
1371
|
-
"STRUCTURE",
|
|
1372
|
-
"SYSTEM_USER",
|
|
1373
|
-
"TABLE",
|
|
1374
|
-
"TERMINATE",
|
|
1375
|
-
"THAN",
|
|
1376
|
-
"THEN",
|
|
1377
|
-
"TIMESTAMP",
|
|
1378
|
-
"TIMEZONE_HOUR",
|
|
1379
|
-
"TIMEZONE_MINUTE",
|
|
1380
|
-
"TO",
|
|
1381
|
-
"TRAILING",
|
|
1382
|
-
"TRANSLATION",
|
|
1383
|
-
"TRUE",
|
|
1384
|
-
"UNDER",
|
|
1385
|
-
"UNION",
|
|
1386
|
-
"UNIQUE",
|
|
1387
|
-
"UNNEST",
|
|
1388
|
-
"USER",
|
|
1389
|
-
"USING",
|
|
1390
|
-
"VALUE",
|
|
1391
|
-
"VARIABLE",
|
|
1392
|
-
"VERBOSE",
|
|
1393
|
-
"WHEN",
|
|
1394
|
-
"WHENEVER",
|
|
1395
|
-
"WHERE",
|
|
1396
|
-
]
|