sql-blocks 1.25.111__py3-none-any.whl → 1.25.113__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 +88 -35
- {sql_blocks-1.25.111.dist-info → sql_blocks-1.25.113.dist-info}/METADATA +7 -1
- sql_blocks-1.25.113.dist-info/RECORD +7 -0
- sql_blocks-1.25.111.dist-info/RECORD +0 -7
- {sql_blocks-1.25.111.dist-info → sql_blocks-1.25.113.dist-info}/LICENSE +0 -0
- {sql_blocks-1.25.111.dist-info → sql_blocks-1.25.113.dist-info}/WHEEL +0 -0
- {sql_blocks-1.25.111.dist-info → sql_blocks-1.25.113.dist-info}/top_level.txt +0 -0
sql_blocks/sql_blocks.py
CHANGED
@@ -70,6 +70,10 @@ class SQLObject:
|
|
70
70
|
appendix = {WHERE: r'\s+and\s+|', FROM: r'\s+join\s+|\s+JOIN\s+'}
|
71
71
|
return KEYWORD[key][0].format(appendix.get(key, ''))
|
72
72
|
|
73
|
+
@staticmethod
|
74
|
+
def is_named_field(fld: str, key: str) -> bool:
|
75
|
+
return key == SELECT and re.search(r'\s+as\s+|\s+AS\s+', fld)
|
76
|
+
|
73
77
|
def diff(self, key: str, search_list: list, exact: bool=False) -> set:
|
74
78
|
def disassemble(source: list) -> list:
|
75
79
|
if not exact:
|
@@ -82,12 +86,10 @@ class SQLObject:
|
|
82
86
|
if exact:
|
83
87
|
fld = fld.lower()
|
84
88
|
return fld.strip()
|
85
|
-
def is_named_field(fld: str) -> bool:
|
86
|
-
return key == SELECT and re.search(r'\s+as\s+|\s+AS\s+', fld)
|
87
89
|
def field_set(source: list) -> set:
|
88
90
|
return set(
|
89
91
|
(
|
90
|
-
fld if is_named_field(fld) else
|
92
|
+
fld if self.is_named_field(fld, key) else
|
91
93
|
re.sub(pattern, '', cleanup(fld))
|
92
94
|
)
|
93
95
|
for string in disassemble(source)
|
@@ -105,13 +107,16 @@ class SQLObject:
|
|
105
107
|
return s1.symmetric_difference(s2)
|
106
108
|
return s1 - s2
|
107
109
|
|
108
|
-
def delete(self, search: str, keys: list=USUAL_KEYS):
|
110
|
+
def delete(self, search: str, keys: list=USUAL_KEYS, exact: bool=False):
|
111
|
+
if exact:
|
112
|
+
not_match = lambda item: not re.search(fr'\w*[.]*{search}$', item)
|
113
|
+
else:
|
114
|
+
not_match = lambda item: search not in item
|
109
115
|
for key in keys:
|
110
|
-
|
111
|
-
|
112
|
-
if
|
113
|
-
|
114
|
-
self.values[key] = result
|
116
|
+
self.values[key] = [
|
117
|
+
item for item in self.values.get(key, [])
|
118
|
+
if not_match(item)
|
119
|
+
]
|
115
120
|
|
116
121
|
|
117
122
|
SQL_CONST_SYSDATE = 'SYSDATE'
|
@@ -266,23 +271,38 @@ class Current_Date(Function):
|
|
266
271
|
return super().get_pattern()
|
267
272
|
# --------------------------------------------------------
|
268
273
|
|
269
|
-
class
|
274
|
+
class Frame:
|
270
275
|
break_lines: bool = True
|
271
276
|
|
272
277
|
def over(self, **args):
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
278
|
+
"""
|
279
|
+
How to use:
|
280
|
+
over(field1=OrderBy, field2=Partition)
|
281
|
+
"""
|
282
|
+
keywords = ''
|
283
|
+
for field, obj in args.items():
|
284
|
+
is_valid = any([
|
285
|
+
obj is class_type # or isinstance(obj, class_type)
|
286
|
+
for class_type in (OrderBy, Partition)
|
287
|
+
])
|
288
|
+
if not is_valid:
|
289
|
+
continue
|
290
|
+
keywords += '{}{} {}'.format(
|
291
|
+
'\n\t\t' if self.break_lines else ' ',
|
292
|
+
obj.cls_to_str(), field
|
293
|
+
)
|
280
294
|
if keywords and self.break_lines:
|
281
295
|
keywords += '\n\t'
|
282
296
|
self.pattern = self.get_pattern() + f' OVER({keywords})'
|
283
297
|
return self
|
284
298
|
|
285
299
|
|
300
|
+
class Aggregate(Frame):
|
301
|
+
...
|
302
|
+
|
303
|
+
class Window(Frame):
|
304
|
+
...
|
305
|
+
|
286
306
|
# ---- Aggregate Functions: -------------------------------
|
287
307
|
class Avg(Aggregate, Function):
|
288
308
|
...
|
@@ -295,6 +315,17 @@ class Sum(Aggregate, Function):
|
|
295
315
|
class Count(Aggregate, Function):
|
296
316
|
...
|
297
317
|
|
318
|
+
# ---- Window Functions: -----------------------------------
|
319
|
+
class Row_Number(Window, Function):
|
320
|
+
...
|
321
|
+
class Rank(Window, Function):
|
322
|
+
...
|
323
|
+
class Lag(Window, Function):
|
324
|
+
...
|
325
|
+
class Lead(Window, Function):
|
326
|
+
...
|
327
|
+
|
328
|
+
|
298
329
|
# ---- Conversions and other Functions: ---------------------
|
299
330
|
class Coalesce(Function):
|
300
331
|
...
|
@@ -539,6 +570,16 @@ class OrderBy(Clause):
|
|
539
570
|
name = cls.format(name, main)
|
540
571
|
main.values.setdefault(ORDER_BY, []).append(name+cls.sort.value)
|
541
572
|
|
573
|
+
@classmethod
|
574
|
+
def cls_to_str(cls) -> str:
|
575
|
+
return ORDER_BY
|
576
|
+
|
577
|
+
PARTITION_BY = 'PARTITION BY'
|
578
|
+
class Partition:
|
579
|
+
@classmethod
|
580
|
+
def cls_to_str(cls) -> str:
|
581
|
+
return PARTITION_BY
|
582
|
+
|
542
583
|
|
543
584
|
class GroupBy(Clause):
|
544
585
|
@classmethod
|
@@ -925,7 +966,7 @@ class SQLParser(Parser):
|
|
925
966
|
obj.values[key] = [
|
926
967
|
Field.format(fld, obj)
|
927
968
|
for fld in re.split(separator, values[key])
|
928
|
-
if (fld != '*' and len(tables) == 1) or obj.match(fld)
|
969
|
+
if (fld != '*' and len(tables) == 1) or obj.match(fld, key)
|
929
970
|
]
|
930
971
|
result[obj.alias] = obj
|
931
972
|
self.queries = list( result.values() )
|
@@ -990,8 +1031,9 @@ class CypherParser(Parser):
|
|
990
1031
|
if func_name == 'count':
|
991
1032
|
if not token:
|
992
1033
|
token = 'count_1'
|
993
|
-
|
994
|
-
|
1034
|
+
pk_field = self.queries[-1].key_field or 'id'
|
1035
|
+
Count().As(token, extra_classes).add(pk_field, self.queries[-1])
|
1036
|
+
return
|
995
1037
|
else:
|
996
1038
|
FUNCTION_CLASS = {f.__name__.lower(): f for f in Function.__subclasses__()}
|
997
1039
|
class_list = [ FUNCTION_CLASS[func_name] ]
|
@@ -1012,7 +1054,7 @@ class CypherParser(Parser):
|
|
1012
1054
|
if not last.values.get(SELECT):
|
1013
1055
|
raise IndexError(f'Primary Key not found for {last.table_name}.')
|
1014
1056
|
pk_field = last.values[SELECT][-1].split('.')[-1]
|
1015
|
-
last.delete(pk_field, [SELECT])
|
1057
|
+
last.delete(pk_field, [SELECT], exact=True)
|
1016
1058
|
if '{}' in token:
|
1017
1059
|
foreign_fld = token.format(
|
1018
1060
|
last.table_name.lower()
|
@@ -1027,12 +1069,11 @@ class CypherParser(Parser):
|
|
1027
1069
|
if fld not in curr.values.get(GROUP_BY, [])
|
1028
1070
|
]
|
1029
1071
|
foreign_fld = fields[0].split('.')[-1]
|
1030
|
-
curr.delete(foreign_fld, [SELECT])
|
1072
|
+
curr.delete(foreign_fld, [SELECT], exact=True)
|
1031
1073
|
if curr.join_type == JoinType.RIGHT:
|
1032
1074
|
pk_field, foreign_fld = foreign_fld, pk_field
|
1033
1075
|
if curr.join_type == JoinType.RIGHT:
|
1034
1076
|
curr, last = last, curr
|
1035
|
-
# pk_field, foreign_fld = foreign_fld, pk_field
|
1036
1077
|
k = ForeignKey.get_key(curr, last)
|
1037
1078
|
ForeignKey.references[k] = (foreign_fld, pk_field)
|
1038
1079
|
|
@@ -1273,8 +1314,17 @@ class Select(SQLObject):
|
|
1273
1314
|
self.values.setdefault(LIMIT, result)
|
1274
1315
|
return self
|
1275
1316
|
|
1276
|
-
def match(self,
|
1277
|
-
|
1317
|
+
def match(self, field: str, key: str) -> bool:
|
1318
|
+
'''
|
1319
|
+
Recognizes if the field is from the current table
|
1320
|
+
'''
|
1321
|
+
if key in (ORDER_BY, GROUP_BY) and '.' not in field:
|
1322
|
+
return any(
|
1323
|
+
self.is_named_field(fld, SELECT)
|
1324
|
+
for fld in self.values[SELECT]
|
1325
|
+
if field in fld
|
1326
|
+
)
|
1327
|
+
return re.findall(f'\b*{self.alias}[.]', field) != []
|
1278
1328
|
|
1279
1329
|
@classmethod
|
1280
1330
|
def parse(cls, txt: str, parser: Parser = SQLParser) -> list[SQLObject]:
|
@@ -1395,12 +1445,13 @@ class RuleReplaceJoinBySubselect(Rule):
|
|
1395
1445
|
more_relations = any([
|
1396
1446
|
ref[0] == query.table_name for ref in ForeignKey.references
|
1397
1447
|
])
|
1398
|
-
|
1448
|
+
keep_join = any([
|
1399
1449
|
len( query.values.get(SELECT, []) ) > 0,
|
1400
1450
|
len( query.values.get(WHERE, []) ) == 0,
|
1401
1451
|
not fk_field, more_relations
|
1402
1452
|
])
|
1403
|
-
if
|
1453
|
+
if keep_join:
|
1454
|
+
query.add(fk_field, main)
|
1404
1455
|
continue
|
1405
1456
|
query.__class__ = SubSelect
|
1406
1457
|
Field.add(primary_k, query)
|
@@ -1446,14 +1497,16 @@ def detect(text: str) -> Select:
|
|
1446
1497
|
return result
|
1447
1498
|
|
1448
1499
|
|
1449
|
-
if __name__ ==
|
1450
|
-
|
1451
|
-
|
1452
|
-
|
1453
|
-
|
1454
|
-
|
1455
|
-
|
1500
|
+
if __name__ == '__main__':
|
1501
|
+
p, c, a = Select.parse('''
|
1502
|
+
Professor(?nome="Júlio Cascalles", id)
|
1503
|
+
<- Curso@disciplina(professor, aluno) ->
|
1504
|
+
Aluno(id ^count$qtd_alunos)
|
1505
|
+
''', CypherParser)
|
1506
|
+
query = p + c + a
|
1507
|
+
print('#######################################')
|
1456
1508
|
print(query)
|
1457
|
-
print('
|
1509
|
+
print('***************************************')
|
1458
1510
|
query.optimize([RuleReplaceJoinBySubselect])
|
1459
1511
|
print(query)
|
1512
|
+
print('#######################################')
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: sql_blocks
|
3
|
-
Version: 1.25.
|
3
|
+
Version: 1.25.113
|
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
|
@@ -614,6 +614,12 @@ You may use this functions:
|
|
614
614
|
* Max
|
615
615
|
* Sum
|
616
616
|
* Count
|
617
|
+
* Lag
|
618
|
+
* Lead
|
619
|
+
* Row_Number
|
620
|
+
* Rank
|
621
|
+
* Coalesce
|
622
|
+
* Cast
|
617
623
|
> Some of these functions may vary in syntax depending on the database.
|
618
624
|
For example, if your query is going to run on Oracle, do the following:
|
619
625
|
|
@@ -0,0 +1,7 @@
|
|
1
|
+
sql_blocks/__init__.py,sha256=5ItzGCyqqa6kwY8wvF9kapyHsAiWJ7KEXCcC-OtdXKg,37
|
2
|
+
sql_blocks/sql_blocks.py,sha256=Saj3MqmF0OHY60bhc_aSl8fL-PhXM2apmiPiE6KspOc,50553
|
3
|
+
sql_blocks-1.25.113.dist-info/LICENSE,sha256=6kbiFSfobTZ7beWiKnHpN902HgBx-Jzgcme0SvKqhKY,1091
|
4
|
+
sql_blocks-1.25.113.dist-info/METADATA,sha256=td1IZl9O3m2U0rWFk6gyecqWnkr0cMDW94PfeqhzP8Y,14638
|
5
|
+
sql_blocks-1.25.113.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
6
|
+
sql_blocks-1.25.113.dist-info/top_level.txt,sha256=57AbUvUjYNy4m1EqDaU3WHeP-uyIAfV0n8GAUp1a1YQ,11
|
7
|
+
sql_blocks-1.25.113.dist-info/RECORD,,
|
@@ -1,7 +0,0 @@
|
|
1
|
-
sql_blocks/__init__.py,sha256=5ItzGCyqqa6kwY8wvF9kapyHsAiWJ7KEXCcC-OtdXKg,37
|
2
|
-
sql_blocks/sql_blocks.py,sha256=rARGbJxL3Di9mOXRBS4aEFvclO922-dzDOXPy1vSTGg,49018
|
3
|
-
sql_blocks-1.25.111.dist-info/LICENSE,sha256=6kbiFSfobTZ7beWiKnHpN902HgBx-Jzgcme0SvKqhKY,1091
|
4
|
-
sql_blocks-1.25.111.dist-info/METADATA,sha256=Lm7JKPIdZSJT5dkWUc2K7fpY4r0MQ-TeTyv0pbiMy24,14581
|
5
|
-
sql_blocks-1.25.111.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
6
|
-
sql_blocks-1.25.111.dist-info/top_level.txt,sha256=57AbUvUjYNy4m1EqDaU3WHeP-uyIAfV0n8GAUp1a1YQ,11
|
7
|
-
sql_blocks-1.25.111.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|