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 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 '$' in token:
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 not last.values.get(SELECT):
1140
- raise IndexError(f'Primary Key not found for {last.table_name}.')
1141
- pk_field = last.values[SELECT][-1].split('.')[-1]
1142
- last.delete(pk_field, [SELECT], exact=True)
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, name: str, query_list: list[Select]):
1464
- self.name = name
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
- return 'WITH {prefix}{name} AS (\n\t{queries}\n)SELECT * FROM {name}'.format(
1483
- prefix=self.prefix, name=self.name,
1484
- queries='\nUNION ALL\n\t'.join(
1485
- self.format(q) for q in self.query_list
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
- alias = self.name[0].lower()
1495
- self.query_list[-1].values[FROM].append(f', {self.name} {alias}')
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 45)
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.30011644
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=contains(
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
- 'sql_blocks/music/data/Album.csv' album
179
- ,'sql_blocks/music/data/Singer.csv' artist
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=contains('Smith', Position.EndsWith)
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 me.name = 'Júlio Cascalles'
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 WHERE you.id = n.friend
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,,