sql-blocks 1.20250714__py3-none-any.whl → 1.20250718__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 +120 -51
- {sql_blocks-1.20250714.dist-info → sql_blocks-1.20250718.dist-info}/METADATA +15 -11
- sql_blocks-1.20250718.dist-info/RECORD +7 -0
- sql_blocks-1.20250714.dist-info/RECORD +0 -7
- {sql_blocks-1.20250714.dist-info → sql_blocks-1.20250718.dist-info}/LICENSE +0 -0
- {sql_blocks-1.20250714.dist-info → sql_blocks-1.20250718.dist-info}/WHEEL +0 -0
- {sql_blocks-1.20250714.dist-info → sql_blocks-1.20250718.dist-info}/top_level.txt +0 -0
sql_blocks/sql_blocks.py
CHANGED
@@ -202,9 +202,14 @@ class NamedField:
|
|
202
202
|
self.class_type = class_type
|
203
203
|
|
204
204
|
def add(self, name: str, main: SQLObject):
|
205
|
+
def is_literal() -> bool:
|
206
|
+
if re.search(r'^[\'"].*', name):
|
207
|
+
return True
|
208
|
+
return False
|
205
209
|
main.values.setdefault(SELECT, []).append(
|
206
210
|
'{} as {}'.format(
|
207
|
-
|
211
|
+
name if is_literal()
|
212
|
+
else self.class_type.format(name, main),
|
208
213
|
self.alias # --- field alias
|
209
214
|
)
|
210
215
|
)
|
@@ -1989,7 +1994,7 @@ class Select(SQLObject):
|
|
1989
1994
|
from copy import deepcopy
|
1990
1995
|
return deepcopy(self)
|
1991
1996
|
|
1992
|
-
def
|
1997
|
+
def relation_error(self, other: SQLObject):
|
1993
1998
|
raise ValueError(f'No relationship found between {self.table_name} and {other.table_name}.')
|
1994
1999
|
|
1995
2000
|
def __add__(self, other: SQLObject):
|
@@ -2006,7 +2011,7 @@ class Select(SQLObject):
|
|
2006
2011
|
PrimaryKey.add(primary_key, query)
|
2007
2012
|
query.add(foreign_field, other)
|
2008
2013
|
return other
|
2009
|
-
self.
|
2014
|
+
self.relation_error(other) # === raise ERROR ... ===
|
2010
2015
|
elif primary_key:
|
2011
2016
|
PrimaryKey.add(primary_key, other)
|
2012
2017
|
other.add(foreign_field, query)
|
@@ -2035,7 +2040,7 @@ class Select(SQLObject):
|
|
2035
2040
|
else:
|
2036
2041
|
fk_field, primary_k = ForeignKey.find(other, self)
|
2037
2042
|
if not fk_field:
|
2038
|
-
self.
|
2043
|
+
self.relation_error(other) # === raise ERROR ... ===
|
2039
2044
|
query = other.copy()
|
2040
2045
|
other = self.copy()
|
2041
2046
|
query.__class__ = NotSelectIN
|
@@ -2122,8 +2127,8 @@ class CTE(Select):
|
|
2122
2127
|
query.break_lines = False
|
2123
2128
|
result, line = [], ''
|
2124
2129
|
keywords = '|'.join(KEYWORD)
|
2125
|
-
for word in re.split(fr'({keywords}|AND|OR|,)', str(query)):
|
2126
|
-
if len(line) >=
|
2130
|
+
for word in re.split(fr'({keywords}|AND|OR|JOIN|,)', str(query)):
|
2131
|
+
if len(line) >= 30:
|
2127
2132
|
result.append(line)
|
2128
2133
|
line = ''
|
2129
2134
|
line += word
|
@@ -2192,7 +2197,9 @@ class Recursive(CTE):
|
|
2192
2197
|
MAIN_TAG = '__main__'
|
2193
2198
|
|
2194
2199
|
class CTEFactory:
|
2195
|
-
|
2200
|
+
TEMPLATE_FIELD_FUNC = lambda t: t.lower()[:3] + '_id'
|
2201
|
+
|
2202
|
+
def __init__(self, txt: str, template: str = ''):
|
2196
2203
|
"""
|
2197
2204
|
SQL syntax:
|
2198
2205
|
---
|
@@ -2202,37 +2209,61 @@ class CTEFactory:
|
|
2202
2209
|
|
2203
2210
|
Cypher syntax:
|
2204
2211
|
---
|
2205
|
-
|
2206
|
-
|
2212
|
+
`cte_name`[
|
2213
|
+
Table1(field, `function$`field`:alias`, `group@`) <- Table2(field)
|
2214
|
+
]
|
2207
2215
|
"""
|
2208
|
-
|
2209
|
-
|
2210
|
-
|
2211
|
-
|
2212
|
-
|
2213
|
-
|
2214
|
-
|
2215
|
-
|
2216
|
-
|
2217
|
-
|
2218
|
-
|
2219
|
-
|
2220
|
-
|
2221
|
-
|
2222
|
-
|
2223
|
-
|
2224
|
-
|
2225
|
-
|
2226
|
-
|
2216
|
+
def put_parentheses():
|
2217
|
+
result = txt
|
2218
|
+
for found in re.findall(r'\[\d+\][^(]', result):
|
2219
|
+
item = found.strip()[:3]
|
2220
|
+
result = result.replace(item, f'{item}()')
|
2221
|
+
return result
|
2222
|
+
txt, *negative = re.split(r'(\[-\d+\])', txt)
|
2223
|
+
txt = put_parentheses()
|
2224
|
+
txt, *suffix = re.split(r'(\[\d+\])', txt, maxsplit=1)
|
2225
|
+
if template:
|
2226
|
+
for table in re.findall(r'[#](\w+)', txt):
|
2227
|
+
txt = txt.replace( f'#{table}', template.format(
|
2228
|
+
t=table, f=CTEFactory.TEMPLATE_FIELD_FUNC(table)
|
2229
|
+
) )
|
2230
|
+
self.cte_list = []
|
2231
|
+
self.main = None
|
2232
|
+
for script in txt.split(']'):
|
2233
|
+
if '(' not in script:
|
2234
|
+
script += self.replace_wildcards(''.join(suffix))
|
2235
|
+
suffix = []
|
2236
|
+
self.build_ctes(script)
|
2237
|
+
if not self.cte_list:
|
2238
|
+
return
|
2239
|
+
if not suffix and negative:
|
2240
|
+
suffix = negative
|
2241
|
+
if suffix:
|
2242
|
+
self.main = detect( self.replace_wildcards(''.join(suffix)) )
|
2243
|
+
elif not self.main:
|
2244
|
+
self.main = Select(self.cte_list[0].table_name)
|
2245
|
+
self.main.break_lines = False
|
2246
|
+
|
2247
|
+
def build_ctes(self, script: str):
|
2248
|
+
alias, *body = script.split('[')
|
2249
|
+
if body:
|
2250
|
+
script = ''.join(body)
|
2251
|
+
if not re.sub( r'\s+', '', script):
|
2252
|
+
return
|
2253
|
+
if parser_class(script) == CypherParser:
|
2254
|
+
query_list = Select.parse(script, CypherParser)
|
2255
|
+
if not body:
|
2227
2256
|
alias = '_'.join(query.table_name for query in query_list)
|
2228
|
-
|
2229
|
-
|
2230
|
-
|
2231
|
-
|
2257
|
+
related_tables = any([
|
2258
|
+
query.join_type.value for query in query_list
|
2259
|
+
])
|
2260
|
+
if related_tables:
|
2261
|
+
query_list = [ join_queries(query_list) ]
|
2262
|
+
self.cte_list += [CTE(alias, query_list)]
|
2232
2263
|
return
|
2233
|
-
summary = self.extract_subqueries(
|
2264
|
+
summary = self.extract_subqueries(script)
|
2234
2265
|
self.main = detect( summary.pop(MAIN_TAG) )
|
2235
|
-
self.cte_list
|
2266
|
+
self.cte_list += [
|
2236
2267
|
CTE(alias, [
|
2237
2268
|
Select.parse(query)[0]
|
2238
2269
|
for query in elements
|
@@ -2241,11 +2272,45 @@ class CTEFactory:
|
|
2241
2272
|
]
|
2242
2273
|
|
2243
2274
|
def __str__(self):
|
2275
|
+
if not self.main:
|
2276
|
+
return ''
|
2244
2277
|
CTE.show_query = False
|
2245
2278
|
lines = [str(cte) for cte in self.cte_list]
|
2246
2279
|
result = ',\n'.join(lines) + '\n' + str(self.main)
|
2247
2280
|
CTE.show_query = True
|
2248
2281
|
return result
|
2282
|
+
|
2283
|
+
def replace_wildcards(self, txt: str) -> str:
|
2284
|
+
ALL_FIELDS_WILDCARD = '**'
|
2285
|
+
result = ''
|
2286
|
+
names = []
|
2287
|
+
query_list = []
|
2288
|
+
cte: CTE
|
2289
|
+
for cte in self.cte_list:
|
2290
|
+
names.append(cte.table_name)
|
2291
|
+
query_list += cte.query_list
|
2292
|
+
last = 0
|
2293
|
+
for found in re.finditer(r'\[[-]*\d+\]', txt):
|
2294
|
+
pos = int(re.sub(r'\[|\]', '', found.group()))
|
2295
|
+
if pos > 0:
|
2296
|
+
pos -= 1
|
2297
|
+
start = found.start()
|
2298
|
+
result += txt[last:start] + names[pos]
|
2299
|
+
last = found.end()
|
2300
|
+
txt = result + txt[last:]
|
2301
|
+
if ALL_FIELDS_WILDCARD in txt:
|
2302
|
+
field_list = []
|
2303
|
+
for query in query_list:
|
2304
|
+
for field in query.values.get(SELECT, []):
|
2305
|
+
new_item = re.split(
|
2306
|
+
r'\bas\b|\bAS\b|[.]', field
|
2307
|
+
)[-1].strip()
|
2308
|
+
if new_item not in field_list:
|
2309
|
+
field_list.append(new_item)
|
2310
|
+
return txt.replace(ALL_FIELDS_WILDCARD, '{}'.format(
|
2311
|
+
','.join(field_list)
|
2312
|
+
))
|
2313
|
+
return txt
|
2249
2314
|
|
2250
2315
|
@staticmethod
|
2251
2316
|
def extract_subqueries(txt: str) -> dict:
|
@@ -2282,7 +2347,7 @@ class CTEFactory:
|
|
2282
2347
|
query_list = [
|
2283
2348
|
clean_subquery( expr.split() )
|
2284
2349
|
for expr in re.split(
|
2285
|
-
r'\bUNION\b', txt[start: end], re.IGNORECASE
|
2350
|
+
r'\bUNION\b', txt[start: end], flags=re.IGNORECASE
|
2286
2351
|
)
|
2287
2352
|
]
|
2288
2353
|
result[MAIN_TAG] += f' {alias} {alias}'
|
@@ -2399,8 +2464,8 @@ def parser_class(text: str) -> Parser:
|
|
2399
2464
|
PARSER_REGEX = [
|
2400
2465
|
(r'select.*from', SQLParser),
|
2401
2466
|
(r'[.](find|aggregate)[(]', MongoParser),
|
2402
|
-
(r'
|
2403
|
-
(r'^\w+[
|
2467
|
+
(r'\bmatch\b\s*[(]', Neo4JParser),
|
2468
|
+
(r'^\w+\S+[(]', CypherParser),
|
2404
2469
|
]
|
2405
2470
|
text = Parser.remove_spaces(text)
|
2406
2471
|
for regex, class_type in PARSER_REGEX:
|
@@ -2440,20 +2505,24 @@ def detect(text: str, join_method = join_queries, format: str='') -> Select | li
|
|
2440
2505
|
|
2441
2506
|
|
2442
2507
|
if __name__ == "__main__":
|
2443
|
-
|
2508
|
+
# cte = CTEFactory("""
|
2509
|
+
# Sales(year$ref_date:ref_year@, sum$quantity:qty_sold, vendor) <- Vendor(id, name:vendors_name@)
|
2510
|
+
# """)
|
2444
2511
|
cte = CTEFactory(
|
2445
|
-
|
2446
|
-
|
2447
|
-
|
2448
|
-
|
2449
|
-
|
2450
|
-
|
2451
|
-
|
2452
|
-
|
2453
|
-
|
2454
|
-
|
2455
|
-
|
2456
|
-
|
2457
|
-
|
2512
|
+
txt='''
|
2513
|
+
#Empregado #Cliente #Fornecedor
|
2514
|
+
|
2515
|
+
Todas_as_pessoas[
|
2516
|
+
[1] [2] [3]
|
2517
|
+
]
|
2518
|
+
|
2519
|
+
[-1](**, ano*) <- Meta(ano, qt_ideal)
|
2520
|
+
''',
|
2521
|
+
template='''
|
2522
|
+
Vendas_por_{t}[
|
2523
|
+
Vendas(year$data:ano@, sum$quantidade:qt_vendida,
|
2524
|
+
{f}) -> {t}(id, nome:nome_pessoa@)
|
2525
|
+
]
|
2526
|
+
'''
|
2458
2527
|
)
|
2459
2528
|
print(cte)
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: sql_blocks
|
3
|
-
Version: 1.
|
3
|
+
Version: 1.20250718
|
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
|
@@ -995,26 +995,30 @@ results...
|
|
995
995
|
|
996
996
|
#### 17.3.1 - You can also pass a Cypher script like in the example below:
|
997
997
|
|
998
|
-
cte = CTEFactory(
|
999
|
-
|
1000
|
-
|
1001
|
-
|
1002
|
-
|
1003
|
-
|
998
|
+
cte = CTEFactory("""
|
999
|
+
Annual_Sales_per_Vendor[
|
1000
|
+
Sales(
|
1001
|
+
year$ref_date:ref_year@, sum$quantity:qty_sold,
|
1002
|
+
vendor) <- Vendor(id, name:vendors_name@)
|
1003
|
+
]
|
1004
1004
|
|
1005
|
-
|
1005
|
+
[-1](**, ref_year) -> Goal(year, target)
|
1006
|
+
""")
|
1007
|
+
print(cte)
|
1006
1008
|
|
1007
1009
|
results...
|
1008
1010
|
```
|
1009
1011
|
WITH Annual_Sales_per_Vendor AS (
|
1010
|
-
SELECT ven.name as vendors_name
|
1011
|
-
,
|
1012
|
+
SELECT ven.name as vendors_name
|
1013
|
+
, Year(sal.ref_date) as ref_year
|
1014
|
+
, Sum(sal.quantity) as qty_sold
|
1015
|
+
FROM Vendor ven LEFT JOIN Sales sal ON (ven.id = sal.vendor
|
1012
1016
|
GROUP BY ven.name, ref_year
|
1013
1017
|
)
|
1014
1018
|
SELECT
|
1019
|
+
aspv.vendors_name,
|
1015
1020
|
aspv.ref_year,
|
1016
1021
|
aspv.qty_sold,
|
1017
|
-
aspv.vendors_name,
|
1018
1022
|
goa.target
|
1019
1023
|
FROM
|
1020
1024
|
Annual_Sales_per_Vendor aspv
|
@@ -0,0 +1,7 @@
|
|
1
|
+
sql_blocks/__init__.py,sha256=5ItzGCyqqa6kwY8wvF9kapyHsAiWJ7KEXCcC-OtdXKg,37
|
2
|
+
sql_blocks/sql_blocks.py,sha256=jsihV2ylEjZu_g5W4e6jtJlbmK2ZXqVJXcqKncGikNk,86290
|
3
|
+
sql_blocks-1.20250718.dist-info/LICENSE,sha256=6kbiFSfobTZ7beWiKnHpN902HgBx-Jzgcme0SvKqhKY,1091
|
4
|
+
sql_blocks-1.20250718.dist-info/METADATA,sha256=r4s-4ScclaImOIhaIo9t5F0gY6WkJdoEpGgq-YcNOeA,25345
|
5
|
+
sql_blocks-1.20250718.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
6
|
+
sql_blocks-1.20250718.dist-info/top_level.txt,sha256=57AbUvUjYNy4m1EqDaU3WHeP-uyIAfV0n8GAUp1a1YQ,11
|
7
|
+
sql_blocks-1.20250718.dist-info/RECORD,,
|
@@ -1,7 +0,0 @@
|
|
1
|
-
sql_blocks/__init__.py,sha256=5ItzGCyqqa6kwY8wvF9kapyHsAiWJ7KEXCcC-OtdXKg,37
|
2
|
-
sql_blocks/sql_blocks.py,sha256=wYBE0XgHwsWQM0Wng5w03HtGMJ5aAqIoHpr0sAvlH-A,84546
|
3
|
-
sql_blocks-1.20250714.dist-info/LICENSE,sha256=6kbiFSfobTZ7beWiKnHpN902HgBx-Jzgcme0SvKqhKY,1091
|
4
|
-
sql_blocks-1.20250714.dist-info/METADATA,sha256=CPrPfsQDAQcGlGZ2z9ajGXsj_u7UHwlZia9kDqSgvrc,25388
|
5
|
-
sql_blocks-1.20250714.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
6
|
-
sql_blocks-1.20250714.dist-info/top_level.txt,sha256=57AbUvUjYNy4m1EqDaU3WHeP-uyIAfV0n8GAUp1a1YQ,11
|
7
|
-
sql_blocks-1.20250714.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|