sql-blocks 0.2.3__py3-none-any.whl → 0.2.6__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.
- sql_blocks/sql_blocks.py +183 -22
- {sql_blocks-0.2.3.dist-info → sql_blocks-0.2.6.dist-info}/METADATA +37 -1
- sql_blocks-0.2.6.dist-info/RECORD +7 -0
- sql_blocks-0.2.3.dist-info/RECORD +0 -7
- {sql_blocks-0.2.3.dist-info → sql_blocks-0.2.6.dist-info}/LICENSE +0 -0
- {sql_blocks-0.2.3.dist-info → sql_blocks-0.2.6.dist-info}/WHEEL +0 -0
- {sql_blocks-0.2.3.dist-info → sql_blocks-0.2.6.dist-info}/top_level.txt +0 -0
sql_blocks/sql_blocks.py
CHANGED
@@ -9,7 +9,7 @@ DISTINCT_PREFX = '(DISTINCT|distinct)'
|
|
9
9
|
KEYWORD = {
|
10
10
|
'SELECT': (',{}', 'SELECT *', DISTINCT_PREFX),
|
11
11
|
'FROM': ('{}', '', PATTERN_SUFFIX),
|
12
|
-
'WHERE': ('{}AND ', '', ''),
|
12
|
+
'WHERE': ('{}AND ', '', r'["\']'),
|
13
13
|
'GROUP BY': (',{}', '', PATTERN_SUFFIX),
|
14
14
|
'ORDER BY': (',{}', '', PATTERN_SUFFIX),
|
15
15
|
'LIMIT': (' ', '', ''),
|
@@ -69,27 +69,32 @@ class SQLObject:
|
|
69
69
|
return KEYWORD[key][0].format(appendix.get(key, ''))
|
70
70
|
|
71
71
|
def diff(self, key: str, search_list: list, exact: bool=False) -> set:
|
72
|
+
def disassemble(source: list) -> list:
|
73
|
+
result = []
|
74
|
+
for fld in source:
|
75
|
+
result += re.split(r'([=()]|<>|\s+ON\s+|\s+on\s+)', fld)
|
76
|
+
return result
|
72
77
|
def cleanup(fld: str) -> str:
|
73
78
|
if exact:
|
74
79
|
fld = fld.lower()
|
75
80
|
return fld.strip()
|
76
81
|
def is_named_field(fld: str) -> bool:
|
77
82
|
return key == SELECT and re.search(' as | AS ', fld)
|
78
|
-
pattern = KEYWORD[key][2]
|
79
|
-
if exact:
|
80
|
-
if key == WHERE:
|
81
|
-
pattern = ' '
|
82
|
-
pattern += f'|{PATTERN_PREFIX}'
|
83
|
-
separator = self.get_separator(key)
|
84
83
|
def field_set(source: list) -> set:
|
85
84
|
return set(
|
86
85
|
(
|
87
86
|
fld if is_named_field(fld) else
|
88
87
|
re.sub(pattern, '', cleanup(fld))
|
89
88
|
)
|
90
|
-
for string in source
|
89
|
+
for string in disassemble(source)
|
91
90
|
for fld in re.split(separator, string)
|
92
91
|
)
|
92
|
+
pattern = KEYWORD[key][2]
|
93
|
+
if exact:
|
94
|
+
if key == WHERE:
|
95
|
+
pattern = ' '
|
96
|
+
pattern += f'|{PATTERN_PREFIX}'
|
97
|
+
separator = self.get_separator(key)
|
93
98
|
s1 = field_set(search_list)
|
94
99
|
s2 = field_set(self.values.get(key, []))
|
95
100
|
if exact:
|
@@ -421,14 +426,30 @@ class Parser:
|
|
421
426
|
|
422
427
|
def __init__(self, txt: str, class_type):
|
423
428
|
self.queries = []
|
424
|
-
|
425
|
-
self.prepare()
|
429
|
+
self.prepare()
|
426
430
|
self.class_type = class_type
|
427
431
|
self.eval(txt)
|
428
432
|
|
429
433
|
def eval(self, txt: str):
|
430
434
|
...
|
431
435
|
|
436
|
+
@staticmethod
|
437
|
+
def remove_spaces(script: str) -> str:
|
438
|
+
is_string = False
|
439
|
+
result = []
|
440
|
+
for token in re.split(r'(")', script):
|
441
|
+
if token == '"':
|
442
|
+
is_string = not is_string
|
443
|
+
if not is_string:
|
444
|
+
token = re.sub(r'\s+', '', token)
|
445
|
+
result.append(token)
|
446
|
+
return ''.join(result)
|
447
|
+
|
448
|
+
def get_tokens(self, txt: str) -> list:
|
449
|
+
return self.REGEX['separator'].split(
|
450
|
+
self.remove_spaces(txt)
|
451
|
+
)
|
452
|
+
|
432
453
|
|
433
454
|
class SQLParser(Parser):
|
434
455
|
REGEX = {}
|
@@ -506,17 +527,18 @@ class SQLParser(Parser):
|
|
506
527
|
|
507
528
|
class Cypher(Parser):
|
508
529
|
REGEX = {}
|
509
|
-
TOKEN_METHODS = {}
|
510
530
|
|
511
531
|
def prepare(self):
|
512
|
-
self.REGEX['separator'] = re.compile(r'([(,?)^]|->|<-)')
|
532
|
+
self.REGEX['separator'] = re.compile(r'([(,?)^{}[\]]|->|<-)')
|
513
533
|
self.REGEX['condition'] = re.compile(r'(^\w+)|([<>=])')
|
534
|
+
self.join_type = JoinType.INNER
|
514
535
|
self.TOKEN_METHODS = {
|
515
536
|
'(': self.add_field, '?': self.add_where,
|
516
537
|
',': self.add_field, '^': self.add_order,
|
517
538
|
')': self.new_query, '->': self.left_ftable,
|
518
539
|
'<-': self.right_ftable,
|
519
540
|
}
|
541
|
+
self.method = self.new_query
|
520
542
|
|
521
543
|
def new_query(self, token: str):
|
522
544
|
if token.isidentifier():
|
@@ -542,31 +564,170 @@ class Cypher(Parser):
|
|
542
564
|
self.new_query(token)
|
543
565
|
self.join_type = JoinType.RIGHT
|
544
566
|
|
545
|
-
def add_foreign_key(self, token: str):
|
567
|
+
def add_foreign_key(self, token: str, pk_field: str=''):
|
546
568
|
curr, last = [self.queries[i] for i in (-1, -2)]
|
547
|
-
|
548
|
-
|
569
|
+
if not pk_field:
|
570
|
+
if not last.values.get(SELECT):
|
571
|
+
return
|
572
|
+
pk_field = last.values[SELECT][-1].split('.')[-1]
|
573
|
+
last.delete(pk_field, [SELECT])
|
549
574
|
if self.join_type == JoinType.RIGHT:
|
550
575
|
curr, last = last, curr
|
551
|
-
|
552
|
-
|
576
|
+
if '{}' in token:
|
577
|
+
token = token.format(
|
578
|
+
curr.table_name.lower()
|
579
|
+
)
|
553
580
|
k = ForeignKey.get_key(last, curr)
|
554
|
-
ForeignKey.references[k] = (
|
581
|
+
ForeignKey.references[k] = (token, pk_field)
|
555
582
|
self.join_type = JoinType.INNER
|
556
583
|
|
557
584
|
def eval(self, txt: str):
|
558
|
-
|
559
|
-
self.method = self.new_query
|
560
|
-
for token in self.REGEX['separator'].split( re.sub(r'\s+', '', txt) ):
|
585
|
+
for token in self.get_tokens(txt):
|
561
586
|
if not token:
|
562
587
|
continue
|
563
588
|
if self.method:
|
564
589
|
self.method(token)
|
565
|
-
if token
|
590
|
+
if token in '([' and self.join_type != JoinType.INNER:
|
566
591
|
self.method = self.add_foreign_key
|
567
592
|
else:
|
568
593
|
self.method = self.TOKEN_METHODS.get(token)
|
569
594
|
|
595
|
+
class Neo4JParser(Cypher):
|
596
|
+
def prepare(self):
|
597
|
+
super().prepare()
|
598
|
+
self.TOKEN_METHODS = {
|
599
|
+
'(': self.new_query, '{': self.add_where,
|
600
|
+
'->': self.left_ftable, '<-': self.right_ftable,
|
601
|
+
'[': self.new_query
|
602
|
+
}
|
603
|
+
self.method = None
|
604
|
+
|
605
|
+
def new_query(self, token: str):
|
606
|
+
super().new_query(token.split(':')[-1])
|
607
|
+
|
608
|
+
def add_where(self, token: str):
|
609
|
+
super().add_where(token.replace(':', '='))
|
610
|
+
|
611
|
+
def add_foreign_key(self, token: str, pk_field: str='') -> tuple:
|
612
|
+
self.new_query(token)
|
613
|
+
return super().add_foreign_key('{}_id', 'id')
|
614
|
+
|
615
|
+
# ----------------------------
|
616
|
+
class MongoParser(Parser):
|
617
|
+
REGEX = {}
|
618
|
+
|
619
|
+
def prepare(self):
|
620
|
+
self.REGEX['separator'] = re.compile(r'([({[\]},)])')
|
621
|
+
|
622
|
+
def new_query(self, token: str):
|
623
|
+
if not token:
|
624
|
+
return
|
625
|
+
*table, function = token.split('.')
|
626
|
+
self.param_type = self.PARAM_BY_FUNCTION.get(function)
|
627
|
+
if not self.param_type:
|
628
|
+
raise SyntaxError(f'Unknown function {function}')
|
629
|
+
if table and table[0]:
|
630
|
+
self.queries.append( self.class_type(table[-1]) )
|
631
|
+
|
632
|
+
def param_is_where(self) -> bool:
|
633
|
+
return self.param_type == Where or isinstance(self.param_type, Where)
|
634
|
+
|
635
|
+
def next_param(self, token: str):
|
636
|
+
if self.param_type == GroupBy:
|
637
|
+
self.param_type = Field
|
638
|
+
self.get_param(token)
|
639
|
+
|
640
|
+
def get_param(self, token: str):
|
641
|
+
if not ':' in token:
|
642
|
+
return
|
643
|
+
field, value = token.split(':')
|
644
|
+
is_function = field.startswith('$')
|
645
|
+
if not value and not is_function:
|
646
|
+
if self.param_is_where():
|
647
|
+
self.last_field = field
|
648
|
+
return
|
649
|
+
if self.param_is_where():
|
650
|
+
if is_function:
|
651
|
+
function = field
|
652
|
+
field = self.last_field
|
653
|
+
self.last_field = ''
|
654
|
+
else:
|
655
|
+
function = '$eq'
|
656
|
+
if '"' in value:
|
657
|
+
value = value.replace('"', '')
|
658
|
+
elif value and value[0].isnumeric():
|
659
|
+
numeric_type = float if len(value.split('.')) == 2 else int
|
660
|
+
value = numeric_type(value)
|
661
|
+
self.param_type = self.CONDITIONS[function](value)
|
662
|
+
if function == '$or':
|
663
|
+
return
|
664
|
+
elif self.param_type == GroupBy:
|
665
|
+
if field != '_id':
|
666
|
+
return
|
667
|
+
field = re.sub('"|[$]', '', value)
|
668
|
+
elif self.param_type == OrderBy and value == '-1':
|
669
|
+
OrderBy.sort = SortType.DESC
|
670
|
+
elif field.startswith('$'):
|
671
|
+
field = '{}({})'.format(
|
672
|
+
field.replace('$', ''), value
|
673
|
+
)
|
674
|
+
if self.where_list is not None and self.param_is_where():
|
675
|
+
self.where_list[field] = self.param_type
|
676
|
+
return
|
677
|
+
self.param_type.add(field, self.queries[-1])
|
678
|
+
|
679
|
+
def close_brackets(self, token: str):
|
680
|
+
self.brackets[token] -= 1
|
681
|
+
if self.param_is_where() and self.brackets[token] == 0:
|
682
|
+
if self.where_list is not None:
|
683
|
+
Options(**self.where_list).add('OR', self.queries[-1])
|
684
|
+
self.where_list = None
|
685
|
+
if token == '{':
|
686
|
+
self.param_type = Field
|
687
|
+
|
688
|
+
def begin_conditions(self, value: str):
|
689
|
+
self.where_list = {}
|
690
|
+
return Where
|
691
|
+
|
692
|
+
def increment_brackets(self, value: str):
|
693
|
+
self.brackets[value] += 1
|
694
|
+
|
695
|
+
def eval(self, txt: str):
|
696
|
+
self.method = self.new_query
|
697
|
+
self.last_field = ''
|
698
|
+
self.where_list = None
|
699
|
+
self.PARAM_BY_FUNCTION = {
|
700
|
+
'find': Where, 'aggregate': GroupBy, 'sort': OrderBy
|
701
|
+
}
|
702
|
+
BRACKET_PAIR = {'}': '{', ']': '['}
|
703
|
+
self.brackets = {char: 0 for char in BRACKET_PAIR.values()}
|
704
|
+
self.CONDITIONS = {
|
705
|
+
'$in': lambda value: contains(value),
|
706
|
+
'$gt': lambda value: gt(value),
|
707
|
+
'$gte' : lambda value: gte(value),
|
708
|
+
'$lt': lambda value: lt(value),
|
709
|
+
'$lte' : lambda value: lte(value),
|
710
|
+
'$eq': lambda value: eq(value),
|
711
|
+
'$ne': lambda value: Not.eq(value),
|
712
|
+
'$or': self.begin_conditions,
|
713
|
+
}
|
714
|
+
self.TOKEN_METHODS = {
|
715
|
+
'{': self.get_param, ',': self.next_param, ')': self.new_query,
|
716
|
+
}
|
717
|
+
for token in self.get_tokens(txt):
|
718
|
+
if not token:
|
719
|
+
continue
|
720
|
+
if self.method:
|
721
|
+
self.method(token)
|
722
|
+
if token in self.brackets:
|
723
|
+
self.increment_brackets(token)
|
724
|
+
elif token in BRACKET_PAIR:
|
725
|
+
self.close_brackets(
|
726
|
+
BRACKET_PAIR[token]
|
727
|
+
)
|
728
|
+
self.method = self.TOKEN_METHODS.get(token)
|
729
|
+
# ----------------------------
|
730
|
+
|
570
731
|
|
571
732
|
class JoinType(Enum):
|
572
733
|
INNER = ''
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: sql_blocks
|
3
|
-
Version: 0.2.
|
3
|
+
Version: 0.2.6
|
4
4
|
Summary: Allows you to create objects for parts of SQL query commands. Also to combine these objects by joining them, adding or removing parts...
|
5
5
|
Home-page: https://github.com/julio-cascalles/sql_blocks
|
6
6
|
Author: Júlio Cascalles
|
@@ -381,3 +381,39 @@ m2 = Select(
|
|
381
381
|
created_at=[Field, GroupBy, OrderBy]
|
382
382
|
)
|
383
383
|
```
|
384
|
+
|
385
|
+
### 13 - Change parser engine
|
386
|
+
```
|
387
|
+
a, c, m = Select.parse(
|
388
|
+
"""
|
389
|
+
Actor(name, id ?age = 40)
|
390
|
+
<- Cast(actor_id, movie_id) ->
|
391
|
+
Movie(id ^title)
|
392
|
+
""",
|
393
|
+
Cypher
|
394
|
+
# ^^^ recognizes syntax like Neo4J queries
|
395
|
+
)
|
396
|
+
```
|
397
|
+
|
398
|
+
**print(a+c+m)**
|
399
|
+
```
|
400
|
+
SELECT
|
401
|
+
act.name,
|
402
|
+
mov.title
|
403
|
+
FROM
|
404
|
+
Cast cas
|
405
|
+
JOIN Movie mov ON (cas.movie_id = mov.id)
|
406
|
+
JOIN Actor act ON (cas.actor_id = act.id)
|
407
|
+
WHERE
|
408
|
+
act.age = 40
|
409
|
+
ORDER BY
|
410
|
+
mov.title
|
411
|
+
```
|
412
|
+
---
|
413
|
+
> **Separators and meaning:**
|
414
|
+
* `( )` Delimits a table and its fields
|
415
|
+
* `,` Separate fields
|
416
|
+
* `?` For simple conditions (> < = <>)
|
417
|
+
* `<-` connects to the table on the left
|
418
|
+
* `->` connects to the table on the right
|
419
|
+
* `^` Put the field in the ORDER BY clause
|
@@ -0,0 +1,7 @@
|
|
1
|
+
sql_blocks/__init__.py,sha256=5ItzGCyqqa6kwY8wvF9kapyHsAiWJ7KEXCcC-OtdXKg,37
|
2
|
+
sql_blocks/sql_blocks.py,sha256=IT3XUhBdOA1MawoSaw-oMm1v4yv16fDwqcu21sGnIQs,30086
|
3
|
+
sql_blocks-0.2.6.dist-info/LICENSE,sha256=6kbiFSfobTZ7beWiKnHpN902HgBx-Jzgcme0SvKqhKY,1091
|
4
|
+
sql_blocks-0.2.6.dist-info/METADATA,sha256=4A--jCZFBGeVn_fXcF41szBc_4ReMJrVKr9WWqJZnZA,9675
|
5
|
+
sql_blocks-0.2.6.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
6
|
+
sql_blocks-0.2.6.dist-info/top_level.txt,sha256=57AbUvUjYNy4m1EqDaU3WHeP-uyIAfV0n8GAUp1a1YQ,11
|
7
|
+
sql_blocks-0.2.6.dist-info/RECORD,,
|
@@ -1,7 +0,0 @@
|
|
1
|
-
sql_blocks/__init__.py,sha256=5ItzGCyqqa6kwY8wvF9kapyHsAiWJ7KEXCcC-OtdXKg,37
|
2
|
-
sql_blocks/sql_blocks.py,sha256=xWP7coiy5Tj8YU_WgtkN8J8tc9-0_BaE-bYljPdEp6o,24361
|
3
|
-
sql_blocks-0.2.3.dist-info/LICENSE,sha256=6kbiFSfobTZ7beWiKnHpN902HgBx-Jzgcme0SvKqhKY,1091
|
4
|
-
sql_blocks-0.2.3.dist-info/METADATA,sha256=0bRpoB8KehBZ40kf-pXyOvrVcozTUZo-BsC3Aq5xn2I,8892
|
5
|
-
sql_blocks-0.2.3.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
6
|
-
sql_blocks-0.2.3.dist-info/top_level.txt,sha256=57AbUvUjYNy4m1EqDaU3WHeP-uyIAfV0n8GAUp1a1YQ,11
|
7
|
-
sql_blocks-0.2.3.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|