sql-blocks 0.2.9__tar.gz → 0.31.13__tar.gz
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-0.2.9/sql_blocks.egg-info → sql_blocks-0.31.13}/PKG-INFO +100 -1
- {sql_blocks-0.2.9 → sql_blocks-0.31.13}/README.md +99 -0
- {sql_blocks-0.2.9 → sql_blocks-0.31.13}/pyproject.toml +1 -1
- {sql_blocks-0.2.9 → sql_blocks-0.31.13}/setup.py +1 -1
- {sql_blocks-0.2.9 → sql_blocks-0.31.13}/sql_blocks/sql_blocks.py +145 -43
- {sql_blocks-0.2.9 → sql_blocks-0.31.13/sql_blocks.egg-info}/PKG-INFO +100 -1
- {sql_blocks-0.2.9 → sql_blocks-0.31.13}/LICENSE +0 -0
- {sql_blocks-0.2.9 → sql_blocks-0.31.13}/setup.cfg +0 -0
- {sql_blocks-0.2.9 → sql_blocks-0.31.13}/sql_blocks/__init__.py +0 -0
- {sql_blocks-0.2.9 → sql_blocks-0.31.13}/sql_blocks.egg-info/SOURCES.txt +0 -0
- {sql_blocks-0.2.9 → sql_blocks-0.31.13}/sql_blocks.egg-info/dependency_links.txt +0 -0
- {sql_blocks-0.2.9 → sql_blocks-0.31.13}/sql_blocks.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: sql_blocks
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.31.13
|
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
|
@@ -417,3 +417,102 @@ ORDER BY
|
|
417
417
|
* `<-` connects to the table on the left
|
418
418
|
* `->` connects to the table on the right
|
419
419
|
* `^` Put the field in the ORDER BY clause
|
420
|
+
* `@` Immediately after the table name, it indicates the grouping field.
|
421
|
+
* `$` For SQL functions like **avg**$_field_, **sum**$_field_, **count**$_field_...
|
422
|
+
|
423
|
+
|
424
|
+
---
|
425
|
+
## `detect` function
|
426
|
+
|
427
|
+
It is useful to write a query in a few lines, without specifying the script type (cypher, mongoDB, SQL, Neo4J...)
|
428
|
+
### Examples:
|
429
|
+
|
430
|
+
> **1 - Relationship**
|
431
|
+
```
|
432
|
+
query = detect(
|
433
|
+
'MATCH(c:Customer)<-[:Order]->(p:Product)RETURN c, p'
|
434
|
+
)
|
435
|
+
print(query)
|
436
|
+
```
|
437
|
+
##### output:
|
438
|
+
SELECT * FROM
|
439
|
+
Order ord
|
440
|
+
LEFT JOIN Customer cus ON (ord.customer_id = cus.id)
|
441
|
+
RIGHT JOIN Product pro ON (ord.product_id = pro.id)
|
442
|
+
> **2 - Grouping**
|
443
|
+
```
|
444
|
+
query = detect(
|
445
|
+
'People@gender(avg$age?region="SOUTH"^count$qtde)'
|
446
|
+
)
|
447
|
+
print(query)
|
448
|
+
```
|
449
|
+
##### output:
|
450
|
+
SELECT
|
451
|
+
peo.gender,
|
452
|
+
Avg(peo.age),
|
453
|
+
Count(*) as qtde
|
454
|
+
FROM
|
455
|
+
People peo
|
456
|
+
WHERE
|
457
|
+
peo.region = "SOUTH"
|
458
|
+
GROUP BY
|
459
|
+
peo.gender
|
460
|
+
ORDER BY
|
461
|
+
peo.qtde
|
462
|
+
|
463
|
+
> **3 - Many conditions...**
|
464
|
+
```
|
465
|
+
print( detect('''
|
466
|
+
db.people.find({
|
467
|
+
{
|
468
|
+
$or: [
|
469
|
+
status:{$eq:"B"},
|
470
|
+
age:{$lt:50}
|
471
|
+
]
|
472
|
+
},
|
473
|
+
age:{$gte:18}, status:{$eq:"A"}
|
474
|
+
},{
|
475
|
+
name: 1, user_id: 1
|
476
|
+
}).sort({
|
477
|
+
user_id: -1
|
478
|
+
})
|
479
|
+
''') )
|
480
|
+
```
|
481
|
+
#### output:
|
482
|
+
SELECT
|
483
|
+
peo.name,
|
484
|
+
peo.user_id
|
485
|
+
FROM
|
486
|
+
people peo
|
487
|
+
WHERE
|
488
|
+
( peo. = 'B' OR peo.age < 50 ) AND
|
489
|
+
peo.age >= 18 AND
|
490
|
+
peo.status = 'A'
|
491
|
+
ORDER BY
|
492
|
+
peo.user_id DESC
|
493
|
+
|
494
|
+
> **4 - Relations with same table twice (or more)**
|
495
|
+
|
496
|
+
Automatically assigns aliases to each side of the relationship (In this example, one user invites another to add to their contact list)
|
497
|
+
```
|
498
|
+
print( detect(
|
499
|
+
'User(^name,id) <-Contact(requester,guest)-> User(id,name)'
|
500
|
+
# ^^^ u1 ^^^ u2
|
501
|
+
) )
|
502
|
+
```
|
503
|
+
SELECT
|
504
|
+
u1.name,
|
505
|
+
u2.name
|
506
|
+
FROM
|
507
|
+
Contact con
|
508
|
+
RIGHT JOIN User u2 ON (con.guest = u2.id)
|
509
|
+
LEFT JOIN User u1 ON (con.requester = u1.id)
|
510
|
+
ORDER BY
|
511
|
+
u1.name
|
512
|
+
|
513
|
+
---
|
514
|
+
### `translate_to` method
|
515
|
+
It consists of the inverse process of parsing: From a Select object, it returns the text to a script in any of the languages below:
|
516
|
+
* QueryLanguage - default
|
517
|
+
* MongoDBLanguage
|
518
|
+
* Neo4JLanguage
|
@@ -402,3 +402,102 @@ ORDER BY
|
|
402
402
|
* `<-` connects to the table on the left
|
403
403
|
* `->` connects to the table on the right
|
404
404
|
* `^` Put the field in the ORDER BY clause
|
405
|
+
* `@` Immediately after the table name, it indicates the grouping field.
|
406
|
+
* `$` For SQL functions like **avg**$_field_, **sum**$_field_, **count**$_field_...
|
407
|
+
|
408
|
+
|
409
|
+
---
|
410
|
+
## `detect` function
|
411
|
+
|
412
|
+
It is useful to write a query in a few lines, without specifying the script type (cypher, mongoDB, SQL, Neo4J...)
|
413
|
+
### Examples:
|
414
|
+
|
415
|
+
> **1 - Relationship**
|
416
|
+
```
|
417
|
+
query = detect(
|
418
|
+
'MATCH(c:Customer)<-[:Order]->(p:Product)RETURN c, p'
|
419
|
+
)
|
420
|
+
print(query)
|
421
|
+
```
|
422
|
+
##### output:
|
423
|
+
SELECT * FROM
|
424
|
+
Order ord
|
425
|
+
LEFT JOIN Customer cus ON (ord.customer_id = cus.id)
|
426
|
+
RIGHT JOIN Product pro ON (ord.product_id = pro.id)
|
427
|
+
> **2 - Grouping**
|
428
|
+
```
|
429
|
+
query = detect(
|
430
|
+
'People@gender(avg$age?region="SOUTH"^count$qtde)'
|
431
|
+
)
|
432
|
+
print(query)
|
433
|
+
```
|
434
|
+
##### output:
|
435
|
+
SELECT
|
436
|
+
peo.gender,
|
437
|
+
Avg(peo.age),
|
438
|
+
Count(*) as qtde
|
439
|
+
FROM
|
440
|
+
People peo
|
441
|
+
WHERE
|
442
|
+
peo.region = "SOUTH"
|
443
|
+
GROUP BY
|
444
|
+
peo.gender
|
445
|
+
ORDER BY
|
446
|
+
peo.qtde
|
447
|
+
|
448
|
+
> **3 - Many conditions...**
|
449
|
+
```
|
450
|
+
print( detect('''
|
451
|
+
db.people.find({
|
452
|
+
{
|
453
|
+
$or: [
|
454
|
+
status:{$eq:"B"},
|
455
|
+
age:{$lt:50}
|
456
|
+
]
|
457
|
+
},
|
458
|
+
age:{$gte:18}, status:{$eq:"A"}
|
459
|
+
},{
|
460
|
+
name: 1, user_id: 1
|
461
|
+
}).sort({
|
462
|
+
user_id: -1
|
463
|
+
})
|
464
|
+
''') )
|
465
|
+
```
|
466
|
+
#### output:
|
467
|
+
SELECT
|
468
|
+
peo.name,
|
469
|
+
peo.user_id
|
470
|
+
FROM
|
471
|
+
people peo
|
472
|
+
WHERE
|
473
|
+
( peo. = 'B' OR peo.age < 50 ) AND
|
474
|
+
peo.age >= 18 AND
|
475
|
+
peo.status = 'A'
|
476
|
+
ORDER BY
|
477
|
+
peo.user_id DESC
|
478
|
+
|
479
|
+
> **4 - Relations with same table twice (or more)**
|
480
|
+
|
481
|
+
Automatically assigns aliases to each side of the relationship (In this example, one user invites another to add to their contact list)
|
482
|
+
```
|
483
|
+
print( detect(
|
484
|
+
'User(^name,id) <-Contact(requester,guest)-> User(id,name)'
|
485
|
+
# ^^^ u1 ^^^ u2
|
486
|
+
) )
|
487
|
+
```
|
488
|
+
SELECT
|
489
|
+
u1.name,
|
490
|
+
u2.name
|
491
|
+
FROM
|
492
|
+
Contact con
|
493
|
+
RIGHT JOIN User u2 ON (con.guest = u2.id)
|
494
|
+
LEFT JOIN User u1 ON (con.requester = u1.id)
|
495
|
+
ORDER BY
|
496
|
+
u1.name
|
497
|
+
|
498
|
+
---
|
499
|
+
### `translate_to` method
|
500
|
+
It consists of the inverse process of parsing: From a Select object, it returns the text to a script in any of the languages below:
|
501
|
+
* QueryLanguage - default
|
502
|
+
* MongoDBLanguage
|
503
|
+
* Neo4JLanguage
|
@@ -219,6 +219,7 @@ class ForeignKey:
|
|
219
219
|
|
220
220
|
@staticmethod
|
221
221
|
def get_key(obj1: SQLObject, obj2: SQLObject) -> tuple:
|
222
|
+
# [To-Do] including alias will allow to relate the same table twice
|
222
223
|
return obj1.table_name, obj2.table_name
|
223
224
|
|
224
225
|
def add(self, name: str, main: SQLObject):
|
@@ -442,7 +443,7 @@ class QueryLanguage:
|
|
442
443
|
return self.join_with_tabs(values, ' AND ')
|
443
444
|
|
444
445
|
def sort_by(self, values: list) -> str:
|
445
|
-
return
|
446
|
+
return self.join_with_tabs(values)
|
446
447
|
|
447
448
|
def set_group(self, values: list) -> str:
|
448
449
|
return self.join_with_tabs(values, ',')
|
@@ -475,8 +476,8 @@ class QueryLanguage:
|
|
475
476
|
if not method or (not values and not self.has_default[key]):
|
476
477
|
self.result[ref] = ''
|
477
478
|
continue
|
478
|
-
text =
|
479
|
-
self.result[ref] = text
|
479
|
+
text = method(values)
|
480
|
+
self.result[ref] = self.prefix(key) + text
|
480
481
|
return self.pattern.format(**self.result).strip()
|
481
482
|
|
482
483
|
class MongoDBLanguage(QueryLanguage):
|
@@ -572,11 +573,13 @@ class MongoDBLanguage(QueryLanguage):
|
|
572
573
|
|
573
574
|
|
574
575
|
class Neo4JLanguage(QueryLanguage):
|
575
|
-
pattern = 'MATCH {_from}
|
576
|
-
has_default = {
|
576
|
+
pattern = 'MATCH {_from}{where}RETURN {select}{order_by}'
|
577
|
+
has_default = {WHERE: False, FROM: False, ORDER_BY: True, SELECT: True}
|
577
578
|
|
578
579
|
def add_field(self, values: list) -> str:
|
579
|
-
|
580
|
+
if values:
|
581
|
+
return self.join_with_tabs(values, ',')
|
582
|
+
return self.TABULATION + ','.join(self.aliases.keys())
|
580
583
|
|
581
584
|
def get_tables(self, values: list) -> str:
|
582
585
|
NODE_FORMAT = dict(
|
@@ -589,12 +592,13 @@ class Neo4JLanguage(QueryLanguage):
|
|
589
592
|
found = re.search(
|
590
593
|
r'^(left|right)\s+', txt, re.IGNORECASE
|
591
594
|
)
|
592
|
-
pos = 'core'
|
595
|
+
pos, end, i = 'core', 0, 0
|
593
596
|
if found:
|
594
597
|
start, end = found.span()
|
595
598
|
pos = txt[start:end-1].lower()
|
596
|
-
|
597
|
-
|
599
|
+
i = 1
|
600
|
+
tokens = re.split(r'JOIN\s+|ON\s+', txt[end:])
|
601
|
+
txt = tokens[i].strip()
|
598
602
|
table_name, *alias = txt.split()
|
599
603
|
if alias:
|
600
604
|
alias = alias[0]
|
@@ -604,23 +608,27 @@ class Neo4JLanguage(QueryLanguage):
|
|
604
608
|
if not condition:
|
605
609
|
self.aliases[alias] = ''
|
606
610
|
nodes[pos] = NODE_FORMAT[pos].format(alias, table_name, condition)
|
607
|
-
self.
|
608
|
-
|
611
|
+
return self.TABULATION + '{left}{core}{right}'.format(**nodes)
|
612
|
+
|
609
613
|
|
610
614
|
def extract_conditions(self, values: list) -> str:
|
615
|
+
equalities = {}
|
616
|
+
where_list = []
|
611
617
|
for condition in values:
|
612
618
|
other_comparisions = any(
|
613
|
-
char in condition for char in '
|
619
|
+
char in condition for char in '<>'
|
614
620
|
)
|
621
|
+
where_list.append(condition)
|
615
622
|
if '=' not in condition or other_comparisions:
|
616
|
-
|
623
|
+
continue
|
617
624
|
alias, field, const = re.split(r'[.=]', condition)
|
618
625
|
begin, end = '{', '}'
|
619
|
-
|
620
|
-
|
621
|
-
|
622
|
-
|
623
|
-
|
626
|
+
equalities[alias] = f'{begin}{field}:{const}{end}'
|
627
|
+
if len(equalities) == len(where_list):
|
628
|
+
self.aliases.update(equalities)
|
629
|
+
self.has_default[WHERE] = True
|
630
|
+
return self.LINE_BREAK
|
631
|
+
return self.join_with_tabs(where_list, ' AND ') + self.LINE_BREAK
|
624
632
|
|
625
633
|
def set_group(self, values: list) -> str:
|
626
634
|
return ''
|
@@ -628,9 +636,15 @@ class Neo4JLanguage(QueryLanguage):
|
|
628
636
|
def __init__(self, target: 'Select'):
|
629
637
|
super().__init__(target)
|
630
638
|
self.aliases = {}
|
631
|
-
self.KEYWORDS = [WHERE, FROM]
|
639
|
+
self.KEYWORDS = [WHERE, FROM, ORDER_BY, SELECT]
|
632
640
|
|
633
641
|
def prefix(self, key: str):
|
642
|
+
default_prefix = any([
|
643
|
+
(key == WHERE and not self.has_default[WHERE]),
|
644
|
+
key == ORDER_BY
|
645
|
+
])
|
646
|
+
if default_prefix:
|
647
|
+
return super().prefix(key)
|
634
648
|
return ''
|
635
649
|
|
636
650
|
|
@@ -662,9 +676,10 @@ class Parser:
|
|
662
676
|
return ''.join(result)
|
663
677
|
|
664
678
|
def get_tokens(self, txt: str) -> list:
|
665
|
-
return
|
666
|
-
self.remove_spaces(
|
667
|
-
|
679
|
+
return [
|
680
|
+
self.remove_spaces(t)
|
681
|
+
for t in self.REGEX['separator'].split(txt)
|
682
|
+
]
|
668
683
|
|
669
684
|
|
670
685
|
class JoinType(Enum):
|
@@ -748,12 +763,18 @@ class SQLParser(Parser):
|
|
748
763
|
self.queries = list( result.values() )
|
749
764
|
|
750
765
|
|
751
|
-
class
|
766
|
+
class CypherParser(Parser):
|
752
767
|
REGEX = {}
|
768
|
+
CHAR_SET = r'[(,?)^{}[\]]'
|
769
|
+
KEYWORDS = '|'.join(
|
770
|
+
fr'\s+{word}\s+'
|
771
|
+
for word in "where return WHERE RETURN and AND".split()
|
772
|
+
)
|
753
773
|
|
754
774
|
def prepare(self):
|
755
|
-
self.REGEX['separator'] = re.compile(
|
775
|
+
self.REGEX['separator'] = re.compile(fr'({self.CHAR_SET}|->|<-|{self.KEYWORDS})')
|
756
776
|
self.REGEX['condition'] = re.compile(r'(^\w+)|([<>=])')
|
777
|
+
self.REGEX['alias_pos'] = re.compile(r'(\w+)[.](\w+)')
|
757
778
|
self.join_type = JoinType.INNER
|
758
779
|
self.TOKEN_METHODS = {
|
759
780
|
'(': self.add_field, '?': self.add_where,
|
@@ -762,24 +783,52 @@ class Cypher(Parser):
|
|
762
783
|
'->': self.right_ftable,
|
763
784
|
}
|
764
785
|
self.method = self.new_query
|
786
|
+
self.aliases = {}
|
765
787
|
|
766
|
-
def new_query(self, token: str, join_type = JoinType.INNER):
|
767
|
-
|
768
|
-
|
769
|
-
|
770
|
-
|
788
|
+
def new_query(self, token: str, join_type = JoinType.INNER, alias: str=''):
|
789
|
+
token, *group_fields = token.split('@')
|
790
|
+
if not token.isidentifier():
|
791
|
+
return
|
792
|
+
table_name = f'{token} {alias}' if alias else token
|
793
|
+
query = self.class_type(table_name)
|
794
|
+
if not alias:
|
795
|
+
alias = query.alias
|
796
|
+
self.queries.append(query)
|
797
|
+
self.aliases[alias] = query
|
798
|
+
FieldList(group_fields, [Field, GroupBy]).add('', query)
|
799
|
+
query.join_type = join_type
|
771
800
|
|
772
801
|
def add_where(self, token: str):
|
773
|
-
|
774
|
-
|
775
|
-
|
776
|
-
|
802
|
+
elements = [t for t in self.REGEX['alias_pos'].split(token) if t]
|
803
|
+
if len(elements) == 3:
|
804
|
+
alias, field, *condition = elements
|
805
|
+
query = self.aliases[alias]
|
806
|
+
else:
|
807
|
+
field, *condition = [
|
808
|
+
t for t in self.REGEX['condition'].split(token) if t
|
809
|
+
]
|
810
|
+
query = self.queries[-1]
|
811
|
+
Where(' '.join(condition)).add(field, query)
|
777
812
|
|
778
813
|
def add_order(self, token: str):
|
779
|
-
|
814
|
+
self.add_field(token, [OrderBy])
|
780
815
|
|
781
|
-
def add_field(self, token: str):
|
782
|
-
|
816
|
+
def add_field(self, token: str, extra_classes: list['type']=[]):
|
817
|
+
if token in self.TOKEN_METHODS:
|
818
|
+
return
|
819
|
+
class_list = [Field]
|
820
|
+
if '$' in token:
|
821
|
+
func_name, token = token.split('$')
|
822
|
+
if func_name == 'count':
|
823
|
+
if not token:
|
824
|
+
token = 'count_1'
|
825
|
+
NamedField(token, Count).add('*', self.queries[-1])
|
826
|
+
class_list = []
|
827
|
+
else:
|
828
|
+
FUNCTION_CLASS = {f.__name__.lower(): f for f in Function.__subclasses__()}
|
829
|
+
class_list = [ FUNCTION_CLASS[func_name] ]
|
830
|
+
class_list += extra_classes
|
831
|
+
FieldList(token, class_list).add('', self.queries[-1])
|
783
832
|
|
784
833
|
def left_ftable(self, token: str):
|
785
834
|
if self.queries:
|
@@ -807,8 +856,11 @@ class Cypher(Parser):
|
|
807
856
|
raise IndexError(f'Foreign Key not found for {curr.table_name}.')
|
808
857
|
foreign_fld = curr.values[SELECT][0].split('.')[-1]
|
809
858
|
curr.delete(foreign_fld, [SELECT])
|
859
|
+
if curr.join_type == JoinType.RIGHT:
|
860
|
+
pk_field, foreign_fld = foreign_fld, pk_field
|
810
861
|
if curr.join_type == JoinType.RIGHT:
|
811
862
|
curr, last = last, curr
|
863
|
+
# pk_field, foreign_fld = foreign_fld, pk_field
|
812
864
|
k = ForeignKey.get_key(curr, last)
|
813
865
|
ForeignKey.references[k] = (foreign_fld, pk_field)
|
814
866
|
|
@@ -831,21 +883,25 @@ class Cypher(Parser):
|
|
831
883
|
self.method(token)
|
832
884
|
if token in ')]' and has_side_table():
|
833
885
|
self.add_foreign_key('')
|
834
|
-
self.method = self.TOKEN_METHODS.get(token)
|
886
|
+
self.method = self.TOKEN_METHODS.get(token.upper())
|
835
887
|
# ====================================
|
836
888
|
|
837
|
-
class Neo4JParser(
|
889
|
+
class Neo4JParser(CypherParser):
|
838
890
|
def prepare(self):
|
839
891
|
super().prepare()
|
840
892
|
self.TOKEN_METHODS = {
|
841
|
-
'(': self.new_query, '{': self.add_where,
|
842
|
-
'<-': self.left_ftable, '->': self.right_ftable,
|
843
|
-
'
|
893
|
+
'(': self.new_query, '{': self.add_where, '[': self.new_query,
|
894
|
+
'<-': self.left_ftable, '->': self.right_ftable,
|
895
|
+
'WHERE': self.add_where, 'AND': self.add_where,
|
844
896
|
}
|
845
897
|
self.method = None
|
898
|
+
self.aliases = {}
|
846
899
|
|
847
900
|
def new_query(self, token: str, join_type = JoinType.INNER):
|
848
|
-
|
901
|
+
alias = ''
|
902
|
+
if ':' in token:
|
903
|
+
alias, token = token.split(':')
|
904
|
+
super().new_query(token, join_type, alias)
|
849
905
|
|
850
906
|
def add_where(self, token: str):
|
851
907
|
super().add_where(token.replace(':', '='))
|
@@ -973,6 +1029,7 @@ class MongoParser(Parser):
|
|
973
1029
|
class Select(SQLObject):
|
974
1030
|
join_type: JoinType = JoinType.INNER
|
975
1031
|
REGEX = {}
|
1032
|
+
EQUIVALENT_NAMES = {}
|
976
1033
|
|
977
1034
|
def __init__(self, table_name: str='', **values):
|
978
1035
|
super().__init__(table_name)
|
@@ -988,7 +1045,7 @@ class Select(SQLObject):
|
|
988
1045
|
new_tables = set([
|
989
1046
|
'{jt}JOIN {tb} {a2} ON ({a1}.{f1} = {a2}.{f2})'.format(
|
990
1047
|
jt=self.join_type.value,
|
991
|
-
tb=self.table_name,
|
1048
|
+
tb=self.EQUIVALENT_NAMES.get(self.table_name, self.table_name),
|
992
1049
|
a1=main.alias, f1=name,
|
993
1050
|
a2=self.alias, f2=self.key_field
|
994
1051
|
)
|
@@ -1151,3 +1208,48 @@ class RuleDateFuncReplace(Rule):
|
|
1151
1208
|
temp = Select(f'{target.table_name} {target.alias}')
|
1152
1209
|
Between(f'{year}-01-01', f'{year}-12-31').add(field, temp)
|
1153
1210
|
target.values[WHERE][i] = ' AND '.join(temp.values[WHERE])
|
1211
|
+
|
1212
|
+
|
1213
|
+
def parser_class(text: str) -> Parser:
|
1214
|
+
PARSER_REGEX = [
|
1215
|
+
(r'select.*from', SQLParser),
|
1216
|
+
(r'[.](find|aggregate)[(]', MongoParser),
|
1217
|
+
(r'[(\[]\w*[:]\w+', Neo4JParser),
|
1218
|
+
(r'^\w+[@]*\w*[(]', CypherParser)
|
1219
|
+
]
|
1220
|
+
text = Parser.remove_spaces(text)
|
1221
|
+
for regex, class_type in PARSER_REGEX:
|
1222
|
+
if re.findall(regex, text, re.IGNORECASE):
|
1223
|
+
return class_type
|
1224
|
+
return None
|
1225
|
+
|
1226
|
+
|
1227
|
+
def detect(text: str) -> Select:
|
1228
|
+
from collections import Counter
|
1229
|
+
parser = parser_class(text)
|
1230
|
+
if not parser:
|
1231
|
+
raise SyntaxError('Unknown parser class')
|
1232
|
+
if parser == CypherParser:
|
1233
|
+
for table, count in Counter( re.findall(r'(\w+)[(]', text) ).most_common():
|
1234
|
+
if count < 2:
|
1235
|
+
continue
|
1236
|
+
pos = [ f.span() for f in re.finditer(fr'({table})[(]', text) ]
|
1237
|
+
for begin, end in pos[::-1]:
|
1238
|
+
new_name = f'{table}_{count}' # See set_table (line 45)
|
1239
|
+
Select.EQUIVALENT_NAMES[new_name] = table
|
1240
|
+
text = text[:begin] + new_name + '(' + text[end:]
|
1241
|
+
count -= 1
|
1242
|
+
query_list = Select.parse(text, parser)
|
1243
|
+
result = query_list[0]
|
1244
|
+
for query in query_list[1:]:
|
1245
|
+
result += query
|
1246
|
+
return result
|
1247
|
+
|
1248
|
+
|
1249
|
+
if __name__ == "__main__":
|
1250
|
+
print('@'*100)
|
1251
|
+
print( detect(
|
1252
|
+
# 'User(^name?role="Manager",id)<-Contact(requester, guest)->User(id,name)'
|
1253
|
+
'User(^name,id) <-Contact(requester,guest)-> User(id,name)'
|
1254
|
+
) )
|
1255
|
+
print('@'*100)
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: sql_blocks
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.31.13
|
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
|
@@ -417,3 +417,102 @@ ORDER BY
|
|
417
417
|
* `<-` connects to the table on the left
|
418
418
|
* `->` connects to the table on the right
|
419
419
|
* `^` Put the field in the ORDER BY clause
|
420
|
+
* `@` Immediately after the table name, it indicates the grouping field.
|
421
|
+
* `$` For SQL functions like **avg**$_field_, **sum**$_field_, **count**$_field_...
|
422
|
+
|
423
|
+
|
424
|
+
---
|
425
|
+
## `detect` function
|
426
|
+
|
427
|
+
It is useful to write a query in a few lines, without specifying the script type (cypher, mongoDB, SQL, Neo4J...)
|
428
|
+
### Examples:
|
429
|
+
|
430
|
+
> **1 - Relationship**
|
431
|
+
```
|
432
|
+
query = detect(
|
433
|
+
'MATCH(c:Customer)<-[:Order]->(p:Product)RETURN c, p'
|
434
|
+
)
|
435
|
+
print(query)
|
436
|
+
```
|
437
|
+
##### output:
|
438
|
+
SELECT * FROM
|
439
|
+
Order ord
|
440
|
+
LEFT JOIN Customer cus ON (ord.customer_id = cus.id)
|
441
|
+
RIGHT JOIN Product pro ON (ord.product_id = pro.id)
|
442
|
+
> **2 - Grouping**
|
443
|
+
```
|
444
|
+
query = detect(
|
445
|
+
'People@gender(avg$age?region="SOUTH"^count$qtde)'
|
446
|
+
)
|
447
|
+
print(query)
|
448
|
+
```
|
449
|
+
##### output:
|
450
|
+
SELECT
|
451
|
+
peo.gender,
|
452
|
+
Avg(peo.age),
|
453
|
+
Count(*) as qtde
|
454
|
+
FROM
|
455
|
+
People peo
|
456
|
+
WHERE
|
457
|
+
peo.region = "SOUTH"
|
458
|
+
GROUP BY
|
459
|
+
peo.gender
|
460
|
+
ORDER BY
|
461
|
+
peo.qtde
|
462
|
+
|
463
|
+
> **3 - Many conditions...**
|
464
|
+
```
|
465
|
+
print( detect('''
|
466
|
+
db.people.find({
|
467
|
+
{
|
468
|
+
$or: [
|
469
|
+
status:{$eq:"B"},
|
470
|
+
age:{$lt:50}
|
471
|
+
]
|
472
|
+
},
|
473
|
+
age:{$gte:18}, status:{$eq:"A"}
|
474
|
+
},{
|
475
|
+
name: 1, user_id: 1
|
476
|
+
}).sort({
|
477
|
+
user_id: -1
|
478
|
+
})
|
479
|
+
''') )
|
480
|
+
```
|
481
|
+
#### output:
|
482
|
+
SELECT
|
483
|
+
peo.name,
|
484
|
+
peo.user_id
|
485
|
+
FROM
|
486
|
+
people peo
|
487
|
+
WHERE
|
488
|
+
( peo. = 'B' OR peo.age < 50 ) AND
|
489
|
+
peo.age >= 18 AND
|
490
|
+
peo.status = 'A'
|
491
|
+
ORDER BY
|
492
|
+
peo.user_id DESC
|
493
|
+
|
494
|
+
> **4 - Relations with same table twice (or more)**
|
495
|
+
|
496
|
+
Automatically assigns aliases to each side of the relationship (In this example, one user invites another to add to their contact list)
|
497
|
+
```
|
498
|
+
print( detect(
|
499
|
+
'User(^name,id) <-Contact(requester,guest)-> User(id,name)'
|
500
|
+
# ^^^ u1 ^^^ u2
|
501
|
+
) )
|
502
|
+
```
|
503
|
+
SELECT
|
504
|
+
u1.name,
|
505
|
+
u2.name
|
506
|
+
FROM
|
507
|
+
Contact con
|
508
|
+
RIGHT JOIN User u2 ON (con.guest = u2.id)
|
509
|
+
LEFT JOIN User u1 ON (con.requester = u1.id)
|
510
|
+
ORDER BY
|
511
|
+
u1.name
|
512
|
+
|
513
|
+
---
|
514
|
+
### `translate_to` method
|
515
|
+
It consists of the inverse process of parsing: From a Select object, it returns the text to a script in any of the languages below:
|
516
|
+
* QueryLanguage - default
|
517
|
+
* MongoDBLanguage
|
518
|
+
* Neo4JLanguage
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|