sql-blocks 1.25.47999999999__py3-none-any.whl → 1.25.514999999999__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 +166 -23
- {sql_blocks-1.25.47999999999.dist-info → sql_blocks-1.25.514999999999.dist-info}/METADATA +30 -1
- sql_blocks-1.25.514999999999.dist-info/RECORD +7 -0
- sql_blocks-1.25.47999999999.dist-info/RECORD +0 -7
- {sql_blocks-1.25.47999999999.dist-info → sql_blocks-1.25.514999999999.dist-info}/LICENSE +0 -0
- {sql_blocks-1.25.47999999999.dist-info → sql_blocks-1.25.514999999999.dist-info}/WHEEL +0 -0
- {sql_blocks-1.25.47999999999.dist-info → sql_blocks-1.25.514999999999.dist-info}/top_level.txt +0 -0
sql_blocks/sql_blocks.py
CHANGED
@@ -99,10 +99,11 @@ class SQLObject:
|
|
99
99
|
for fld in source:
|
100
100
|
result += re.split(r'([=()]|<>|\s+ON\s+|\s+on\s+)', fld)
|
101
101
|
return result
|
102
|
-
def cleanup(
|
102
|
+
def cleanup(text: str) -> str:
|
103
|
+
text = re.sub(r'[\n\t]', ' ', text)
|
103
104
|
if exact:
|
104
|
-
|
105
|
-
return
|
105
|
+
text = text.lower()
|
106
|
+
return text.strip()
|
106
107
|
def field_set(source: list) -> set:
|
107
108
|
return set(
|
108
109
|
(
|
@@ -510,14 +511,16 @@ class ForeignKey:
|
|
510
511
|
|
511
512
|
def quoted(value) -> str:
|
512
513
|
if isinstance(value, str):
|
514
|
+
if re.search(r'\bor\b', value, re.IGNORECASE):
|
515
|
+
raise PermissionError('Possible SQL injection attempt')
|
513
516
|
value = f"'{value}'"
|
514
517
|
return str(value)
|
515
518
|
|
516
519
|
|
517
520
|
class Position(Enum):
|
521
|
+
StartsWith = -1
|
518
522
|
Middle = 0
|
519
|
-
|
520
|
-
EndsWith = 2
|
523
|
+
EndsWith = 1
|
521
524
|
|
522
525
|
|
523
526
|
class Where:
|
@@ -535,7 +538,9 @@ class Where:
|
|
535
538
|
return cls.__constructor('=', value)
|
536
539
|
|
537
540
|
@classmethod
|
538
|
-
def contains(cls, text: str, pos: Position = Position.Middle):
|
541
|
+
def contains(cls, text: str, pos: int | Position = Position.Middle):
|
542
|
+
if isinstance(pos, int):
|
543
|
+
pos = Position(pos)
|
539
544
|
return cls(
|
540
545
|
"LIKE '{}{}{}'".format(
|
541
546
|
'%' if pos != Position.StartsWith else '',
|
@@ -593,10 +598,11 @@ class Where:
|
|
593
598
|
main.values[FROM].append(f',{query.table_name} {query.alias}')
|
594
599
|
for key in USUAL_KEYS:
|
595
600
|
main.update_values(key, query.values.get(key, []))
|
596
|
-
|
597
|
-
|
598
|
-
|
599
|
-
|
601
|
+
if query.key_field:
|
602
|
+
main.values.setdefault(WHERE, []).append('({a1}.{f1} = {a2}.{f2})'.format(
|
603
|
+
a1=main.alias, f1=name,
|
604
|
+
a2=query.alias, f2=query.key_field
|
605
|
+
))
|
600
606
|
|
601
607
|
def add(self, name: str, main: SQLObject):
|
602
608
|
func_type = FUNCTION_CLASS.get(name.lower())
|
@@ -652,7 +658,7 @@ class Case:
|
|
652
658
|
'\n'.join(
|
653
659
|
f'\t\tWHEN {field} {cond.content} THEN {res}'
|
654
660
|
for res, cond in self.__conditions.items()
|
655
|
-
) + f'\n\t\tELSE {default}' if default else '',
|
661
|
+
) + (f'\n\t\tELSE {default}' if default else ''),
|
656
662
|
name
|
657
663
|
)
|
658
664
|
main.values.setdefault(SELECT, []).append(name)
|
@@ -663,28 +669,35 @@ class Options:
|
|
663
669
|
self.__children: dict = values
|
664
670
|
|
665
671
|
def add(self, logical_separator: str, main: SQLObject):
|
666
|
-
if logical_separator not in ('AND', 'OR'):
|
672
|
+
if logical_separator.upper() not in ('AND', 'OR'):
|
667
673
|
raise ValueError('`logical_separator` must be AND or OR')
|
668
|
-
|
674
|
+
temp = Select(f'{main.table_name} {main.alias}')
|
669
675
|
child: Where
|
670
676
|
for field, child in self.__children.items():
|
671
|
-
|
672
|
-
Field.format(field, main), child.content
|
673
|
-
))
|
677
|
+
child.add(field, temp)
|
674
678
|
main.values.setdefault(WHERE, []).append(
|
675
|
-
'(' + logical_separator.join(
|
679
|
+
'(' + f'\n\t{logical_separator} '.join(temp.values[WHERE]) + ')'
|
676
680
|
)
|
677
681
|
|
678
682
|
|
679
683
|
class Between:
|
684
|
+
is_literal: bool = False
|
685
|
+
|
680
686
|
def __init__(self, start, end):
|
681
687
|
if start > end:
|
682
688
|
start, end = end, start
|
683
689
|
self.start = start
|
684
690
|
self.end = end
|
685
691
|
|
692
|
+
def literal(self) -> Where:
|
693
|
+
return Where('BETWEEN {} AND {}'.format(
|
694
|
+
self.start, self.end
|
695
|
+
))
|
696
|
+
|
686
697
|
def add(self, name: str, main:SQLObject):
|
687
|
-
|
698
|
+
if self.is_literal:
|
699
|
+
return self.literal().add(name, main)
|
700
|
+
Where.gte(self.start).add(name, main)
|
688
701
|
Where.lte(self.end).add(name, main)
|
689
702
|
|
690
703
|
class SameDay(Between):
|
@@ -695,6 +708,19 @@ class SameDay(Between):
|
|
695
708
|
)
|
696
709
|
|
697
710
|
|
711
|
+
class Range(Case):
|
712
|
+
INC_FUNCTION = lambda x: x + 1
|
713
|
+
|
714
|
+
def __init__(self, field: str, values: dict):
|
715
|
+
super().__init__(field)
|
716
|
+
start = 0
|
717
|
+
cls = self.__class__
|
718
|
+
for label, value in sorted(values.items(), key=lambda item: item[1]):
|
719
|
+
self.when(
|
720
|
+
Between(start, value).literal(), label
|
721
|
+
)
|
722
|
+
start = cls.INC_FUNCTION(value)
|
723
|
+
|
698
724
|
|
699
725
|
class Clause:
|
700
726
|
@classmethod
|
@@ -810,8 +836,16 @@ class QueryLanguage:
|
|
810
836
|
has_default = {key: bool(key == SELECT) for key in KEYWORD}
|
811
837
|
|
812
838
|
@staticmethod
|
813
|
-
def remove_alias(
|
814
|
-
|
839
|
+
def remove_alias(text: str) -> str:
|
840
|
+
value, sep = '', ''
|
841
|
+
text = re.sub('[\n\t]', ' ', text)
|
842
|
+
if ':' in text:
|
843
|
+
text, value = text.split(':', maxsplit=1)
|
844
|
+
sep = ':'
|
845
|
+
return '{}{}{}'.format(
|
846
|
+
''.join(re.split(r'\w+[.]', text)),
|
847
|
+
sep, value.replace("'", '"')
|
848
|
+
)
|
815
849
|
|
816
850
|
def join_with_tabs(self, values: list, sep: str='') -> str:
|
817
851
|
sep = sep + self.TABULATION
|
@@ -879,7 +913,8 @@ class MongoDBLanguage(QueryLanguage):
|
|
879
913
|
LOGICAL_OP_TO_MONGO_FUNC = {
|
880
914
|
'>': '$gt', '>=': '$gte',
|
881
915
|
'<': '$lt', '<=': '$lte',
|
882
|
-
'=': '$eq', '<>': '$ne',
|
916
|
+
'=': '$eq', '<>': '$ne',
|
917
|
+
'like': '$regex', 'LIKE': '$regex',
|
883
918
|
}
|
884
919
|
OPERATORS = '|'.join(op for op in LOGICAL_OP_TO_MONGO_FUNC)
|
885
920
|
REGEX = {
|
@@ -932,7 +967,7 @@ class MongoDBLanguage(QueryLanguage):
|
|
932
967
|
field, *op, const = tokens
|
933
968
|
op = ''.join(op)
|
934
969
|
expr = '{begin}{op}:{const}{end}'.format(
|
935
|
-
begin='{', const=const, end='}',
|
970
|
+
begin='{', const=const.replace('%', '.*'), end='}',
|
936
971
|
op=cls.LOGICAL_OP_TO_MONGO_FUNC[op],
|
937
972
|
)
|
938
973
|
where_list.append(f'{field}:{expr}')
|
@@ -1041,6 +1076,55 @@ class Neo4JLanguage(QueryLanguage):
|
|
1041
1076
|
return ''
|
1042
1077
|
|
1043
1078
|
|
1079
|
+
class DatabricksLanguage(QueryLanguage):
|
1080
|
+
pattern = '{_from}{where}{group_by}{order_by}{select}{limit}'
|
1081
|
+
has_default = {key: bool(key == SELECT) for key in KEYWORD}
|
1082
|
+
|
1083
|
+
def __init__(self, target: 'Select'):
|
1084
|
+
super().__init__(target)
|
1085
|
+
self.aggregation_fields = []
|
1086
|
+
|
1087
|
+
def add_field(self, values: list) -> str:
|
1088
|
+
AGG_FUNCS = '|'.join(cls.__name__ for cls in Aggregate.__subclasses__())
|
1089
|
+
# --------------------------------------------------------------
|
1090
|
+
def is_agg_field(fld: str) -> bool:
|
1091
|
+
return re.findall(fr'({AGG_FUNCS})[(]', fld, re.IGNORECASE)
|
1092
|
+
# --------------------------------------------------------------
|
1093
|
+
new_values = []
|
1094
|
+
for val in values:
|
1095
|
+
if is_agg_field(val):
|
1096
|
+
self.aggregation_fields.append(val)
|
1097
|
+
else:
|
1098
|
+
new_values.append(val)
|
1099
|
+
values = new_values
|
1100
|
+
return super().add_field(values)
|
1101
|
+
|
1102
|
+
def prefix(self, key: str) -> str:
|
1103
|
+
def get_aggregate() -> str:
|
1104
|
+
return 'AGGREGATE {} '.format(
|
1105
|
+
','.join(self.aggregation_fields)
|
1106
|
+
)
|
1107
|
+
return '{}{}{}{}{}'.format(
|
1108
|
+
'|> ' if key != FROM else '',
|
1109
|
+
self.LINE_BREAK,
|
1110
|
+
get_aggregate() if key == GROUP_BY else '',
|
1111
|
+
key, self.TABULATION
|
1112
|
+
)
|
1113
|
+
|
1114
|
+
# def get_tables(self, values: list) -> str:
|
1115
|
+
# return self.join_with_tabs(values)
|
1116
|
+
|
1117
|
+
# def extract_conditions(self, values: list) -> str:
|
1118
|
+
# return self.join_with_tabs(values, ' AND ')
|
1119
|
+
|
1120
|
+
# def sort_by(self, values: list) -> str:
|
1121
|
+
# return self.join_with_tabs(values, ',')
|
1122
|
+
|
1123
|
+
def set_group(self, values: list) -> str:
|
1124
|
+
return self.join_with_tabs(values, ',')
|
1125
|
+
|
1126
|
+
|
1127
|
+
|
1044
1128
|
class Parser:
|
1045
1129
|
REGEX = {}
|
1046
1130
|
|
@@ -1396,7 +1480,18 @@ class MongoParser(Parser):
|
|
1396
1480
|
|
1397
1481
|
def begin_conditions(self, value: str):
|
1398
1482
|
self.where_list = {}
|
1483
|
+
self.field_method = self.first_ORfield
|
1399
1484
|
return Where
|
1485
|
+
|
1486
|
+
def first_ORfield(self, text: str):
|
1487
|
+
if text.startswith('$'):
|
1488
|
+
return
|
1489
|
+
found = re.search(r'\w+[:]', text)
|
1490
|
+
if not found:
|
1491
|
+
return
|
1492
|
+
self.field_method = None
|
1493
|
+
p1, p2 = found.span()
|
1494
|
+
self.last_field = text[p1: p2-1]
|
1400
1495
|
|
1401
1496
|
def increment_brackets(self, value: str):
|
1402
1497
|
self.brackets[value] += 1
|
@@ -1405,6 +1500,7 @@ class MongoParser(Parser):
|
|
1405
1500
|
self.method = self.new_query
|
1406
1501
|
self.last_field = ''
|
1407
1502
|
self.where_list = None
|
1503
|
+
self.field_method = None
|
1408
1504
|
self.PARAM_BY_FUNCTION = {
|
1409
1505
|
'find': Where, 'aggregate': GroupBy, 'sort': OrderBy
|
1410
1506
|
}
|
@@ -1434,13 +1530,14 @@ class MongoParser(Parser):
|
|
1434
1530
|
self.close_brackets(
|
1435
1531
|
BRACKET_PAIR[token]
|
1436
1532
|
)
|
1533
|
+
elif self.field_method:
|
1534
|
+
self.field_method(token)
|
1437
1535
|
self.method = self.TOKEN_METHODS.get(token)
|
1438
1536
|
# ----------------------------
|
1439
1537
|
|
1440
1538
|
|
1441
1539
|
class Select(SQLObject):
|
1442
1540
|
join_type: JoinType = JoinType.INNER
|
1443
|
-
REGEX = {}
|
1444
1541
|
EQUIVALENT_NAMES = {}
|
1445
1542
|
|
1446
1543
|
def __init__(self, table_name: str='', **values):
|
@@ -1822,3 +1919,49 @@ def detect(text: str, join_queries: bool = True, format: str='') -> Select | lis
|
|
1822
1919
|
return result
|
1823
1920
|
# ===========================================================================================//
|
1824
1921
|
|
1922
|
+
|
1923
|
+
if __name__ == "__main__":
|
1924
|
+
# def identifica_suspeitos() -> Select:
|
1925
|
+
# """Mostra quais pessoas tem caracteríosticas iguais à descrição do suspeito"""
|
1926
|
+
# Select.join_type = JoinType.LEFT
|
1927
|
+
# return Select(
|
1928
|
+
# 'Suspeito s', id=Field,
|
1929
|
+
# _=Where.join(
|
1930
|
+
# Select('Pessoa p',
|
1931
|
+
# OR=Options(
|
1932
|
+
# pessoa=Where('= s.id'),
|
1933
|
+
# altura=Where.formula('ABS(% - s.{f}) < 0.5'),
|
1934
|
+
# peso=Where.formula('ABS(% - s.{f}) < 0.5'),
|
1935
|
+
# cabelo=Where.formula('% = s.{f}'),
|
1936
|
+
# olhos=Where.formula('% = s.{f}'),
|
1937
|
+
# sexo=Where.formula('% = s.{f}'),
|
1938
|
+
# ),
|
1939
|
+
# nome=Field
|
1940
|
+
# )
|
1941
|
+
# )
|
1942
|
+
# )
|
1943
|
+
# query = identifica_suspeitos()
|
1944
|
+
# print('='*50)
|
1945
|
+
# print(query)
|
1946
|
+
# print('-'*50)
|
1947
|
+
script = '''
|
1948
|
+
db.people.find({
|
1949
|
+
{
|
1950
|
+
$or: [
|
1951
|
+
status:{$eq:"B"},
|
1952
|
+
age:{$lt:50}
|
1953
|
+
]
|
1954
|
+
},
|
1955
|
+
age:{$gte:18}, status:{$eq:"A"}
|
1956
|
+
},{
|
1957
|
+
name: 1, user_id: 1
|
1958
|
+
}).sort({
|
1959
|
+
'''
|
1960
|
+
print('='*50)
|
1961
|
+
q1 = Select.parse(script, MongoParser)[0]
|
1962
|
+
print(q1)
|
1963
|
+
print('-'*50)
|
1964
|
+
q2 = q1.translate_to(MongoDBLanguage)
|
1965
|
+
print(q2)
|
1966
|
+
# print('-'*50)
|
1967
|
+
print('='*50)
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: sql_blocks
|
3
|
-
Version: 1.25.
|
3
|
+
Version: 1.25.514999999999
|
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
|
@@ -15,6 +15,10 @@ License-File: LICENSE
|
|
15
15
|
|
16
16
|
# SQL_Blocks
|
17
17
|
|
18
|
+
## _SQL_Blocks_ is useful for building complex SQL commands through smaller query blocks:
|
19
|
+
|
20
|
+
---
|
21
|
+
|
18
22
|
### 1 - You can assemble a simple object that will then be converted into an SQL command:
|
19
23
|
|
20
24
|
> a = Select('Actor') # --> SELECT * FROM Actor act
|
@@ -411,6 +415,31 @@ m2 = Select(
|
|
411
415
|
)
|
412
416
|
)
|
413
417
|
|
418
|
+
10.1 - If the labels used in the CASE are based on ranges of values in sequence, you can use the **Range class**:
|
419
|
+
|
420
|
+
query = Select(
|
421
|
+
'People p',
|
422
|
+
age_group=Range('age',{ # <<----------
|
423
|
+
'adult': 50,
|
424
|
+
'teenager': 17,
|
425
|
+
'child': 10,
|
426
|
+
'elderly': 70,
|
427
|
+
'young': 21,
|
428
|
+
})
|
429
|
+
)
|
430
|
+
is equivalent to...
|
431
|
+
```
|
432
|
+
SELECT
|
433
|
+
CASE
|
434
|
+
WHEN p.age BETWEEN 0 AND 10 THEN 'child'
|
435
|
+
WHEN p.age BETWEEN 11 AND 17 THEN 'teenager'
|
436
|
+
WHEN p.age BETWEEN 18 AND 21 THEN 'young'
|
437
|
+
WHEN p.age BETWEEN 22 AND 50 THEN 'adult'
|
438
|
+
WHEN p.age BETWEEN 51 AND 70 THEN 'elderly'
|
439
|
+
END AS age_group
|
440
|
+
FROM
|
441
|
+
People p
|
442
|
+
```
|
414
443
|
---
|
415
444
|
|
416
445
|
### 11 - optimize method
|
@@ -0,0 +1,7 @@
|
|
1
|
+
sql_blocks/__init__.py,sha256=5ItzGCyqqa6kwY8wvF9kapyHsAiWJ7KEXCcC-OtdXKg,37
|
2
|
+
sql_blocks/sql_blocks.py,sha256=8msHsR5Ttp8vpCJbhU7wd91IP-TboC0XAc1204kLKXE,65953
|
3
|
+
sql_blocks-1.25.514999999999.dist-info/LICENSE,sha256=6kbiFSfobTZ7beWiKnHpN902HgBx-Jzgcme0SvKqhKY,1091
|
4
|
+
sql_blocks-1.25.514999999999.dist-info/METADATA,sha256=vxHahM3KUO84oALwycgcIdR2szRmrKUo-9RjDZffWhk,22242
|
5
|
+
sql_blocks-1.25.514999999999.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
6
|
+
sql_blocks-1.25.514999999999.dist-info/top_level.txt,sha256=57AbUvUjYNy4m1EqDaU3WHeP-uyIAfV0n8GAUp1a1YQ,11
|
7
|
+
sql_blocks-1.25.514999999999.dist-info/RECORD,,
|
@@ -1,7 +0,0 @@
|
|
1
|
-
sql_blocks/__init__.py,sha256=5ItzGCyqqa6kwY8wvF9kapyHsAiWJ7KEXCcC-OtdXKg,37
|
2
|
-
sql_blocks/sql_blocks.py,sha256=Ho1Q7yej4MoDsXC9nvtnb2nsHjAk2MNi0zkWj65uYTk,61017
|
3
|
-
sql_blocks-1.25.47999999999.dist-info/LICENSE,sha256=6kbiFSfobTZ7beWiKnHpN902HgBx-Jzgcme0SvKqhKY,1091
|
4
|
-
sql_blocks-1.25.47999999999.dist-info/METADATA,sha256=Oh1WCd2FRe1OToEyxH4rvf7l5dREZ8JDbESunVoIFxg,21270
|
5
|
-
sql_blocks-1.25.47999999999.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
6
|
-
sql_blocks-1.25.47999999999.dist-info/top_level.txt,sha256=57AbUvUjYNy4m1EqDaU3WHeP-uyIAfV0n8GAUp1a1YQ,11
|
7
|
-
sql_blocks-1.25.47999999999.dist-info/RECORD,,
|
File without changes
|
File without changes
|
{sql_blocks-1.25.47999999999.dist-info → sql_blocks-1.25.514999999999.dist-info}/top_level.txt
RENAMED
File without changes
|