sql-blocks 1.25.112__tar.gz → 1.25.1301__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-1.25.112/sql_blocks.egg-info → sql_blocks-1.25.1301}/PKG-INFO +9 -3
- {sql_blocks-1.25.112 → sql_blocks-1.25.1301}/README.md +8 -2
- {sql_blocks-1.25.112 → sql_blocks-1.25.1301}/pyproject.toml +1 -1
- {sql_blocks-1.25.112 → sql_blocks-1.25.1301}/setup.py +1 -1
- {sql_blocks-1.25.112 → sql_blocks-1.25.1301}/sql_blocks/sql_blocks.py +69 -25
- {sql_blocks-1.25.112 → sql_blocks-1.25.1301/sql_blocks.egg-info}/PKG-INFO +9 -3
- {sql_blocks-1.25.112 → sql_blocks-1.25.1301}/LICENSE +0 -0
- {sql_blocks-1.25.112 → sql_blocks-1.25.1301}/setup.cfg +0 -0
- {sql_blocks-1.25.112 → sql_blocks-1.25.1301}/sql_blocks/__init__.py +0 -0
- {sql_blocks-1.25.112 → sql_blocks-1.25.1301}/sql_blocks.egg-info/SOURCES.txt +0 -0
- {sql_blocks-1.25.112 → sql_blocks-1.25.1301}/sql_blocks.egg-info/dependency_links.txt +0 -0
- {sql_blocks-1.25.112 → sql_blocks-1.25.1301}/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: 1.25.
|
3
|
+
Version: 1.25.1301
|
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
|
@@ -554,12 +554,18 @@ It consists of the inverse process of parsing: From a Select object, it returns
|
|
554
554
|
---
|
555
555
|
### 14 - Window Function
|
556
556
|
|
557
|
-
Aggregation functions (Avg, Min, Max, Sum, Count) have the **over** method...
|
557
|
+
Aggregation functions (Avg, Min, Max, Sum, Count) -- or Window functions (Lead, Lag, Row_Number, Rank) -- have the **over** method...
|
558
558
|
|
559
559
|
query=Select(
|
560
560
|
'Enrollment e',
|
561
561
|
payment=Sum().over(
|
562
|
-
|
562
|
+
student_id=Partition, due_date=OrderBy,
|
563
|
+
# _=Rows(Current(), Following(5)),
|
564
|
+
# ^^^-------> ROWS BETWEEN CURRENT ROW AND 5 FOLLOWING
|
565
|
+
# _=Rows(Preceding(3), Following()),
|
566
|
+
# ^^^-------> ROWS BETWEEN 3 PRECEDING AND UNBOUNDED FOLLOWING
|
567
|
+
# _=Rows(Preceding(3))
|
568
|
+
# ^^^-------> ROWS 3 PRECEDING
|
563
569
|
).As('sum_per_student')
|
564
570
|
)
|
565
571
|
|
@@ -539,12 +539,18 @@ It consists of the inverse process of parsing: From a Select object, it returns
|
|
539
539
|
---
|
540
540
|
### 14 - Window Function
|
541
541
|
|
542
|
-
Aggregation functions (Avg, Min, Max, Sum, Count) have the **over** method...
|
542
|
+
Aggregation functions (Avg, Min, Max, Sum, Count) -- or Window functions (Lead, Lag, Row_Number, Rank) -- have the **over** method...
|
543
543
|
|
544
544
|
query=Select(
|
545
545
|
'Enrollment e',
|
546
546
|
payment=Sum().over(
|
547
|
-
|
547
|
+
student_id=Partition, due_date=OrderBy,
|
548
|
+
# _=Rows(Current(), Following(5)),
|
549
|
+
# ^^^-------> ROWS BETWEEN CURRENT ROW AND 5 FOLLOWING
|
550
|
+
# _=Rows(Preceding(3), Following()),
|
551
|
+
# ^^^-------> ROWS BETWEEN 3 PRECEDING AND UNBOUNDED FOLLOWING
|
552
|
+
# _=Rows(Preceding(3))
|
553
|
+
# ^^^-------> ROWS 3 PRECEDING
|
548
554
|
).As('sum_per_student')
|
549
555
|
)
|
550
556
|
|
@@ -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'
|
@@ -277,14 +282,15 @@ class Frame:
|
|
277
282
|
keywords = ''
|
278
283
|
for field, obj in args.items():
|
279
284
|
is_valid = any([
|
280
|
-
obj is
|
281
|
-
|
285
|
+
obj is OrderBy,
|
286
|
+
obj is Partition,
|
287
|
+
isinstance(obj, Rows),
|
282
288
|
])
|
283
289
|
if not is_valid:
|
284
290
|
continue
|
285
291
|
keywords += '{}{} {}'.format(
|
286
292
|
'\n\t\t' if self.break_lines else ' ',
|
287
|
-
obj.cls_to_str(), field
|
293
|
+
obj.cls_to_str(), field if field != '_' else ''
|
288
294
|
)
|
289
295
|
if keywords and self.break_lines:
|
290
296
|
keywords += '\n\t'
|
@@ -556,6 +562,34 @@ class SortType(Enum):
|
|
556
562
|
ASC = ''
|
557
563
|
DESC = ' DESC'
|
558
564
|
|
565
|
+
class Row:
|
566
|
+
def __init__(self, value: int=0):
|
567
|
+
self.value = value
|
568
|
+
|
569
|
+
def __str__(self) -> str:
|
570
|
+
return '{} {}'.format(
|
571
|
+
'UNBOUNDED' if self.value == 0 else self.value,
|
572
|
+
self.__class__.__name__.upper()
|
573
|
+
)
|
574
|
+
|
575
|
+
class Preceding(Row):
|
576
|
+
...
|
577
|
+
class Following(Row):
|
578
|
+
...
|
579
|
+
class Current(Row):
|
580
|
+
def __str__(self) -> str:
|
581
|
+
return 'CURRENT ROW'
|
582
|
+
|
583
|
+
class Rows:
|
584
|
+
def __init__(self, *rows: list[Row]):
|
585
|
+
self.rows = rows
|
586
|
+
|
587
|
+
def cls_to_str(self) -> str:
|
588
|
+
return 'ROWS {}{}'.format(
|
589
|
+
'BETWEEN ' if len(self.rows) > 1 else '',
|
590
|
+
' AND '.join(str(row) for row in self.rows)
|
591
|
+
)
|
592
|
+
|
559
593
|
|
560
594
|
class OrderBy(Clause):
|
561
595
|
sort: SortType = SortType.ASC
|
@@ -961,7 +995,7 @@ class SQLParser(Parser):
|
|
961
995
|
obj.values[key] = [
|
962
996
|
Field.format(fld, obj)
|
963
997
|
for fld in re.split(separator, values[key])
|
964
|
-
if (fld != '*' and len(tables) == 1) or obj.match(fld)
|
998
|
+
if (fld != '*' and len(tables) == 1) or obj.match(fld, key)
|
965
999
|
]
|
966
1000
|
result[obj.alias] = obj
|
967
1001
|
self.queries = list( result.values() )
|
@@ -1026,8 +1060,9 @@ class CypherParser(Parser):
|
|
1026
1060
|
if func_name == 'count':
|
1027
1061
|
if not token:
|
1028
1062
|
token = 'count_1'
|
1029
|
-
|
1030
|
-
|
1063
|
+
pk_field = self.queries[-1].key_field or 'id'
|
1064
|
+
Count().As(token, extra_classes).add(pk_field, self.queries[-1])
|
1065
|
+
return
|
1031
1066
|
else:
|
1032
1067
|
FUNCTION_CLASS = {f.__name__.lower(): f for f in Function.__subclasses__()}
|
1033
1068
|
class_list = [ FUNCTION_CLASS[func_name] ]
|
@@ -1048,7 +1083,7 @@ class CypherParser(Parser):
|
|
1048
1083
|
if not last.values.get(SELECT):
|
1049
1084
|
raise IndexError(f'Primary Key not found for {last.table_name}.')
|
1050
1085
|
pk_field = last.values[SELECT][-1].split('.')[-1]
|
1051
|
-
last.delete(pk_field, [SELECT])
|
1086
|
+
last.delete(pk_field, [SELECT], exact=True)
|
1052
1087
|
if '{}' in token:
|
1053
1088
|
foreign_fld = token.format(
|
1054
1089
|
last.table_name.lower()
|
@@ -1063,12 +1098,11 @@ class CypherParser(Parser):
|
|
1063
1098
|
if fld not in curr.values.get(GROUP_BY, [])
|
1064
1099
|
]
|
1065
1100
|
foreign_fld = fields[0].split('.')[-1]
|
1066
|
-
curr.delete(foreign_fld, [SELECT])
|
1101
|
+
curr.delete(foreign_fld, [SELECT], exact=True)
|
1067
1102
|
if curr.join_type == JoinType.RIGHT:
|
1068
1103
|
pk_field, foreign_fld = foreign_fld, pk_field
|
1069
1104
|
if curr.join_type == JoinType.RIGHT:
|
1070
1105
|
curr, last = last, curr
|
1071
|
-
# pk_field, foreign_fld = foreign_fld, pk_field
|
1072
1106
|
k = ForeignKey.get_key(curr, last)
|
1073
1107
|
ForeignKey.references[k] = (foreign_fld, pk_field)
|
1074
1108
|
|
@@ -1309,8 +1343,17 @@ class Select(SQLObject):
|
|
1309
1343
|
self.values.setdefault(LIMIT, result)
|
1310
1344
|
return self
|
1311
1345
|
|
1312
|
-
def match(self,
|
1313
|
-
|
1346
|
+
def match(self, field: str, key: str) -> bool:
|
1347
|
+
'''
|
1348
|
+
Recognizes if the field is from the current table
|
1349
|
+
'''
|
1350
|
+
if key in (ORDER_BY, GROUP_BY) and '.' not in field:
|
1351
|
+
return any(
|
1352
|
+
self.is_named_field(fld, SELECT)
|
1353
|
+
for fld in self.values[SELECT]
|
1354
|
+
if field in fld
|
1355
|
+
)
|
1356
|
+
return re.findall(f'\b*{self.alias}[.]', field) != []
|
1314
1357
|
|
1315
1358
|
@classmethod
|
1316
1359
|
def parse(cls, txt: str, parser: Parser = SQLParser) -> list[SQLObject]:
|
@@ -1431,12 +1474,13 @@ class RuleReplaceJoinBySubselect(Rule):
|
|
1431
1474
|
more_relations = any([
|
1432
1475
|
ref[0] == query.table_name for ref in ForeignKey.references
|
1433
1476
|
])
|
1434
|
-
|
1477
|
+
keep_join = any([
|
1435
1478
|
len( query.values.get(SELECT, []) ) > 0,
|
1436
1479
|
len( query.values.get(WHERE, []) ) == 0,
|
1437
1480
|
not fk_field, more_relations
|
1438
1481
|
])
|
1439
|
-
if
|
1482
|
+
if keep_join:
|
1483
|
+
query.add(fk_field, main)
|
1440
1484
|
continue
|
1441
1485
|
query.__class__ = SubSelect
|
1442
1486
|
Field.add(primary_k, query)
|
@@ -1460,7 +1504,7 @@ def parser_class(text: str) -> Parser:
|
|
1460
1504
|
return None
|
1461
1505
|
|
1462
1506
|
|
1463
|
-
def detect(text: str) -> Select:
|
1507
|
+
def detect(text: str, join_queries: bool = True) -> Select:
|
1464
1508
|
from collections import Counter
|
1465
1509
|
parser = parser_class(text)
|
1466
1510
|
if not parser:
|
@@ -1476,9 +1520,9 @@ def detect(text: str) -> Select:
|
|
1476
1520
|
text = text[:begin] + new_name + '(' + text[end:]
|
1477
1521
|
count -= 1
|
1478
1522
|
query_list = Select.parse(text, parser)
|
1523
|
+
if not join_queries:
|
1524
|
+
return query_list
|
1479
1525
|
result = query_list[0]
|
1480
1526
|
for query in query_list[1:]:
|
1481
1527
|
result += query
|
1482
1528
|
return result
|
1483
|
-
|
1484
|
-
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: sql_blocks
|
3
|
-
Version: 1.25.
|
3
|
+
Version: 1.25.1301
|
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
|
@@ -554,12 +554,18 @@ It consists of the inverse process of parsing: From a Select object, it returns
|
|
554
554
|
---
|
555
555
|
### 14 - Window Function
|
556
556
|
|
557
|
-
Aggregation functions (Avg, Min, Max, Sum, Count) have the **over** method...
|
557
|
+
Aggregation functions (Avg, Min, Max, Sum, Count) -- or Window functions (Lead, Lag, Row_Number, Rank) -- have the **over** method...
|
558
558
|
|
559
559
|
query=Select(
|
560
560
|
'Enrollment e',
|
561
561
|
payment=Sum().over(
|
562
|
-
|
562
|
+
student_id=Partition, due_date=OrderBy,
|
563
|
+
# _=Rows(Current(), Following(5)),
|
564
|
+
# ^^^-------> ROWS BETWEEN CURRENT ROW AND 5 FOLLOWING
|
565
|
+
# _=Rows(Preceding(3), Following()),
|
566
|
+
# ^^^-------> ROWS BETWEEN 3 PRECEDING AND UNBOUNDED FOLLOWING
|
567
|
+
# _=Rows(Preceding(3))
|
568
|
+
# ^^^-------> ROWS 3 PRECEDING
|
563
569
|
).As('sum_per_student')
|
564
570
|
)
|
565
571
|
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|