sql-blocks 1.25.30012339__py3-none-any.whl → 1.25.32011301__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 +75 -14
- {sql_blocks-1.25.30012339.dist-info → sql_blocks-1.25.32011301.dist-info}/METADATA +88 -5
- sql_blocks-1.25.32011301.dist-info/RECORD +7 -0
- sql_blocks-1.25.30012339.dist-info/RECORD +0 -7
- {sql_blocks-1.25.30012339.dist-info → sql_blocks-1.25.32011301.dist-info}/LICENSE +0 -0
- {sql_blocks-1.25.30012339.dist-info → sql_blocks-1.25.32011301.dist-info}/WHEEL +0 -0
- {sql_blocks-1.25.30012339.dist-info → sql_blocks-1.25.32011301.dist-info}/top_level.txt +0 -0
sql_blocks/sql_blocks.py
CHANGED
@@ -65,6 +65,11 @@ class SQLObject:
|
|
65
65
|
def table_name(self) -> str:
|
66
66
|
return self.values[FROM][0].split()[0]
|
67
67
|
|
68
|
+
def set_file_format(self, pattern: str):
|
69
|
+
if '{' not in pattern:
|
70
|
+
pattern = '{}' + pattern
|
71
|
+
self.values[FROM][0] = pattern.format(self.aka())
|
72
|
+
|
68
73
|
@property
|
69
74
|
def alias(self) -> str:
|
70
75
|
if self.__alias:
|
@@ -366,15 +371,20 @@ class ExpressionField:
|
|
366
371
|
class FieldList:
|
367
372
|
separator = ','
|
368
373
|
|
369
|
-
def __init__(self, fields: list=[], class_types = [Field]):
|
374
|
+
def __init__(self, fields: list=[], class_types = [Field], ziped: bool=False):
|
370
375
|
if isinstance(fields, str):
|
371
376
|
fields = [
|
372
377
|
f.strip() for f in fields.split(self.separator)
|
373
378
|
]
|
374
379
|
self.fields = fields
|
375
380
|
self.class_types = class_types
|
381
|
+
self.ziped = ziped
|
376
382
|
|
377
383
|
def add(self, name: str, main: SQLObject):
|
384
|
+
if self.ziped: # --- One class per field...
|
385
|
+
for field, class_type in zip(self.fields, self.class_types):
|
386
|
+
class_type.add(field, main)
|
387
|
+
return
|
378
388
|
for field in self.fields:
|
379
389
|
for class_type in self.class_types:
|
380
390
|
class_type.add(field, main)
|
@@ -1112,7 +1122,11 @@ class CypherParser(Parser):
|
|
1112
1122
|
if token in self.TOKEN_METHODS:
|
1113
1123
|
return
|
1114
1124
|
class_list = [Field]
|
1115
|
-
if '
|
1125
|
+
if '*' in token:
|
1126
|
+
token = token.replace('*', '')
|
1127
|
+
self.queries[-1].key_field = token
|
1128
|
+
return
|
1129
|
+
elif '$' in token:
|
1116
1130
|
func_name, token = token.split('$')
|
1117
1131
|
if func_name == 'count':
|
1118
1132
|
if not token:
|
@@ -1136,10 +1150,13 @@ class CypherParser(Parser):
|
|
1136
1150
|
def add_foreign_key(self, token: str, pk_field: str=''):
|
1137
1151
|
curr, last = [self.queries[i] for i in (-1, -2)]
|
1138
1152
|
if not pk_field:
|
1139
|
-
if
|
1140
|
-
|
1141
|
-
|
1142
|
-
|
1153
|
+
if last.key_field:
|
1154
|
+
pk_field = last.key_field
|
1155
|
+
else:
|
1156
|
+
if not last.values.get(SELECT):
|
1157
|
+
raise IndexError(f'Primary Key not found for {last.table_name}.')
|
1158
|
+
pk_field = last.values[SELECT][-1].split('.')[-1]
|
1159
|
+
last.delete(pk_field, [SELECT], exact=True)
|
1143
1160
|
if '{}' in token:
|
1144
1161
|
foreign_fld = token.format(
|
1145
1162
|
last.table_name.lower()
|
@@ -1465,24 +1482,25 @@ class CTE(Select):
|
|
1465
1482
|
for query in query_list:
|
1466
1483
|
query.break_lines = False
|
1467
1484
|
self.query_list = query_list
|
1485
|
+
self.break_lines = False
|
1468
1486
|
|
1469
1487
|
def __str__(self) -> str:
|
1470
1488
|
# ---------------------------------------------------------
|
1471
1489
|
def justify(query: Select) -> str:
|
1472
|
-
LINE_MAX_SIZE = 50
|
1473
1490
|
result, line = [], ''
|
1474
|
-
|
1475
|
-
|
1491
|
+
keywords = '|'.join(KEYWORD)
|
1492
|
+
for word in re.split(fr'({keywords}|AND|OR|,)', str(query)):
|
1493
|
+
if len(line) >= 65:
|
1476
1494
|
result.append(line)
|
1477
1495
|
line = ''
|
1478
|
-
line += word
|
1496
|
+
line += word
|
1479
1497
|
if line:
|
1480
1498
|
result.append(line)
|
1481
|
-
return '\n
|
1499
|
+
return '\n '.join(result)
|
1482
1500
|
# ---------------------------------------------------------
|
1483
|
-
return 'WITH {}{} AS (\n
|
1501
|
+
return 'WITH {}{} AS (\n {}\n){}'.format(
|
1484
1502
|
self.prefix, self.table_name,
|
1485
|
-
'\nUNION ALL\n
|
1503
|
+
'\nUNION ALL\n '.join(
|
1486
1504
|
justify(q) for q in self.query_list
|
1487
1505
|
), super().__str__()
|
1488
1506
|
)
|
@@ -1496,6 +1514,45 @@ class Recursive(CTE):
|
|
1496
1514
|
f', {self.table_name} {self.alias}')
|
1497
1515
|
return super().__str__()
|
1498
1516
|
|
1517
|
+
@classmethod
|
1518
|
+
def create(cls, name: str, pattern: str, formula: str, init_value, format: str=''):
|
1519
|
+
SQLObject.ALIAS_FUNC = None
|
1520
|
+
def get_field(obj: SQLObject, pos: int) -> str:
|
1521
|
+
return obj.values[SELECT][pos].split('.')[-1]
|
1522
|
+
t1, t2 = detect(
|
1523
|
+
pattern*2, join_queries=False, format=format
|
1524
|
+
)
|
1525
|
+
pk_field = get_field(t1, 0)
|
1526
|
+
foreign_key = ''
|
1527
|
+
for num in re.findall(r'\[(\d+)\]', formula):
|
1528
|
+
num = int(num)
|
1529
|
+
if not foreign_key:
|
1530
|
+
foreign_key = get_field(t2, num-1)
|
1531
|
+
formula = formula.replace(f'[{num}]', '%')
|
1532
|
+
else:
|
1533
|
+
formula = formula.replace(f'[{num}]', get_field(t2, num-1))
|
1534
|
+
Where.eq(init_value).add(pk_field, t1)
|
1535
|
+
Where.formula(formula).add(foreign_key or pk_field, t2)
|
1536
|
+
return cls(name, [t1, t2])
|
1537
|
+
|
1538
|
+
def join(self, pattern: str, fields: list | str, format: str):
|
1539
|
+
if isinstance(fields, str):
|
1540
|
+
count = len( fields.split(',') )
|
1541
|
+
else:
|
1542
|
+
count = len(fields)
|
1543
|
+
queries = detect(
|
1544
|
+
pattern*count, join_queries=False, format=format
|
1545
|
+
)
|
1546
|
+
FieldList(fields, queries, ziped=True).add('', self)
|
1547
|
+
self.break_lines = True
|
1548
|
+
|
1549
|
+
def counter(self, name: str, start, increment: str='+1'):
|
1550
|
+
for i, query in enumerate(self.query_list):
|
1551
|
+
if i == 0:
|
1552
|
+
Field.add(f'{start} AS {name}', query)
|
1553
|
+
else:
|
1554
|
+
Field.add(f'({name}{increment}) AS {name}', query)
|
1555
|
+
|
1499
1556
|
|
1500
1557
|
# ----- Rules -----
|
1501
1558
|
|
@@ -1613,7 +1670,7 @@ def parser_class(text: str) -> Parser:
|
|
1613
1670
|
return None
|
1614
1671
|
|
1615
1672
|
|
1616
|
-
def detect(text: str, join_queries: bool = True) -> Select | list[Select]:
|
1673
|
+
def detect(text: str, join_queries: bool = True, format: str='') -> Select | list[Select]:
|
1617
1674
|
from collections import Counter
|
1618
1675
|
parser = parser_class(text)
|
1619
1676
|
if not parser:
|
@@ -1629,6 +1686,9 @@ def detect(text: str, join_queries: bool = True) -> Select | list[Select]:
|
|
1629
1686
|
text = text[:begin] + new_name + '(' + text[end:]
|
1630
1687
|
count -= 1
|
1631
1688
|
query_list = Select.parse(text, parser)
|
1689
|
+
if format:
|
1690
|
+
for query in query_list:
|
1691
|
+
query.set_file_format(format)
|
1632
1692
|
if not join_queries:
|
1633
1693
|
return query_list
|
1634
1694
|
result = query_list[0]
|
@@ -1636,3 +1696,4 @@ def detect(text: str, join_queries: bool = True) -> Select | list[Select]:
|
|
1636
1696
|
result += query
|
1637
1697
|
return result
|
1638
1698
|
|
1699
|
+
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: sql_blocks
|
3
|
-
Version: 1.25.
|
3
|
+
Version: 1.25.32011301
|
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
|
@@ -489,6 +489,7 @@ ORDER BY
|
|
489
489
|
* `^` Put the field in the ORDER BY clause
|
490
490
|
* `@` Immediately after the table name, it indicates the grouping field.
|
491
491
|
* `$` For SQL functions like **avg**$_field_, **sum**$_field_, **count**$_field_...
|
492
|
+
* `*` Sets the primary key field.
|
492
493
|
|
493
494
|
|
494
495
|
---
|
@@ -671,7 +672,7 @@ For example, if your query is going to run on Oracle, do the following:
|
|
671
672
|
|
672
673
|
### 17 - CTE and Recursive classes
|
673
674
|
|
674
|
-
* **_CTE class_**
|
675
|
+
* **17.1 - _CTE class_**
|
675
676
|
```
|
676
677
|
query = Select(
|
677
678
|
'SocialMedia s', post=Count, reaction=Sum, user=GroupBy
|
@@ -685,7 +686,7 @@ The result is...
|
|
685
686
|
)SELECT * FROM Metrics
|
686
687
|
```
|
687
688
|
|
688
|
-
* **_Recursive class_**
|
689
|
+
* **17.2 - _Recursive class_**
|
689
690
|
```
|
690
691
|
q1 = Select(
|
691
692
|
'SocialMedia me', name=[ eq(MY_NAME), Field ]
|
@@ -698,8 +699,90 @@ print( Recursive('Network', [q1, q2]) )
|
|
698
699
|
The result is...
|
699
700
|
```
|
700
701
|
WITH RECURSIVE Network AS (
|
701
|
-
SELECT me.name FROM SocialMedia me WHERE
|
702
|
+
SELECT me.name FROM SocialMedia me WHERE
|
703
|
+
me.name = 'Júlio Cascalles'
|
702
704
|
UNION ALL
|
703
|
-
SELECT you.name FROM SocialMedia you , Network n
|
705
|
+
SELECT you.name FROM SocialMedia you , Network n
|
706
|
+
WHERE you.id = n.friend
|
704
707
|
)SELECT * FROM Network
|
705
708
|
```
|
709
|
+
|
710
|
+
* **17.2.1 - The `create` method** ... parameters :
|
711
|
+
- name: The name of the CTE
|
712
|
+
- pattern: A cypher script that defines the tables used
|
713
|
+
- formula: The format for `Where.formula` method _(*)_
|
714
|
+
- init_value: The value for the condition in the first table
|
715
|
+
- format (optional): If tables are files or internet hiperlinks, you may especify the extension and/or folder...
|
716
|
+
> Example:
|
717
|
+
```
|
718
|
+
R = Recursive.create(
|
719
|
+
'Route R', 'Flyght(departure, arrival)',
|
720
|
+
'[2] = R.[1]', 'JFK', format='.csv'
|
721
|
+
) # ^^^--- Flyghts from JFK airport
|
722
|
+
```
|
723
|
+
_...Creates a recursive CTE called Route, using Flyght table, where the recursivity condition is Flyght.arrival equals to Route.departure_
|
724
|
+
>> (*) -- Note that [1] and [2] refers to first field and second field. 😉
|
725
|
+
|
726
|
+
Result:
|
727
|
+
|
728
|
+
WITH RECURSIVE Route AS (
|
729
|
+
SELECT f1.departure, f1.arrival
|
730
|
+
FROM Flyght.csv f1
|
731
|
+
WHERE f1.departure = 'JFK'
|
732
|
+
UNION ALL
|
733
|
+
SELECT f2.departure, f2.arrival
|
734
|
+
FROM Flyght.csv f2
|
735
|
+
, Route R
|
736
|
+
WHERE f2.arrival = R.departure
|
737
|
+
)SELECT * FROM Route R
|
738
|
+
|
739
|
+
**17.2.2 - The `join` method**
|
740
|
+
|
741
|
+
In the previous example, if you add this code...
|
742
|
+
`R.join('Airport(*id,name)', 'departure, arrival', format='.csv')`
|
743
|
+
|
744
|
+
...The result would be:
|
745
|
+
|
746
|
+
WITH RECURSIVE Route AS (
|
747
|
+
SELECT f1.departure, f1.arrival
|
748
|
+
FROM Flyght.csv f1
|
749
|
+
WHERE f1.departure = 'JFK'
|
750
|
+
UNION ALL
|
751
|
+
SELECT f2.departure, f2.arrival
|
752
|
+
FROM Flyght.csv f2
|
753
|
+
, Route R
|
754
|
+
WHERE f2.arrival = R.departure
|
755
|
+
)SELECT
|
756
|
+
a1.name, a2.name
|
757
|
+
FROM
|
758
|
+
Route R
|
759
|
+
JOIN Airport.csv a2 ON (R.arrival = a2.id)
|
760
|
+
JOIN Airport.csv a1 ON (R.departure = a1.id)
|
761
|
+
|
762
|
+
|
763
|
+
**17.2.3 - The `counter` method**
|
764
|
+
Adds an increment field in queries inside CTE:
|
765
|
+
> Examples:
|
766
|
+
* `R.counter('stops', 0)` # -- counter starts with 0 and increment +1
|
767
|
+
* `R2.counter('generation', 5, '- 1')` # -- for the code below...
|
768
|
+
```
|
769
|
+
R2 = Recursive.create(
|
770
|
+
'Ancestors a', 'People(id,name,father,mother,birth)',
|
771
|
+
'(% = a.father OR % = a.mother)', 32630, '.parquet'
|
772
|
+
)
|
773
|
+
```
|
774
|
+
...Results:
|
775
|
+
|
776
|
+
WITH RECURSIVE Ancestors AS (
|
777
|
+
SELECT p1.id, p1.name, p1.father, p1.mother, p1.birth,
|
778
|
+
5 AS generation /* <<---- Most current generation ------------*/
|
779
|
+
FROM People.parquet p1 WHERE p1.id = 32630
|
780
|
+
UNION ALL
|
781
|
+
SELECT p2.id, p2.name, p2.father, p2.mother, p2.birth,
|
782
|
+
(generation- 1) AS generation /* <<-- Previous generation -----*/
|
783
|
+
FROM People.parquet p2 , Ancestors a WHERE (p2.id = a.father OR p2.id = a.mother)
|
784
|
+
)SELECT * FROM Ancestors a
|
785
|
+
|
786
|
+
|
787
|
+
>> Note: Comments added later.
|
788
|
+
---
|
@@ -0,0 +1,7 @@
|
|
1
|
+
sql_blocks/__init__.py,sha256=5ItzGCyqqa6kwY8wvF9kapyHsAiWJ7KEXCcC-OtdXKg,37
|
2
|
+
sql_blocks/sql_blocks.py,sha256=VW8n2nWu7RREnns8dmYruhlvzGBCA3EeuBKiXCjClmE,56975
|
3
|
+
sql_blocks-1.25.32011301.dist-info/LICENSE,sha256=6kbiFSfobTZ7beWiKnHpN902HgBx-Jzgcme0SvKqhKY,1091
|
4
|
+
sql_blocks-1.25.32011301.dist-info/METADATA,sha256=g_Yr5mTcquKqG5PM6qSNeWsuZ4wbo88pdZUldp_rt9k,19574
|
5
|
+
sql_blocks-1.25.32011301.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
6
|
+
sql_blocks-1.25.32011301.dist-info/top_level.txt,sha256=57AbUvUjYNy4m1EqDaU3WHeP-uyIAfV0n8GAUp1a1YQ,11
|
7
|
+
sql_blocks-1.25.32011301.dist-info/RECORD,,
|
@@ -1,7 +0,0 @@
|
|
1
|
-
sql_blocks/__init__.py,sha256=5ItzGCyqqa6kwY8wvF9kapyHsAiWJ7KEXCcC-OtdXKg,37
|
2
|
-
sql_blocks/sql_blocks.py,sha256=TSrqjpP41uyocxLpTePI2JzSJorxbFB_jjLfWkHmnJ0,54584
|
3
|
-
sql_blocks-1.25.30012339.dist-info/LICENSE,sha256=6kbiFSfobTZ7beWiKnHpN902HgBx-Jzgcme0SvKqhKY,1091
|
4
|
-
sql_blocks-1.25.30012339.dist-info/METADATA,sha256=0nqWca0uwfqNns6xVIJAB_uR1KMjPnUUgrJHvBPX7bg,16753
|
5
|
-
sql_blocks-1.25.30012339.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
6
|
-
sql_blocks-1.25.30012339.dist-info/top_level.txt,sha256=57AbUvUjYNy4m1EqDaU3WHeP-uyIAfV0n8GAUp1a1YQ,11
|
7
|
-
sql_blocks-1.25.30012339.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|