sql-blocks 1.20250710__py3-none-any.whl → 1.20250712__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 +101 -57
- {sql_blocks-1.20250710.dist-info → sql_blocks-1.20250712.dist-info}/METADATA +25 -2
- sql_blocks-1.20250712.dist-info/RECORD +7 -0
- sql_blocks-1.20250710.dist-info/RECORD +0 -7
- {sql_blocks-1.20250710.dist-info → sql_blocks-1.20250712.dist-info}/LICENSE +0 -0
- {sql_blocks-1.20250710.dist-info → sql_blocks-1.20250712.dist-info}/WHEEL +0 -0
- {sql_blocks-1.20250710.dist-info → sql_blocks-1.20250712.dist-info}/top_level.txt +0 -0
sql_blocks/sql_blocks.py
CHANGED
@@ -221,11 +221,12 @@ class Code:
|
|
221
221
|
def As(self, field_alias: str, modifiers=None):
|
222
222
|
if modifiers:
|
223
223
|
self.extra[field_alias] = TO_LIST(modifiers)
|
224
|
-
|
224
|
+
if field_alias:
|
225
|
+
self.field_class = NamedField(field_alias)
|
225
226
|
return self
|
226
227
|
|
227
228
|
def format(self, name: str, main: SQLObject) -> str:
|
228
|
-
|
229
|
+
return Field.format(name, main)
|
229
230
|
|
230
231
|
def __add(self, name: str, main: SQLObject):
|
231
232
|
name = self.format(name, main)
|
@@ -335,6 +336,10 @@ class Round(Function):
|
|
335
336
|
inputs = [FLOAT]
|
336
337
|
output = FLOAT
|
337
338
|
|
339
|
+
class Trunc(Function):
|
340
|
+
inputs = [FLOAT]
|
341
|
+
output = FLOAT
|
342
|
+
|
338
343
|
# --- Date Functions: ------------------------------------
|
339
344
|
class DateDiff(Function):
|
340
345
|
inputs = [DATE]
|
@@ -1674,38 +1679,58 @@ class CypherParser(Parser):
|
|
1674
1679
|
Where(' '.join(condition)).add(field, query)
|
1675
1680
|
|
1676
1681
|
def add_order(self, token: str):
|
1677
|
-
self.add_field(token,
|
1682
|
+
self.add_field(token, sorted=True)
|
1678
1683
|
|
1679
|
-
def add_field(self, token: str,
|
1684
|
+
def add_field(self, token: str, sorted: bool = False):
|
1680
1685
|
if token in self.TOKEN_METHODS:
|
1681
1686
|
return
|
1682
|
-
class_list = [Field]
|
1683
1687
|
if '*' in token:
|
1684
|
-
|
1685
|
-
|
1688
|
+
pk_field = token.replace('*', '')
|
1689
|
+
if not pk_field.isidentifier():
|
1690
|
+
pos = int(pk_field or '1')-1
|
1691
|
+
pk_field = self.queries[-1].values[SELECT][pos]
|
1692
|
+
self.queries[-1].key_field = pk_field.split('.')[-1]
|
1686
1693
|
return
|
1687
|
-
|
1688
|
-
|
1689
|
-
|
1690
|
-
|
1691
|
-
|
1692
|
-
|
1693
|
-
|
1694
|
-
|
1695
|
-
|
1696
|
-
|
1694
|
+
# -------------------------------------------------------
|
1695
|
+
def field_params() -> dict:
|
1696
|
+
ROLE_OF_SEPARATOR = {
|
1697
|
+
'$': 'function',
|
1698
|
+
':': 'alias',
|
1699
|
+
'@': 'group',
|
1700
|
+
'!': 'field',
|
1701
|
+
}
|
1702
|
+
REGEX_FIELD = r'([{}])'.format(''.join(ROLE_OF_SEPARATOR))
|
1703
|
+
elements = re.split(REGEX_FIELD, token+'!')
|
1704
|
+
return {
|
1705
|
+
ROLE_OF_SEPARATOR[k]: v
|
1706
|
+
for k, v in zip(elements[1::2], elements[::2])
|
1707
|
+
}
|
1708
|
+
def run(function: str='', alias: str='', group: str='', field: str=''):
|
1709
|
+
is_count = function == 'count'
|
1710
|
+
if alias or is_count:
|
1711
|
+
field, alias = alias, field
|
1712
|
+
extra_classes = [OrderBy] if sorted else []
|
1713
|
+
if group:
|
1714
|
+
if not field:
|
1715
|
+
field = group
|
1716
|
+
elif not alias:
|
1717
|
+
alias = group
|
1718
|
+
extra_classes += [GroupBy]
|
1719
|
+
if function:
|
1720
|
+
if is_count and not field:
|
1721
|
+
field = self.queries[-1].key_field or 'id'
|
1722
|
+
class_type = FUNCTION_CLASS.get(function)
|
1697
1723
|
if not class_type:
|
1698
|
-
raise ValueError(f'Unknown function `{
|
1699
|
-
|
1700
|
-
|
1701
|
-
|
1702
|
-
|
1703
|
-
|
1704
|
-
|
1705
|
-
|
1706
|
-
|
1707
|
-
|
1708
|
-
FieldList(token, class_list).add('', self.queries[-1])
|
1724
|
+
raise ValueError(f'Unknown function `{function}`.')
|
1725
|
+
class_list = [ class_type().As(alias, extra_classes) ]
|
1726
|
+
elif alias:
|
1727
|
+
class_list = [NamedField(alias)] + extra_classes
|
1728
|
+
else:
|
1729
|
+
class_list = [Field] + extra_classes
|
1730
|
+
FieldList(field, class_list).add('', self.queries[-1])
|
1731
|
+
# -------------------------------------------------------
|
1732
|
+
run( **field_params() )
|
1733
|
+
# -------------------------------------------------------
|
1709
1734
|
|
1710
1735
|
def left_ftable(self, token: str):
|
1711
1736
|
if self.queries:
|
@@ -2117,7 +2142,7 @@ class CTE(Select):
|
|
2117
2142
|
else:
|
2118
2143
|
count = len(fields)
|
2119
2144
|
queries = detect(
|
2120
|
-
pattern*count,
|
2145
|
+
pattern*count, join_method=None, format=format
|
2121
2146
|
)
|
2122
2147
|
FieldList(fields, queries, ziped=True).add('', self)
|
2123
2148
|
self.break_lines = True
|
@@ -2138,7 +2163,7 @@ class Recursive(CTE):
|
|
2138
2163
|
def get_field(obj: SQLObject, pos: int) -> str:
|
2139
2164
|
return obj.values[SELECT][pos].split('.')[-1]
|
2140
2165
|
t1, t2 = detect(
|
2141
|
-
pattern*2,
|
2166
|
+
pattern*2, join_method=None, format=format
|
2142
2167
|
)
|
2143
2168
|
pk_field = get_field(t1, 0)
|
2144
2169
|
foreign_key = ''
|
@@ -2167,12 +2192,31 @@ MAIN_TAG = '__main__'
|
|
2167
2192
|
class CTEFactory:
|
2168
2193
|
def __init__(self, txt: str):
|
2169
2194
|
"""
|
2170
|
-
|
2195
|
+
SQL syntax:
|
2171
2196
|
---
|
2172
|
-
**SELECT
|
2197
|
+
**SELECT field, field
|
2173
2198
|
FROM** ( `sub_query1` ) **AS** `alias_1`
|
2174
2199
|
JOIN ( `sub_query2` ) **AS** `alias_2` **ON** `__join__`
|
2200
|
+
|
2201
|
+
Cypher syntax:
|
2202
|
+
---
|
2203
|
+
Table1(field, `function$`field`:alias`, `group@`) <- Table2(field)
|
2204
|
+
`...`MainTable(field)
|
2175
2205
|
"""
|
2206
|
+
self.main = None
|
2207
|
+
if parser_class(txt) == CypherParser:
|
2208
|
+
if '...' in txt:
|
2209
|
+
txt, other = txt.split('...')
|
2210
|
+
self.main = detect(other)
|
2211
|
+
alias = self.main.table_name
|
2212
|
+
query_list = Select.parse(txt, CypherParser)
|
2213
|
+
if not self.main:
|
2214
|
+
alias = '_'.join(query.table_name for query in query_list)
|
2215
|
+
self.main = Select(alias)
|
2216
|
+
self.main.break_lines = False
|
2217
|
+
query = join_queries(query_list)
|
2218
|
+
self.cte_list = [CTE(alias, [query])]
|
2219
|
+
return
|
2176
2220
|
summary = self.extract_subqueries(txt)
|
2177
2221
|
self.main = detect( summary.pop(MAIN_TAG) )
|
2178
2222
|
self.cte_list = [
|
@@ -2351,8 +2395,13 @@ def parser_class(text: str) -> Parser:
|
|
2351
2395
|
return class_type
|
2352
2396
|
return None
|
2353
2397
|
|
2398
|
+
def join_queries(query_list: list) -> Select:
|
2399
|
+
result = query_list[0]
|
2400
|
+
for query in query_list[1:]:
|
2401
|
+
result += query
|
2402
|
+
return result
|
2354
2403
|
|
2355
|
-
def detect(text: str,
|
2404
|
+
def detect(text: str, join_method = join_queries, format: str='') -> Select | list[Select]:
|
2356
2405
|
from collections import Counter
|
2357
2406
|
parser = parser_class(text)
|
2358
2407
|
if not parser:
|
@@ -2367,34 +2416,29 @@ def detect(text: str, join_queries: bool = True, format: str='') -> Select | lis
|
|
2367
2416
|
Select.EQUIVALENT_NAMES[new_name] = table
|
2368
2417
|
text = text[:begin] + new_name + '(' + text[end:]
|
2369
2418
|
count -= 1
|
2370
|
-
|
2419
|
+
result = Select.parse(text, parser)
|
2371
2420
|
if format:
|
2372
|
-
for query in
|
2421
|
+
for query in result:
|
2373
2422
|
query.set_file_format(format)
|
2374
|
-
if
|
2375
|
-
|
2376
|
-
result = query_list[0]
|
2377
|
-
for query in query_list[1:]:
|
2378
|
-
result += query
|
2423
|
+
if join_method:
|
2424
|
+
result = join_method(result)
|
2379
2425
|
return result
|
2380
2426
|
# ===========================================================================================//
|
2381
2427
|
|
2382
2428
|
if __name__ == "__main__":
|
2383
|
-
|
2384
|
-
|
2385
|
-
|
2386
|
-
|
2387
|
-
|
2388
|
-
|
2389
|
-
|
2390
|
-
|
2391
|
-
|
2392
|
-
|
2393
|
-
|
2394
|
-
|
2395
|
-
|
2396
|
-
|
2397
|
-
)
|
2398
|
-
)
|
2429
|
+
cte = CTEFactory(
|
2430
|
+
"Sales(year$ref_date:ref_year@, sum$quantity:qty_sold, vendor) <- Vendor(id, name:vendors_name@)"
|
2431
|
+
# ^^^ ^^^ ^^^
|
2432
|
+
# | | | ^^^ ^^^
|
2433
|
+
# | | | | |
|
2434
|
+
# | | | Relate Sales to Vendor --------+ |
|
2435
|
+
# | | | |
|
2436
|
+
# | | +---- Call it `ref_year` and group it |
|
2437
|
+
# | | |
|
2438
|
+
# | +-- Extracts the year from the `ref_date` field |
|
2439
|
+
# | |
|
2440
|
+
# +--- The Sales table |
|
2441
|
+
# Also groups by vendor´s name ------------------+
|
2442
|
+
"...Annual_Sales_per_Vendor(ref_year, qty_sold, vendors_name, *) -> Goal(year, target)"
|
2399
2443
|
)
|
2400
|
-
print(cte)
|
2444
|
+
print(cte)
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: sql_blocks
|
3
|
-
Version: 1.
|
3
|
+
Version: 1.20250712
|
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
|
@@ -948,7 +948,7 @@ R2 = Recursive.create(
|
|
948
948
|
>> Note: Comments added later.
|
949
949
|
---
|
950
950
|
|
951
|
-
### CTEFactory class
|
951
|
+
### 17.3 - CTEFactory class
|
952
952
|
CTEFactory exchanges subqueries for CTEs, simply by passing the text of the "dirty" query:
|
953
953
|
|
954
954
|
*Example*:
|
@@ -992,4 +992,27 @@ results...
|
|
992
992
|
ORDER BY
|
993
993
|
u001.name
|
994
994
|
```
|
995
|
+
|
996
|
+
#### 17.3.1 - You can also pass a Cypher script like in the example below:
|
997
|
+
|
998
|
+

|
999
|
+
|
1000
|
+
results...
|
1001
|
+
```
|
1002
|
+
WITH Annual_Sales_per_Vendor AS (
|
1003
|
+
SELECT ven.name as vendors_name, Year(sal.ref_date) as ref_year
|
1004
|
+
, Sum(sal.quantity) as qty_sold FROM Vendor ven LEFT JOIN Sales sal ON (ven.id = sal.vendor)
|
1005
|
+
GROUP BY ven.name, ref_year
|
1006
|
+
)
|
1007
|
+
SELECT
|
1008
|
+
aspv.ref_year,
|
1009
|
+
aspv.qty_sold,
|
1010
|
+
aspv.vendors_name,
|
1011
|
+
goa.target
|
1012
|
+
FROM
|
1013
|
+
Annual_Sales_per_Vendor aspv
|
1014
|
+
RIGHT JOIN Goal goa ON (aspv.ref_year = goa.year)
|
1015
|
+
```
|
1016
|
+
|
1017
|
+
|
995
1018
|
---
|
@@ -0,0 +1,7 @@
|
|
1
|
+
sql_blocks/__init__.py,sha256=5ItzGCyqqa6kwY8wvF9kapyHsAiWJ7KEXCcC-OtdXKg,37
|
2
|
+
sql_blocks/sql_blocks.py,sha256=A-i1B6jUA6_SYRfZAA65mhqHMkSQApXO7NcmIt3O5O8,83911
|
3
|
+
sql_blocks-1.20250712.dist-info/LICENSE,sha256=6kbiFSfobTZ7beWiKnHpN902HgBx-Jzgcme0SvKqhKY,1091
|
4
|
+
sql_blocks-1.20250712.dist-info/METADATA,sha256=Kj2I_aL5fxL2bxn3hoj8hb54kKDt3M510VDn8mGmA7o,25079
|
5
|
+
sql_blocks-1.20250712.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
6
|
+
sql_blocks-1.20250712.dist-info/top_level.txt,sha256=57AbUvUjYNy4m1EqDaU3WHeP-uyIAfV0n8GAUp1a1YQ,11
|
7
|
+
sql_blocks-1.20250712.dist-info/RECORD,,
|
@@ -1,7 +0,0 @@
|
|
1
|
-
sql_blocks/__init__.py,sha256=5ItzGCyqqa6kwY8wvF9kapyHsAiWJ7KEXCcC-OtdXKg,37
|
2
|
-
sql_blocks/sql_blocks.py,sha256=FLjT_V60QyfqUvbdu2hKUcFh3KTJNP091p0q_pg43v4,81469
|
3
|
-
sql_blocks-1.20250710.dist-info/LICENSE,sha256=6kbiFSfobTZ7beWiKnHpN902HgBx-Jzgcme0SvKqhKY,1091
|
4
|
-
sql_blocks-1.20250710.dist-info/METADATA,sha256=zuWxpPD-1LoJHSD7D4EywcpQaYi5buEjfuypOmwNdOE,24486
|
5
|
-
sql_blocks-1.20250710.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
6
|
-
sql_blocks-1.20250710.dist-info/top_level.txt,sha256=57AbUvUjYNy4m1EqDaU3WHeP-uyIAfV0n8GAUp1a1YQ,11
|
7
|
-
sql_blocks-1.20250710.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|