sql-blocks 1.25.112__py3-none-any.whl → 1.25.1301__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
@@ -70,6 +70,10 @@ class SQLObject:
70
70
  appendix = {WHERE: r'\s+and\s+|', FROM: r'\s+join\s+|\s+JOIN\s+'}
71
71
  return KEYWORD[key][0].format(appendix.get(key, ''))
72
72
 
73
+ @staticmethod
74
+ def is_named_field(fld: str, key: str) -> bool:
75
+ return key == SELECT and re.search(r'\s+as\s+|\s+AS\s+', fld)
76
+
73
77
  def diff(self, key: str, search_list: list, exact: bool=False) -> set:
74
78
  def disassemble(source: list) -> list:
75
79
  if not exact:
@@ -82,12 +86,10 @@ class SQLObject:
82
86
  if exact:
83
87
  fld = fld.lower()
84
88
  return fld.strip()
85
- def is_named_field(fld: str) -> bool:
86
- return key == SELECT and re.search(r'\s+as\s+|\s+AS\s+', fld)
87
89
  def field_set(source: list) -> set:
88
90
  return set(
89
91
  (
90
- fld if is_named_field(fld) else
92
+ fld if self.is_named_field(fld, key) else
91
93
  re.sub(pattern, '', cleanup(fld))
92
94
  )
93
95
  for string in disassemble(source)
@@ -105,13 +107,16 @@ class SQLObject:
105
107
  return s1.symmetric_difference(s2)
106
108
  return s1 - s2
107
109
 
108
- def delete(self, search: str, keys: list=USUAL_KEYS):
110
+ def delete(self, search: str, keys: list=USUAL_KEYS, exact: bool=False):
111
+ if exact:
112
+ not_match = lambda item: not re.search(fr'\w*[.]*{search}$', item)
113
+ else:
114
+ not_match = lambda item: search not in item
109
115
  for key in keys:
110
- result = []
111
- for item in self.values.get(key, []):
112
- if search not in item:
113
- result.append(item)
114
- self.values[key] = result
116
+ self.values[key] = [
117
+ item for item in self.values.get(key, [])
118
+ if not_match(item)
119
+ ]
115
120
 
116
121
 
117
122
  SQL_CONST_SYSDATE = 'SYSDATE'
@@ -277,14 +282,15 @@ class Frame:
277
282
  keywords = ''
278
283
  for field, obj in args.items():
279
284
  is_valid = any([
280
- obj is class_type # or isinstance(obj, class_type)
281
- for class_type in (OrderBy, Partition)
285
+ obj is OrderBy,
286
+ obj is Partition,
287
+ isinstance(obj, Rows),
282
288
  ])
283
289
  if not is_valid:
284
290
  continue
285
291
  keywords += '{}{} {}'.format(
286
292
  '\n\t\t' if self.break_lines else ' ',
287
- obj.cls_to_str(), field
293
+ obj.cls_to_str(), field if field != '_' else ''
288
294
  )
289
295
  if keywords and self.break_lines:
290
296
  keywords += '\n\t'
@@ -556,6 +562,34 @@ class SortType(Enum):
556
562
  ASC = ''
557
563
  DESC = ' DESC'
558
564
 
565
+ class Row:
566
+ def __init__(self, value: int=0):
567
+ self.value = value
568
+
569
+ def __str__(self) -> str:
570
+ return '{} {}'.format(
571
+ 'UNBOUNDED' if self.value == 0 else self.value,
572
+ self.__class__.__name__.upper()
573
+ )
574
+
575
+ class Preceding(Row):
576
+ ...
577
+ class Following(Row):
578
+ ...
579
+ class Current(Row):
580
+ def __str__(self) -> str:
581
+ return 'CURRENT ROW'
582
+
583
+ class Rows:
584
+ def __init__(self, *rows: list[Row]):
585
+ self.rows = rows
586
+
587
+ def cls_to_str(self) -> str:
588
+ return 'ROWS {}{}'.format(
589
+ 'BETWEEN ' if len(self.rows) > 1 else '',
590
+ ' AND '.join(str(row) for row in self.rows)
591
+ )
592
+
559
593
 
560
594
  class OrderBy(Clause):
561
595
  sort: SortType = SortType.ASC
@@ -961,7 +995,7 @@ class SQLParser(Parser):
961
995
  obj.values[key] = [
962
996
  Field.format(fld, obj)
963
997
  for fld in re.split(separator, values[key])
964
- if (fld != '*' and len(tables) == 1) or obj.match(fld)
998
+ if (fld != '*' and len(tables) == 1) or obj.match(fld, key)
965
999
  ]
966
1000
  result[obj.alias] = obj
967
1001
  self.queries = list( result.values() )
@@ -1026,8 +1060,9 @@ class CypherParser(Parser):
1026
1060
  if func_name == 'count':
1027
1061
  if not token:
1028
1062
  token = 'count_1'
1029
- NamedField(token, Count).add('*', self.queries[-1])
1030
- class_list = []
1063
+ pk_field = self.queries[-1].key_field or 'id'
1064
+ Count().As(token, extra_classes).add(pk_field, self.queries[-1])
1065
+ return
1031
1066
  else:
1032
1067
  FUNCTION_CLASS = {f.__name__.lower(): f for f in Function.__subclasses__()}
1033
1068
  class_list = [ FUNCTION_CLASS[func_name] ]
@@ -1048,7 +1083,7 @@ class CypherParser(Parser):
1048
1083
  if not last.values.get(SELECT):
1049
1084
  raise IndexError(f'Primary Key not found for {last.table_name}.')
1050
1085
  pk_field = last.values[SELECT][-1].split('.')[-1]
1051
- last.delete(pk_field, [SELECT])
1086
+ last.delete(pk_field, [SELECT], exact=True)
1052
1087
  if '{}' in token:
1053
1088
  foreign_fld = token.format(
1054
1089
  last.table_name.lower()
@@ -1063,12 +1098,11 @@ class CypherParser(Parser):
1063
1098
  if fld not in curr.values.get(GROUP_BY, [])
1064
1099
  ]
1065
1100
  foreign_fld = fields[0].split('.')[-1]
1066
- curr.delete(foreign_fld, [SELECT])
1101
+ curr.delete(foreign_fld, [SELECT], exact=True)
1067
1102
  if curr.join_type == JoinType.RIGHT:
1068
1103
  pk_field, foreign_fld = foreign_fld, pk_field
1069
1104
  if curr.join_type == JoinType.RIGHT:
1070
1105
  curr, last = last, curr
1071
- # pk_field, foreign_fld = foreign_fld, pk_field
1072
1106
  k = ForeignKey.get_key(curr, last)
1073
1107
  ForeignKey.references[k] = (foreign_fld, pk_field)
1074
1108
 
@@ -1309,8 +1343,17 @@ class Select(SQLObject):
1309
1343
  self.values.setdefault(LIMIT, result)
1310
1344
  return self
1311
1345
 
1312
- def match(self, expr: str) -> bool:
1313
- return re.findall(f'\b*{self.alias}[.]', expr) != []
1346
+ def match(self, field: str, key: str) -> bool:
1347
+ '''
1348
+ Recognizes if the field is from the current table
1349
+ '''
1350
+ if key in (ORDER_BY, GROUP_BY) and '.' not in field:
1351
+ return any(
1352
+ self.is_named_field(fld, SELECT)
1353
+ for fld in self.values[SELECT]
1354
+ if field in fld
1355
+ )
1356
+ return re.findall(f'\b*{self.alias}[.]', field) != []
1314
1357
 
1315
1358
  @classmethod
1316
1359
  def parse(cls, txt: str, parser: Parser = SQLParser) -> list[SQLObject]:
@@ -1431,12 +1474,13 @@ class RuleReplaceJoinBySubselect(Rule):
1431
1474
  more_relations = any([
1432
1475
  ref[0] == query.table_name for ref in ForeignKey.references
1433
1476
  ])
1434
- invalid = any([
1477
+ keep_join = any([
1435
1478
  len( query.values.get(SELECT, []) ) > 0,
1436
1479
  len( query.values.get(WHERE, []) ) == 0,
1437
1480
  not fk_field, more_relations
1438
1481
  ])
1439
- if invalid:
1482
+ if keep_join:
1483
+ query.add(fk_field, main)
1440
1484
  continue
1441
1485
  query.__class__ = SubSelect
1442
1486
  Field.add(primary_k, query)
@@ -1460,7 +1504,7 @@ def parser_class(text: str) -> Parser:
1460
1504
  return None
1461
1505
 
1462
1506
 
1463
- def detect(text: str) -> Select:
1507
+ def detect(text: str, join_queries: bool = True) -> Select:
1464
1508
  from collections import Counter
1465
1509
  parser = parser_class(text)
1466
1510
  if not parser:
@@ -1476,9 +1520,9 @@ def detect(text: str) -> Select:
1476
1520
  text = text[:begin] + new_name + '(' + text[end:]
1477
1521
  count -= 1
1478
1522
  query_list = Select.parse(text, parser)
1523
+ if not join_queries:
1524
+ return query_list
1479
1525
  result = query_list[0]
1480
1526
  for query in query_list[1:]:
1481
1527
  result += query
1482
1528
  return result
1483
-
1484
-
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: sql_blocks
3
- Version: 1.25.112
3
+ Version: 1.25.1301
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
@@ -554,12 +554,18 @@ It consists of the inverse process of parsing: From a Select object, it returns
554
554
  ---
555
555
  ### 14 - Window Function
556
556
 
557
- Aggregation functions (Avg, Min, Max, Sum, Count) have the **over** method...
557
+ Aggregation functions (Avg, Min, Max, Sum, Count) -- or Window functions (Lead, Lag, Row_Number, Rank) -- have the **over** method...
558
558
 
559
559
  query=Select(
560
560
  'Enrollment e',
561
561
  payment=Sum().over(
562
- partition='student_id', order='due_date'
562
+ student_id=Partition, due_date=OrderBy,
563
+ # _=Rows(Current(), Following(5)),
564
+ # ^^^-------> ROWS BETWEEN CURRENT ROW AND 5 FOLLOWING
565
+ # _=Rows(Preceding(3), Following()),
566
+ # ^^^-------> ROWS BETWEEN 3 PRECEDING AND UNBOUNDED FOLLOWING
567
+ # _=Rows(Preceding(3))
568
+ # ^^^-------> ROWS 3 PRECEDING
563
569
  ).As('sum_per_student')
564
570
  )
565
571
 
@@ -0,0 +1,7 @@
1
+ sql_blocks/__init__.py,sha256=5ItzGCyqqa6kwY8wvF9kapyHsAiWJ7KEXCcC-OtdXKg,37
2
+ sql_blocks/sql_blocks.py,sha256=XDjB6YpjJ4ojsHylPt8jGCVt3dViFPKLzE_hbVJwNMo,50844
3
+ sql_blocks-1.25.1301.dist-info/LICENSE,sha256=6kbiFSfobTZ7beWiKnHpN902HgBx-Jzgcme0SvKqhKY,1091
4
+ sql_blocks-1.25.1301.dist-info/METADATA,sha256=NqoWfNLGyyB32vgldi7qEC3xI6PiJ7Z25x6K5s8yyo0,15027
5
+ sql_blocks-1.25.1301.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
6
+ sql_blocks-1.25.1301.dist-info/top_level.txt,sha256=57AbUvUjYNy4m1EqDaU3WHeP-uyIAfV0n8GAUp1a1YQ,11
7
+ sql_blocks-1.25.1301.dist-info/RECORD,,
@@ -1,7 +0,0 @@
1
- sql_blocks/__init__.py,sha256=5ItzGCyqqa6kwY8wvF9kapyHsAiWJ7KEXCcC-OtdXKg,37
2
- sql_blocks/sql_blocks.py,sha256=X13qSCvVhu-BVXdUmqXi48Z7H4THBvXHrP5zw2Hm4wM,49491
3
- sql_blocks-1.25.112.dist-info/LICENSE,sha256=6kbiFSfobTZ7beWiKnHpN902HgBx-Jzgcme0SvKqhKY,1091
4
- sql_blocks-1.25.112.dist-info/METADATA,sha256=npxMGB0xgAFt4J_IgoIaaWmVFcVoNu5lq3qnUAjhb-A,14638
5
- sql_blocks-1.25.112.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
6
- sql_blocks-1.25.112.dist-info/top_level.txt,sha256=57AbUvUjYNy4m1EqDaU3WHeP-uyIAfV0n8GAUp1a1YQ,11
7
- sql_blocks-1.25.112.dist-info/RECORD,,