sql-blocks 1.25.30011644__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 +92 -30
- {sql_blocks-1.25.30011644.dist-info → sql_blocks-1.25.32011301.dist-info}/METADATA +92 -12
- sql_blocks-1.25.32011301.dist-info/RECORD +7 -0
- sql_blocks-1.25.30011644.dist-info/RECORD +0 -7
- {sql_blocks-1.25.30011644.dist-info → sql_blocks-1.25.32011301.dist-info}/LICENSE +0 -0
- {sql_blocks-1.25.30011644.dist-info → sql_blocks-1.25.32011301.dist-info}/WHEEL +0 -0
- {sql_blocks-1.25.30011644.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()
|
@@ -1457,33 +1474,35 @@ class NotSelectIN(SelectIN):
|
|
1457
1474
|
condition_class = Not
|
1458
1475
|
|
1459
1476
|
|
1460
|
-
class CTE:
|
1477
|
+
class CTE(Select):
|
1461
1478
|
prefix = ''
|
1462
1479
|
|
1463
|
-
def __init__(self,
|
1464
|
-
|
1480
|
+
def __init__(self, table_name: str, query_list: list[Select]):
|
1481
|
+
super().__init__(table_name)
|
1465
1482
|
for query in query_list:
|
1466
1483
|
query.break_lines = False
|
1467
1484
|
self.query_list = query_list
|
1468
|
-
|
1469
|
-
def format(self, query: Select) -> str:
|
1470
|
-
LINE_MAX_SIZE = 50
|
1471
|
-
result, line = [], ''
|
1472
|
-
for word in str(query).split(' '):
|
1473
|
-
if len(line) >= LINE_MAX_SIZE and word in KEYWORD:
|
1474
|
-
result.append(line)
|
1475
|
-
line = ''
|
1476
|
-
line += word + ' '
|
1477
|
-
if line:
|
1478
|
-
result.append(line)
|
1479
|
-
return '\n\t'.join(result)
|
1485
|
+
self.break_lines = False
|
1480
1486
|
|
1481
1487
|
def __str__(self) -> str:
|
1482
|
-
|
1483
|
-
|
1484
|
-
|
1485
|
-
|
1486
|
-
)
|
1488
|
+
# ---------------------------------------------------------
|
1489
|
+
def justify(query: Select) -> str:
|
1490
|
+
result, line = [], ''
|
1491
|
+
keywords = '|'.join(KEYWORD)
|
1492
|
+
for word in re.split(fr'({keywords}|AND|OR|,)', str(query)):
|
1493
|
+
if len(line) >= 65:
|
1494
|
+
result.append(line)
|
1495
|
+
line = ''
|
1496
|
+
line += word
|
1497
|
+
if line:
|
1498
|
+
result.append(line)
|
1499
|
+
return '\n '.join(result)
|
1500
|
+
# ---------------------------------------------------------
|
1501
|
+
return 'WITH {}{} AS (\n {}\n){}'.format(
|
1502
|
+
self.prefix, self.table_name,
|
1503
|
+
'\nUNION ALL\n '.join(
|
1504
|
+
justify(q) for q in self.query_list
|
1505
|
+
), super().__str__()
|
1487
1506
|
)
|
1488
1507
|
|
1489
1508
|
class Recursive(CTE):
|
@@ -1491,10 +1510,49 @@ class Recursive(CTE):
|
|
1491
1510
|
|
1492
1511
|
def __str__(self) -> str:
|
1493
1512
|
if len(self.query_list) > 1:
|
1494
|
-
|
1495
|
-
|
1513
|
+
self.query_list[-1].values[FROM].append(
|
1514
|
+
f', {self.table_name} {self.alias}')
|
1496
1515
|
return super().__str__()
|
1497
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
|
+
|
1498
1556
|
|
1499
1557
|
# ----- Rules -----
|
1500
1558
|
|
@@ -1612,7 +1670,7 @@ def parser_class(text: str) -> Parser:
|
|
1612
1670
|
return None
|
1613
1671
|
|
1614
1672
|
|
1615
|
-
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]:
|
1616
1674
|
from collections import Counter
|
1617
1675
|
parser = parser_class(text)
|
1618
1676
|
if not parser:
|
@@ -1623,11 +1681,14 @@ def detect(text: str, join_queries: bool = True) -> Select | list[Select]:
|
|
1623
1681
|
continue
|
1624
1682
|
pos = [ f.span() for f in re.finditer(fr'({table})[(]', text) ]
|
1625
1683
|
for begin, end in pos[::-1]:
|
1626
|
-
new_name = f'{table}_{count}' # See set_table (line
|
1684
|
+
new_name = f'{table}_{count}' # See set_table (line 55)
|
1627
1685
|
Select.EQUIVALENT_NAMES[new_name] = table
|
1628
1686
|
text = text[:begin] + new_name + '(' + text[end:]
|
1629
1687
|
count -= 1
|
1630
1688
|
query_list = Select.parse(text, parser)
|
1689
|
+
if format:
|
1690
|
+
for query in query_list:
|
1691
|
+
query.set_file_format(format)
|
1631
1692
|
if not join_queries:
|
1632
1693
|
return query_list
|
1633
1694
|
result = query_list[0]
|
@@ -1635,3 +1696,4 @@ def detect(text: str, join_queries: bool = True) -> Select | list[Select]:
|
|
1635
1696
|
result += query
|
1636
1697
|
return result
|
1637
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
|
@@ -98,10 +98,7 @@ query = Select('Movie m', title=Field,
|
|
98
98
|
awards=contains("Oscar")
|
99
99
|
)
|
100
100
|
AND=Options(
|
101
|
-
..., name=
|
102
|
-
'Chris',
|
103
|
-
Position.StartsWith
|
104
|
-
)
|
101
|
+
..., name=startswith('Chris')
|
105
102
|
)
|
106
103
|
```
|
107
104
|
|
@@ -175,8 +172,8 @@ FROM
|
|
175
172
|
artist.name as artist_name,
|
176
173
|
album.year_recorded
|
177
174
|
FROM
|
178
|
-
|
179
|
-
,
|
175
|
+
Album album
|
176
|
+
,Singer artist
|
180
177
|
WHERE
|
181
178
|
(album.artist_id = artist.id)
|
182
179
|
|
@@ -412,7 +409,7 @@ m2 = Select(
|
|
412
409
|
query = Select(
|
413
410
|
'Installments i', due_date=Field, customer=Select(
|
414
411
|
'Customer c', id=PrimaryKey,
|
415
|
-
name=
|
412
|
+
name=endswith('Smith')
|
416
413
|
)
|
417
414
|
)
|
418
415
|
print(query)
|
@@ -492,6 +489,7 @@ ORDER BY
|
|
492
489
|
* `^` Put the field in the ORDER BY clause
|
493
490
|
* `@` Immediately after the table name, it indicates the grouping field.
|
494
491
|
* `$` For SQL functions like **avg**$_field_, **sum**$_field_, **count**$_field_...
|
492
|
+
* `*` Sets the primary key field.
|
495
493
|
|
496
494
|
|
497
495
|
---
|
@@ -674,7 +672,7 @@ For example, if your query is going to run on Oracle, do the following:
|
|
674
672
|
|
675
673
|
### 17 - CTE and Recursive classes
|
676
674
|
|
677
|
-
* **_CTE class_**
|
675
|
+
* **17.1 - _CTE class_**
|
678
676
|
```
|
679
677
|
query = Select(
|
680
678
|
'SocialMedia s', post=Count, reaction=Sum, user=GroupBy
|
@@ -688,7 +686,7 @@ The result is...
|
|
688
686
|
)SELECT * FROM Metrics
|
689
687
|
```
|
690
688
|
|
691
|
-
* **_Recursive class_**
|
689
|
+
* **17.2 - _Recursive class_**
|
692
690
|
```
|
693
691
|
q1 = Select(
|
694
692
|
'SocialMedia me', name=[ eq(MY_NAME), Field ]
|
@@ -701,8 +699,90 @@ print( Recursive('Network', [q1, q2]) )
|
|
701
699
|
The result is...
|
702
700
|
```
|
703
701
|
WITH RECURSIVE Network AS (
|
704
|
-
SELECT me.name FROM SocialMedia me WHERE
|
702
|
+
SELECT me.name FROM SocialMedia me WHERE
|
703
|
+
me.name = 'Júlio Cascalles'
|
705
704
|
UNION ALL
|
706
|
-
SELECT you.name FROM SocialMedia you , Network n
|
705
|
+
SELECT you.name FROM SocialMedia you , Network n
|
706
|
+
WHERE you.id = n.friend
|
707
707
|
)SELECT * FROM Network
|
708
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=2R4VoSZUHTb1gSZwKjtNfkjxeH8G1dKYuDpuKv-UP7E,54430
|
3
|
-
sql_blocks-1.25.30011644.dist-info/LICENSE,sha256=6kbiFSfobTZ7beWiKnHpN902HgBx-Jzgcme0SvKqhKY,1091
|
4
|
-
sql_blocks-1.25.30011644.dist-info/METADATA,sha256=2Q1naQygxXqw87DTc70LyLjs8uWK95v0CWu5B9qHLlQ,16884
|
5
|
-
sql_blocks-1.25.30011644.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
6
|
-
sql_blocks-1.25.30011644.dist-info/top_level.txt,sha256=57AbUvUjYNy4m1EqDaU3WHeP-uyIAfV0n8GAUp1a1YQ,11
|
7
|
-
sql_blocks-1.25.30011644.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|