velocity-python 0.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of velocity-python might be problematic. Click here for more details.

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