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,899 @@
1
+ import re
2
+ import hashlib
3
+ import decimal
4
+ import datetime
5
+
6
+ from velocity.db import exceptions
7
+
8
+ def initialize(config):
9
+ import sqlite3
10
+ from velocity.db.core.engine import Engine
11
+ return Engine(sqlite3, config, SQL)
12
+
13
+ def quote(data):
14
+ if isinstance(data, list):
15
+ new = []
16
+ for item in data:
17
+ new.append(quote(item))
18
+ return new
19
+ else:
20
+ parts = data.split('.')
21
+ new = []
22
+ for part in parts:
23
+ if '"' in part:
24
+ new.append(part)
25
+ elif part.upper() in reserved_words:
26
+ new.append('"'+part+'"')
27
+ elif re.findall('[/]',part):
28
+ new.append('"'+part+'"')
29
+ else:
30
+ new.append(part)
31
+ return '.'.join(new)
32
+
33
+ class SQL(object):
34
+ server = "SQLite3"
35
+ type_column_identifier = 'data_type'
36
+ is_nullable = 'is_nullable'
37
+
38
+ default_schema = ''
39
+
40
+ ApplicationErrorCodes = []
41
+
42
+ DatabaseMissingErrorCodes = []
43
+ TableMissingErrorCodes = []
44
+ ColumnMissingErrorCodes = []
45
+ ForeignKeyMissingErrorCodes =[]
46
+
47
+ ConnectionErrorCodes = []
48
+ DuplicateKeyErrorCodes = []
49
+ RetryTransactionCodes = []
50
+ TruncationErrorCodes = []
51
+ LockTimeoutErrorCodes = []
52
+ DatabaseObjectExistsErrorCodes = []
53
+
54
+ @classmethod
55
+ def version(cls):
56
+ return "select version()", tuple()
57
+
58
+ @classmethod
59
+ def timestamp(cls):
60
+ return "select current_timestamp", tuple()
61
+
62
+ @classmethod
63
+ def user(cls):
64
+ return "select current_user", tuple()
65
+
66
+ @classmethod
67
+ def databases(cls):
68
+ return "select datname from pg_database where datistemplate = false", tuple()
69
+
70
+ @classmethod
71
+ def schemas(cls):
72
+ return 'select schema_name from information_schema.schemata', tuple()
73
+
74
+ @classmethod
75
+ def current_schema(cls):
76
+ return 'select current_schema', tuple()
77
+
78
+ @classmethod
79
+ def current_database(cls):
80
+ return 'select current_database()', tuple()
81
+
82
+ @classmethod
83
+ def tables(cls, system=False):
84
+ return "SELECT name FROM sqlite_master WHERE type='table';", tuple()
85
+
86
+ @classmethod
87
+ def views(cls, system=False):
88
+ if system:
89
+ return 'SELECT name FROM sqlite_master WHERE type="view";', tuple()
90
+ else:
91
+ return 'SELECT name FROM sqlite_master WHERE type="view";', tuple()
92
+
93
+ @classmethod
94
+ def __has_pointer(cls,columns):
95
+ if columns:
96
+ if isinstance(columns,list):
97
+ columns = ','.join(columns)
98
+ if '>' in columns:
99
+ return True
100
+ return False
101
+
102
+ @classmethod
103
+ def select(cls,columns=None,table=None,where=None,orderby=None,groupby=None,having=None,start=None,qty=None,tbl=None):
104
+ is_join = False
105
+
106
+ if isinstance(columns,str)\
107
+ and 'distinct' in columns.lower():
108
+ sql = [
109
+ 'SELECT',
110
+ columns,
111
+ 'FROM',
112
+ quote(table),
113
+ ]
114
+ elif cls.__has_pointer(columns):
115
+ if isinstance(columns,str):
116
+ columns = columns.split(',')
117
+ letter = 65
118
+ tables = {table: chr(letter)}
119
+ letter += 1
120
+ __select = []
121
+ __from = ['{} AS {}'.format(quote(table),tables.get(table))]
122
+ __left_join = []
123
+
124
+ for column in columns:
125
+ if '>' in column:
126
+ is_join = True
127
+ parts = column.split('>')
128
+ foreign = tbl.foreign_key_info(parts[0])
129
+ if not foreign:
130
+ raise exceptions.DbApplicationError("Foreign key not defined")
131
+ ref_table = foreign['referenced_table_name']
132
+ ref_schema = foreign['referenced_table_schema']
133
+ ref_column = foreign['referenced_column_name']
134
+ lookup = "{}:{}".format(ref_table,parts[0])
135
+ if lookup in tables:
136
+ __select.append('{}."{}" as "{}"'.format(tables.get(lookup),parts[1],'_'.join(parts)))
137
+ else:
138
+ tables[lookup] = chr(letter)
139
+ letter += 1
140
+ __select.append('{}."{}" as "{}"'.format(tables.get(lookup),parts[1],'_'.join(parts)))
141
+ __left_join.append('LEFT OUTER JOIN "{}"."{}" AS {}'.format(ref_schema,ref_table,tables.get(lookup)))
142
+ __left_join.append('ON {}."{}" = {}."{}"'.format(
143
+ tables.get(table),
144
+ parts[0],
145
+ tables.get(lookup),
146
+ ref_column
147
+ ))
148
+ if orderby and column in orderby:
149
+ orderby = orderby.replace(column,"{}.{}".format(tables.get(lookup),parts[1]))
150
+ else:
151
+ if '(' in column:
152
+ __select.append(column)
153
+ else:
154
+ __select.append("{}.{}".format(tables.get(table),column))
155
+ sql = ['SELECT']
156
+ sql.append(','.join(__select))
157
+ sql.append('FROM')
158
+ sql.extend(__from)
159
+ sql.extend(__left_join)
160
+ else:
161
+ if columns:
162
+ if isinstance(columns,str):
163
+ columns = columns.split(',')
164
+ if isinstance(columns,list):
165
+ columns = quote(columns)
166
+ columns = ','.join(columns)
167
+ else:
168
+ columns = '*'
169
+ sql = [
170
+ 'SELECT',
171
+ columns,
172
+ 'FROM',
173
+ quote(table),
174
+ ]
175
+ vals = []
176
+ if where:
177
+ sql.append('WHERE')
178
+ if isinstance(where,dict):
179
+ where = [x for x in where.items()]
180
+ if isinstance(where,list):
181
+ join = ''
182
+ for key,val in where:
183
+ if join: sql.append(join)
184
+ if is_join:
185
+ if '.' not in key:
186
+ key = 'A.' + key
187
+ if val == None:
188
+ if '!' in key:
189
+ key = key.replace('!','')
190
+ sql.append('{} is not NULL'.format(quote(key.lower())))
191
+ else:
192
+ sql.append('{} is NULL'.format(quote(key.lower())))
193
+ elif isinstance(val,(list,tuple)):
194
+ if '!' in key:
195
+ key = key.replace('!','')
196
+ sql.append('{} not in ?'.format(quote(key.lower())))
197
+ vals.append(tuple(val))
198
+ else:
199
+ sql.append('{} in ?'.format(quote(key.lower())))
200
+ vals.append(tuple(val))
201
+ else:
202
+ if '<>' in key:
203
+ key = key.replace('<>','')
204
+ op = '<>'
205
+ elif '!=' in key:
206
+ key = key.replace('!=','')
207
+ op = '<>'
208
+ elif '!%' in key:
209
+ key = key.replace('!%','')
210
+ op = 'not ilike'
211
+ elif '%%' in key:
212
+ key = key.replace('%%','')
213
+ op = '%'
214
+ elif '%>' in key:
215
+ key = key.replace('%>','')
216
+ op = '%>'
217
+ elif '<%' in key:
218
+ key = key.replace('<%','')
219
+ op = '<%'
220
+ elif '==' in key:
221
+ key = key.replace('==','')
222
+ op = '='
223
+ elif '<=' in key:
224
+ key = key.replace('<=','')
225
+ op = '<='
226
+ elif '>=' in key:
227
+ key = key.replace('>=','')
228
+ op = '>='
229
+ elif '<' in key:
230
+ key = key.replace('<','')
231
+ op = '<'
232
+ elif '>' in key:
233
+ key = key.replace('>','')
234
+ op = '>'
235
+ elif '%' in key:
236
+ key = key.replace('%','')
237
+ op = 'ilike'
238
+ elif '!' in key:
239
+ key = key.replace('!','')
240
+ op = '<>'
241
+ elif '=' in key:
242
+ key = key.replace('=','')
243
+ op = '='
244
+ else:
245
+ op = '='
246
+ if isinstance(val,str) and val[:2] == '@@':
247
+ sql.append('{} {} {}'.format(quote(key.lower()), op, val[2:]))
248
+ else:
249
+ sql.append('{} {} ?'.format(quote(key.lower()), op))
250
+ vals.append(val)
251
+ join = 'AND'
252
+ else:
253
+ sql.append(where)
254
+ if groupby:
255
+ sql.append('GROUP BY')
256
+ if isinstance(groupby,(list,tuple)):
257
+ groupby = ','.join(groupby)
258
+ sql.append(groupby)
259
+ if having:
260
+ sql.append('HAVING')
261
+ if isinstance(having,(list,tuple)):
262
+ having = ','.join(having)
263
+ sql.append(having)
264
+ if orderby:
265
+ sql.append('ORDER BY')
266
+ if isinstance(orderby,(list,tuple)):
267
+ orderby = ','.join(orderby)
268
+ sql.append(orderby)
269
+ if start and qty:
270
+ sql.append('OFFSET {} ROWS FETCH NEXT {} ROWS ONLY'.format(start,qty))
271
+ elif start:
272
+ sql.append('OFFSET {} ROWS'.format(start))
273
+ elif qty:
274
+ sql.append('FETCH NEXT {} ROWS ONLY'.format(qty))
275
+ sql = ' '.join(sql)
276
+ return sql, tuple(vals)
277
+
278
+ @classmethod
279
+ def create_database(cls, name):
280
+ return 'create database ' + name, tuple()
281
+
282
+ @classmethod
283
+ def last_id(cls, table):
284
+ return "SELECT last_insert_rowid()", tuple()
285
+
286
+ @classmethod
287
+ def drop_database(cls, name):
288
+ return 'drop database if exists ' + name, tuple()
289
+
290
+ @classmethod
291
+ def create_table(cls, name, columns={}, drop=False):
292
+ sql = []
293
+ if drop:
294
+ sql.append(cls.drop_table(name))
295
+ sql.append("""
296
+ CREATE TABLE {0} (
297
+ sys_id INTEGER PRIMARY KEY,
298
+ sys_modified TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
299
+ sys_created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
300
+ """.format(name))
301
+
302
+ for key,val in columns.items():
303
+ sql.append(",\n{} {}".format(quote(key),cls.get_type(val)))
304
+
305
+ sql.append("\n);")
306
+ return '\n\t'.join(sql), tuple()
307
+
308
+ @classmethod
309
+ def drop_table(cls, name):
310
+ return "drop table if exists {}".format(quote(name)), tuple()
311
+
312
+ @classmethod
313
+ def columns(cls, name):
314
+ return "PRAGMA table_info({})".format(name), tuple()
315
+
316
+ @classmethod
317
+ def column_info(cls, table, name):
318
+ params = table.split('.')
319
+ params.append(name)
320
+ if '.' in table:
321
+ return """
322
+ select *
323
+ from information_schema.columns
324
+ where table_schema = ?
325
+ and table_name = ?
326
+ and column_name = ?
327
+ """, tuple(params)
328
+ else:
329
+ return """
330
+ select *
331
+ from information_schema.columns
332
+ where table_name = ?
333
+ and column_name = ?
334
+ """, tuple(params)
335
+
336
+
337
+ @classmethod
338
+ def primary_keys(cls, table):
339
+ params = table.split('.')
340
+ params.reverse()
341
+ if '.' in table:
342
+ return """
343
+ SELECT
344
+ pg_attribute.attname
345
+ FROM pg_index, pg_class, pg_attribute, pg_namespace
346
+ WHERE
347
+ pg_class.oid = %s::regclass AND
348
+ indrelid = pg_class.oid AND
349
+ nspname = %s AND
350
+ pg_class.relnamespace = pg_namespace.oid AND
351
+ pg_attribute.attrelid = pg_class.oid AND
352
+ pg_attribute.attnum = any(pg_index.indkey)
353
+ AND indisprimary
354
+ """, tuple(params)
355
+ else:
356
+ return """
357
+ SELECT
358
+ pg_attribute.attname
359
+ FROM pg_index, pg_class, pg_attribute, pg_namespace
360
+ WHERE
361
+ pg_class.oid = %s::regclass AND
362
+ indrelid = pg_class.oid AND
363
+ pg_class.relnamespace = pg_namespace.oid AND
364
+ pg_attribute.attrelid = pg_class.oid AND
365
+ pg_attribute.attnum = any(pg_index.indkey)
366
+ AND indisprimary
367
+ """, tuple(params)
368
+
369
+ @classmethod
370
+ def foreign_key_info(cls,table=None,column=None,schema=None):
371
+ if '.' in table:
372
+ schema, table = table.split('.')
373
+
374
+ sql = """
375
+ SELECT sql
376
+ FROM (
377
+ SELECT
378
+ sql sql,
379
+ type type,
380
+ tbl_name AS referenced_table_name,
381
+ name AS referenced_column_name,
382
+ NULL AS referenced_table_schema
383
+ FROM sqlite_master
384
+ UNION ALL
385
+ SELECT
386
+ sql,
387
+ type,
388
+ referenced_table_name,
389
+ referenced_column_name,
390
+ referenced_table_schema
391
+ FROM sqlite_temp_master
392
+ )
393
+ WHERE type != 'meta'
394
+ AND sql NOTNULL
395
+ AND name NOT LIKE 'sqlite_%'
396
+ ORDER BY substr(type, 2, 1), name
397
+ """
398
+
399
+ return sql, tuple()
400
+
401
+ @classmethod
402
+ def create_index(cls, table=None, columns=None, unique=False, direction=None, name=None, schema=None, tbl=None):
403
+ if '.' not in table and schema:
404
+ table = "{}.{}".format(schema,table)
405
+ if isinstance(columns,(list,set)):
406
+ columns = ','.join([quote(c.lower()) for c in sorted(columns)])
407
+ else:
408
+ columns = quote(columns)
409
+ sql = ['CREATE']
410
+ if unique:
411
+ sql.append('UNIQUE')
412
+ sql.append('INDEX')
413
+ tablename = quote(table)
414
+ if not name:
415
+ name = re.sub(r'\([^)]*\)', '', columns.replace(',','_'))
416
+ sql.append('IDX__{}__{}'.format(table.replace('.','_'),name))
417
+ sql.append('ON')
418
+ sql.append(tablename)
419
+ sql.append('(')
420
+ sql.append(columns)
421
+ sql.append(')')
422
+ return ' '.join(sql),tuple()
423
+
424
+ # Copied from PostGreSQL
425
+ @classmethod
426
+ def create_foreign_key(cls, table, columns, key_to_table, key_to_columns, name=None, schema=None):
427
+ if not name:
428
+ m = hashlib.md5()
429
+ m.update(table.name)
430
+ m.update(' '.join(columns))
431
+ m.update(key_to_table)
432
+ m.update(' ' .join(key_to_columns))
433
+ name = 'FK_' + m.hexdigest()
434
+
435
+ original_name = table.name
436
+
437
+ # Get SQL query to generate table
438
+ sql = table.tx.table('sqlite_master').select(columns='sql',where={"name":table.name,'type':'table'}).scalar()
439
+
440
+ # Rename original table
441
+ table.rename(table.name+"_original_data")
442
+
443
+ key_info = ""
444
+ if isinstance(columns,list) and isinstance(key_to_columns,list):
445
+ for c in columns:
446
+ key_info += """,
447
+ CONSTRAINT {}
448
+ FOREIGN KEY ({})
449
+ REFERENCES {}({})
450
+ """.format(name,c,key_to_table,key_to_columns[columns.index(c)])
451
+ elif isinstance(columns,str) and isinstance(key_to_columns,str):
452
+ key_info += """,
453
+ CONSTRAINT {}
454
+ FOREIGN KEY ({})
455
+ REFERENCES {}({})
456
+ """.format(name,columns,key_to_table,key_to_columns)
457
+ else:
458
+ print("Error parsing argument \"columns\" or \"key_to_columns\"")
459
+
460
+ # Splits "CREATE TABLE" portion out to be readded to lines at the end
461
+ sql_data = sql.split("(",1)
462
+
463
+ # Goes through the SQL code to generate table and adds foreign key info
464
+ sql_list = sql_data[1].replace("\n"," ").split(",")
465
+ new_sql_list = []
466
+ for line in sql_list:
467
+ line = line.strip().lower()
468
+ for line in sql_list:
469
+ if sql_list.index(line) == len(sql_list)-1:
470
+ if ")" in line:
471
+ if line.index(")") == len(line)-1:
472
+ new_sql_list.append(line.replace(")",(key_info+")")))
473
+ else:
474
+ new_sql_list.append(line)
475
+
476
+ # Enable changes to be made to foreign keys
477
+ table.tx.execute("PRAGMA foreign_keys=off;")
478
+
479
+ # Add sql code to recreate original table with foreign keys
480
+ create_db = ','.join(new_sql_list)
481
+ # Adds "CREATE TABLE" portion back into sql code for execution
482
+ create_db = '('.join([sql_data[0],create_db])
483
+ table.tx.execute(create_db)
484
+
485
+ # Create new table with original table name and copy all data from original table
486
+ table.tx.execute("INSERT INTO {} SELECT * FROM {};".format(original_name,table.name))
487
+ # Enable foreign keys
488
+ create_db = "PRAGMA foreign_keys=on;"
489
+
490
+ return create_db, tuple()
491
+
492
+ @classmethod
493
+ def drop_index(cls, table=None, columns=None, name=None, schema=None):
494
+ if '.' not in table and schema:
495
+ table = "{}.{}".format(schema,table)
496
+ if isinstance(columns,(list,set)):
497
+ columns = ','.join([quote(c.lower()) for c in sorted(columns)])
498
+ else:
499
+ columns = quote(columns)
500
+ sql = ['DROP']
501
+ sql.append('INDEX IF EXISTS')
502
+ tablename = quote(table)
503
+ if not name:
504
+ name = re.sub(r'\([^)]*\)', '', columns.replace(',','_'))
505
+ sql.append('IDX__{}__{}'.format(table.replace('.','_'),name))
506
+ return ' '.join(sql),tuple()
507
+
508
+ @classmethod
509
+ def insert(cls, table, data):
510
+ keys = []
511
+ vals = []
512
+ args = []
513
+ for key,val in data.items():
514
+ keys.append(quote(key.lower()))
515
+ if isinstance(val,str) \
516
+ and len(val) > 2 \
517
+ and val[:2] == '@@':
518
+ vals.append(val[2:])
519
+ elif isinstance(val,bytearray):
520
+ vals.append('?')
521
+ args.append(bytes(val))
522
+ else:
523
+ vals.append('?')
524
+ args.append(val)
525
+
526
+ sql = ['INSERT INTO']
527
+ sql.append(quote(table))
528
+ sql.append('(')
529
+ sql.append(','.join(keys))
530
+ sql.append(')')
531
+ sql.append('VALUES')
532
+ sql.append('(')
533
+ sql.append(','.join(vals))
534
+ sql.append(')')
535
+ sql = ' '.join(sql)
536
+ return sql, tuple(args)
537
+
538
+ @classmethod
539
+ def update(cls, table, data, pk):
540
+ sql = ['UPDATE']
541
+ sql.append(quote(table))
542
+ sql.append('SET')
543
+ vals = []
544
+ join = ''
545
+ for key in data.keys():
546
+ val = data[key]
547
+ if join:
548
+ sql.append(join)
549
+ if isinstance(val,str) and val[:2] == '@@':
550
+ sql.append("{} = {}".format(quote(key.lower()),val[2:]))
551
+ elif isinstance(val, bytearray):
552
+ sql.append('{} = ?'.format(quote(key.lower())))
553
+ vals.append(bytes(val))
554
+ else:
555
+ sql.append('{} = ?'.format(quote(key.lower())))
556
+ vals.append(val)
557
+ join = ','
558
+ if pk:
559
+ if isinstance(pk, list):
560
+ items = pk
561
+ elif isinstance(pk, dict):
562
+ items = pk.items()
563
+ sql.append('\nWHERE')
564
+ join = ''
565
+ for key,val in items:
566
+ if join:
567
+ sql.append(join)
568
+ if val is None:
569
+ if '!' in key:
570
+ key = key.replace('!','')
571
+ sql.append('{} is not NULL'.format(quote(key.lower())))
572
+ else:
573
+ sql.append('{} is NULL'.format(quote(key.lower())))
574
+ elif isinstance(val,(tuple,list)):
575
+ if '!' in key:
576
+ key = key.replace('!','')
577
+ sql.append('{} not in ?'.format(quote(key.lower())))
578
+ vals.append(tuple(val))
579
+ else:
580
+ sql.append('{} in ?'.format(quote(key.lower())))
581
+ vals.append(tuple(val))
582
+ else:
583
+ if '<>' in key:
584
+ key = key.replace('<>','')
585
+ op = '<>'
586
+ elif '!=' in key:
587
+ key = key.replace('!=','')
588
+ op = '<>'
589
+ elif '!%' in key:
590
+ key = key.replace('!%','')
591
+ op = 'not ilike'
592
+ elif '%%' in key:
593
+ key = key.replace('%%','')
594
+ op = '%'
595
+ elif '%>' in key:
596
+ key = key.replace('%>','')
597
+ op = '%>'
598
+ elif '<%' in key:
599
+ key = key.replace('<%','')
600
+ op = '<%'
601
+ elif '==' in key:
602
+ key = key.replace('==','')
603
+ op = '='
604
+ elif '<=' in key:
605
+ key = key.replace('<=','')
606
+ op = '<='
607
+ elif '>=' in key:
608
+ key = key.replace('>=','')
609
+ op = '>='
610
+ elif '<' in key:
611
+ key = key.replace('<','')
612
+ op = '<'
613
+ elif '>' in key:
614
+ key = key.replace('>','')
615
+ op = '>'
616
+ elif '%' in key:
617
+ key = key.replace('%','')
618
+ op = 'ilike'
619
+ elif '!' in key:
620
+ key = key.replace('!','')
621
+ op = '<>'
622
+ elif '=' in key:
623
+ key = key.replace('=','')
624
+ op = '='
625
+ else:
626
+ op = '='
627
+ if isinstance(val,str) and val[:2] == '@@':
628
+ sql.append('{} {} {}'.format(quote(key.lower()), op, val[2:]))
629
+ else:
630
+ sql.append('{} {} ?'.format(quote(key.lower()), op))
631
+ vals.append(val)
632
+ join = 'AND'
633
+ sql = ' '.join(sql)
634
+ return sql, tuple(vals)
635
+
636
+ @classmethod
637
+ def get_type(cls, v):
638
+ if isinstance(v, str):
639
+ if v[:2] == '@@':
640
+ return v[2:] or cls.TYPES.TEXT
641
+ elif isinstance(v, (str, bytes)) \
642
+ or v is str \
643
+ or v is bytes:
644
+ return cls.TYPES.TEXT
645
+ elif isinstance(v, bool) \
646
+ or v is bool:
647
+ return cls.TYPES.BOOLEAN
648
+ elif isinstance(v, int) \
649
+ or v is int:
650
+ if v is int:
651
+ return cls.TYPES.INTEGER
652
+ if v > 2147483647 or v < -2147483648:
653
+ return cls.TYPES.BIGINT
654
+ else:
655
+ return cls.TYPES.INTEGER
656
+ elif isinstance(v, float) \
657
+ or v is float:
658
+ return cls.TYPES.NUMERIC + '(19, 6)'
659
+ elif isinstance(v, decimal.Decimal) \
660
+ or v is decimal.Decimal:
661
+ return cls.TYPES.NUMERIC + '(19, 6)'
662
+ elif isinstance (v, datetime.datetime) \
663
+ or v is datetime.datetime:
664
+ return cls.TYPES.DATETIME
665
+ elif isinstance (v, datetime.date) \
666
+ or v is datetime.date:
667
+ return cls.TYPES.DATE
668
+ elif isinstance(v, datetime.time) \
669
+ or v is datetime.time:
670
+ return cls.TYPES.TIME
671
+ elif isinstance(v, datetime.timedelta) \
672
+ or v is datetime.timedelta:
673
+ return cls.TYPES.INTERVAL
674
+ elif isinstance (v, bytearray) \
675
+ or v is bytearray:
676
+ return cls.TYPES.BINARY
677
+ # Everything else defaults to TEXT, incl. None
678
+ return cls.TYPES.TEXT
679
+
680
+ @classmethod
681
+ def py_type(cls, v):
682
+ v = str(v).upper()
683
+ if v == cls.TYPES.INTEGER:
684
+ return int
685
+ elif v == cls.TYPES.BIGINT:
686
+ return int
687
+ elif v == cls.TYPES.NUMERIC:
688
+ return decimal.Decimal
689
+ elif v == cls.TYPES.TEXT:
690
+ return str
691
+ elif v == cls.TYPES.BOOLEAN:
692
+ return bool
693
+ elif v == cls.TYPES.DATE:
694
+ return datetime.date
695
+ elif v == cls.TYPES.TIME:
696
+ return datetime.time
697
+ elif v == cls.TYPES.DATETIME:
698
+ return datetime.datetime
699
+ elif v == cls.TYPES.INTERVAL:
700
+ return datetime.timedelta
701
+ else:
702
+ raise Exception("unmapped type %s" % v)
703
+
704
+ @classmethod
705
+ def massage_data(cls, data):
706
+ """
707
+
708
+ :param :
709
+ :param :
710
+ :param :
711
+ :returns:
712
+ """
713
+ data = {key.lower():val for key,val in data.items()}
714
+ primaryKey = set(cls.GetPrimaryKeyColumnNames())
715
+ if not primaryKey:
716
+ if not cls.Exists():
717
+ raise exceptions.DbTableMissingError
718
+ dataKeys = set(data.keys()).intersection( primaryKey )
719
+ dataColumns = set(data.keys()).difference( primaryKey )
720
+ pk = {}
721
+ pk.update([(k,data[k]) for k in dataKeys])
722
+ d = {}
723
+ d.update([(k,data[k]) for k in dataColumns])
724
+ return d,pk
725
+
726
+ @classmethod
727
+ def alter_add(cls, table, columns, null_allowed=True):
728
+ sql = []
729
+ null = 'NOT NULL' if not null_allowed else ''
730
+ if isinstance(columns,dict):
731
+ for key,val in columns.items():
732
+ sql.append("ALTER TABLE {} ADD {} {} {};".format(quote(table), quote(key), cls.get_type(val), null))
733
+ return '\n\t'.join(sql), tuple()
734
+
735
+ @classmethod
736
+ def alter_drop(cls, table, columns):
737
+ sql = ["ALTER TABLE {} DROP COLUMN".format(quote(table))]
738
+ if isinstance(columns,dict):
739
+ for key,val in columns.items():
740
+ sql.append("{},".format(key))
741
+ if sql[-1][-1] == ',':
742
+ sql[-1] = sql[-1][:-1]
743
+ return '\n\t'.join(sql), tuple()
744
+
745
+ @classmethod
746
+ def alter_column_by_type(cls, table, column, value, null_allowed=True):
747
+ sql = ["ALTER TABLE {} ALTER COLUMN".format(quote(table))]
748
+ sql.append("{} {}".format(quote(column), cls.get_type(value)))
749
+ if not null_allowed:
750
+ sql.append('NOT NULL')
751
+ return '\n\t'.join(sql), tuple()
752
+
753
+ @classmethod
754
+ def alter_column_by_sql(cls, table, column, value):
755
+ sql = ["ALTER TABLE {} ALTER COLUMN".format(quote(table))]
756
+ sql.append("{} {}".format(quote(column), value))
757
+ return ' '.join(sql), tuple()
758
+
759
+ # SQLite3 does not support renaming columns, in order to do so the table must be copied to a version with the new column's name
760
+ @classmethod
761
+ def rename_column(cls, table, orig, new):
762
+ # Solves case parity errors
763
+ orig = orig.lower()
764
+ new = new.lower()
765
+ # Get SQL query to generate table
766
+ sql = table.tx.table('sqlite_master').select(columns='sql',where={"name":table.name,'type':'table'}).scalar()
767
+ original_name = table.name
768
+
769
+ # Splits "CREATE TABLE" portion out to be readded to lines at the end
770
+ sql_data = sql.split("(",1)
771
+
772
+ sql_list = sql_data[1].replace("\n"," ").split(",")
773
+ new_sql_list = []
774
+ for line in sql_list:
775
+ line = line.strip().lower()
776
+ if orig in line:
777
+ if line.index(orig) == 0:
778
+ new_sql_list.append(line.replace(orig,new,1))
779
+ elif (line[0] == "\"" or line[0] == "\'") and line.index(orig) == 1:
780
+ new_sql_list.append(line.replace(orig,new,1))
781
+ else:
782
+ new_sql_list.append(line)
783
+ else:
784
+ new_sql_list.append(line)
785
+
786
+ create_db = ','.join(new_sql_list)
787
+
788
+ # Adds "CREATE TABLE" portion back into sql code for execution
789
+ create_db = '('.join([sql_data[0],create_db])
790
+ create_db += ";"
791
+
792
+ # Rename original table
793
+ table.rename(table.name+"_original_data")
794
+
795
+ table.tx.execute(create_db);
796
+ # Create new table with original table name and copy all data from original table
797
+ create_db = "INSERT INTO {} SELECT * FROM {};".format(original_name,table.name)
798
+ return create_db, tuple()
799
+
800
+
801
+ @classmethod
802
+ def rename_table(cls, table, new):
803
+ return "ALTER TABLE {} RENAME TO {};".format(quote(table), quote(new)), tuple()
804
+
805
+ @classmethod
806
+ def create_savepoint(cls, sp):
807
+ return 'SAVEPOINT {};'.format(sp), tuple()
808
+
809
+ @classmethod
810
+ def release_savepoint(cls, sp):
811
+ return 'RELEASE SAVEPOINT {};'.format(sp), tuple()
812
+
813
+ @classmethod
814
+ def rollback_savepoint(cls, sp):
815
+ return 'ROLLBACK TO SAVEPOINT {};'.format(sp), tuple()
816
+
817
+ @classmethod
818
+ def find_duplicates(cls, table, columns, key):
819
+ if isinstance(columns, str):
820
+ columns = [columns]
821
+ return """
822
+ SELECT {2}
823
+ FROM (SELECT {2},
824
+ ROW_NUMBER() OVER (partition BY {1} ORDER BY {2}) AS rnum
825
+ FROM {0}) t
826
+ WHERE t.rnum > 1;
827
+ """.format(table, ','.join(quote(columns)), key), tuple()
828
+
829
+ @classmethod
830
+ def delete_duplicates(cls, table, columns, key):
831
+ if isinstance(columns, str):
832
+ columns = [columns]
833
+ return """
834
+ DELETE FROM {0}
835
+ WHERE {2} IN (SELECT {2}
836
+ FROM (SELECT {2},
837
+ ROW_NUMBER() OVER (partition BY {1} ORDER BY {2}) AS rnum
838
+ FROM {0}) t
839
+ WHERE t.rnum > 1);
840
+ """.format(table, ','.join(quote(columns)), key), tuple()
841
+
842
+ @classmethod
843
+ def delete(cls, table, where):
844
+ sql = ['DELETE FROM {}'.format(table)]
845
+ sql.append('WHERE')
846
+ vals = []
847
+ if isinstance(where,dict):
848
+ join = ''
849
+ for key in sorted(where.keys()):
850
+ if join: sql.append(join)
851
+ if where[key] == None:
852
+ sql.append('{} is NULL'.format(quote(key.lower())))
853
+ else:
854
+ sql.append('{} = ?'.format(quote(key.lower())))
855
+ vals.append(where[key])
856
+ join = 'AND'
857
+ else:
858
+ sql.append(where)
859
+ return ' '.join(sql), tuple(vals)
860
+
861
+ @classmethod
862
+ def truncate(cls, table):
863
+ return "truncate table {}".format(quote(table)), tuple()
864
+
865
+ @classmethod
866
+ def create_view(cls, name, query, temp=False, silent=True):
867
+ sql = ['CREATE']
868
+ if silent:
869
+ sql.append('OR REPLACE')
870
+ if temp:
871
+ sql.append('TEMPORARY')
872
+ sql.append('VIEW')
873
+ sql.append(name)
874
+ sql.append('AS')
875
+ sql.append(query)
876
+ return ' '.join(sql),tuple()
877
+
878
+ @classmethod
879
+ def drop_view(cls, name, silent=True):
880
+ sql = ['DROP VIEW']
881
+ if silent:
882
+ sql.append('IF EXISTS')
883
+ sql.append(name)
884
+ return ' '.join(sql),tuple()
885
+
886
+ class TYPES(object):
887
+ TEXT = 'TEXT'
888
+ INTEGER = 'INTEGER'
889
+ NUMERIC = 'NUMERIC'
890
+ DATETIME = 'TIMESTAMP WITHOUT TIME ZONE'
891
+ TIMESTAMP = 'TIMESTAMP WITHOUT TIME ZONE'
892
+ DATE = 'DATE'
893
+ TIME = 'TIME WITHOUT TIME ZONE'
894
+ BIGINT = 'BIGINT'
895
+ BOOLEAN = 'BOOLEAN'
896
+ BINARY = 'BLOB'
897
+ INTERVAL = 'INTERVAL'
898
+
899
+ reserved_words = ['ADMIN', 'ALIAS', 'ALL', 'ALLOCATE', 'ANALYSE', 'ANALYZE', 'AND', 'ANY', 'ARE', 'ARRAY', 'AS', 'ASC', 'AUTHORIZATION', 'BETWEEN', 'BINARY', 'BLOB', 'BOTH', 'BREADTH', 'CALL', 'CASCADED', 'CASE', 'CAST', 'CATALOG', 'CHECK', 'CLOB', 'COLLATE', 'COLLATION', 'COLUMN', 'COMPLETION', 'CONNECT', 'CONNECTION', 'CONSTRAINT', 'CONSTRUCTOR', 'CONTINUE', 'CORRESPONDING', 'CREATE', 'CROSS', 'CUBE', 'CURRENT', 'CURRENT_DATE', 'CURRENT_PATH', 'CURRENT_ROLE', 'CURRENT_TIME', 'CURRENT_TIMESTAMP', 'CURRENT_USER', 'DATA', 'DATE', 'DEFAULT', 'DEFERRABLE', 'DEPTH', 'DEREF', 'DESC', 'DESCRIBE', 'DESCRIPTOR', 'DESTROY', 'DESTRUCTOR', 'DETERMINISTIC', 'DIAGNOSTICS', 'DICTIONARY', 'DISCONNECT', 'DISTINCT', 'DO', 'DYNAMIC', 'ELSE', 'END', 'END-EXEC', 'EQUALS', 'EVERY', 'EXCEPT', 'EXCEPTION', 'EXEC', 'FALSE', 'FIRST', 'FOR', 'FOREIGN', 'FOUND', 'FREE', 'FREEZE', 'FROM', 'FULL', 'GENERAL', 'GO', 'GOTO', 'GRANT', 'GROUP', 'GROUPING', 'HAVING', 'HOST', 'IDENTITY', 'IGNORE', 'ILIKE', 'IN', 'INDICATOR', 'INITIALIZE', 'INITIALLY', 'INNER', 'INTERSECT', 'INTO', 'IS', 'ISNULL', 'ITERATE', 'JOIN', 'LARGE', 'LAST', 'LATERAL', 'LEADING', 'LEFT', 'LESS', 'LIKE', 'LIMIT', 'LOCALTIME', 'LOCALTIMESTAMP', 'LOCATOR', 'MAP', 'MODIFIES', 'MODIFY', 'MODULE', 'NAME', 'NATURAL', 'NCLOB', 'NEW', 'NOT', 'NOTNULL', 'NULL', 'OBJECT', 'OFF', 'OFFSET', 'OLD', 'ON', 'ONLY', 'OPEN', 'OPERATION', 'OR', 'ORDER', 'ORDINALITY', 'OUTER', 'OUTPUT', 'OVERLAPS', 'PAD', 'PARAMETER', 'PARAMETERS', 'PLACING', 'POSTFIX', 'PREFIX', 'PREORDER', 'PRESERVE', 'PRIMARY', 'PUBLIC', 'READS', 'RECURSIVE', 'REF', 'REFERENCES', 'REFERENCING', 'RESULT', 'RETURN', 'RIGHT', 'ROLE', 'ROLLUP', 'ROUTINE', 'ROWS', 'SAVEPOINT', 'SCOPE', 'SEARCH', 'SECTION', 'SELECT', 'SESSION_USER', 'SETS', 'SIMILAR', 'SIZE', 'SOME', 'SPACE', 'SPECIFIC', 'SPECIFICTYPE', 'SQL', 'SQLCODE', 'SQLERROR', 'SQLEXCEPTION', 'SQLSTATE', 'SQLWARNING', 'STATE', 'STATIC', 'STRUCTURE', 'SYSTEM_USER', 'TABLE', 'TERMINATE', 'THAN', 'THEN', 'TIMESTAMP', 'TIMEZONE_HOUR', 'TIMEZONE_MINUTE', 'TO', 'TRAILING', 'TRANSLATION', 'TRUE', 'UNDER', 'UNION', 'UNIQUE', 'UNNEST', 'USER', 'USING', 'VALUE', 'VARIABLE', 'VERBOSE', 'WHEN', 'WHENEVER', 'WHERE',]