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 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
- self.class_type.format(name, main),
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 no_relation_error(self, other: SQLObject):
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.no_relation_error(other) # === raise ERROR ... ===
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.no_relation_error(other) # === raise ERROR ... ===
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) >= 50:
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
- def __init__(self, txt: str):
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
- Table1(field, `function$`field`:alias`, `group@`) <- Table2(field)
2206
- `...`MainTable(field)
2212
+ `cte_name`[
2213
+ Table1(field, `function$`field`:alias`, `group@`) <- Table2(field)
2214
+ ]
2207
2215
  """
2208
- if parser_class(txt) == CypherParser:
2209
- txt, main_script = txt.split('...')
2210
- query_list = Select.parse(txt, CypherParser)
2211
- if main_script:
2212
- main_script = ''.join(main_script)
2213
- if '(*)' in main_script:
2214
- field_list = [
2215
- re.split(
2216
- r'\bas\b|\bAS\b', field
2217
- )[-1].strip()
2218
- for query in query_list
2219
- for field in query.values.get(SELECT, [])
2220
- ]
2221
- main_script = main_script.replace('(*)', '({}, *)'.format(
2222
- ','.join(field_list)
2223
- ))
2224
- self.main = detect(main_script)
2225
- alias = self.main.table_name
2226
- else:
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
- self.main = Select(alias)
2229
- self.main.break_lines = False
2230
- query = join_queries(query_list)
2231
- self.cte_list = [CTE(alias, [query])]
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(txt)
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'[(\[]\w*[:]\w+', Neo4JParser),
2403
- (r'^\w+[@]*\w*[(]', CypherParser)
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
- OrderBy.sort = SortType.DESC
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
- "Sales(year$ref_date:ref_year@, sum$quantity:qty_sold, vendor) <- Vendor(id, name:vendors_name@)"
2446
- # ^^^ ^^^ ^^^
2447
- # | | | ^^^ ^^^
2448
- # | | | | |
2449
- # | | | Relate Sales to Vendor --------+ |
2450
- # | | | |
2451
- # | | +---- Call it `ref_year` and group it |
2452
- # | | |
2453
- # | +-- Extracts the year from the `ref_date` field |
2454
- # | |
2455
- # +--- The Sales table |
2456
- # Also groups by vendor´s name ------------------+
2457
- "...Annual_Sales_per_Vendor(*) -> Goal(^year, target)"
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.20250714
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
- "Sales(year$ref_date:ref_year@, sum$quantity:qty_sold, vendor)"
1000
- " <- Vendor(id, name:vendors_name@)"
1001
- "...Annual_Sales_per_Vendor(*) -> Goal(year, target)"
1002
- )
1003
- print(cte)
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
- ![image](https://raw.githubusercontent.com/julio-cascalles/sql_blocks/refs/heads/master/assets/CTEFactory.png)
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, Year(sal.ref_date) as ref_year
1011
- , Sum(sal.quantity) as qty_sold FROM Vendor ven LEFT JOIN Sales sal ON (ven.id = sal.vendor)
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,,