sql-blocks 1.25.47999999999__py3-none-any.whl → 1.25.514999999999__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
@@ -99,10 +99,11 @@ class SQLObject:
99
99
  for fld in source:
100
100
  result += re.split(r'([=()]|<>|\s+ON\s+|\s+on\s+)', fld)
101
101
  return result
102
- def cleanup(fld: str) -> str:
102
+ def cleanup(text: str) -> str:
103
+ text = re.sub(r'[\n\t]', ' ', text)
103
104
  if exact:
104
- fld = fld.lower()
105
- return fld.strip()
105
+ text = text.lower()
106
+ return text.strip()
106
107
  def field_set(source: list) -> set:
107
108
  return set(
108
109
  (
@@ -510,14 +511,16 @@ class ForeignKey:
510
511
 
511
512
  def quoted(value) -> str:
512
513
  if isinstance(value, str):
514
+ if re.search(r'\bor\b', value, re.IGNORECASE):
515
+ raise PermissionError('Possible SQL injection attempt')
513
516
  value = f"'{value}'"
514
517
  return str(value)
515
518
 
516
519
 
517
520
  class Position(Enum):
521
+ StartsWith = -1
518
522
  Middle = 0
519
- StartsWith = 1
520
- EndsWith = 2
523
+ EndsWith = 1
521
524
 
522
525
 
523
526
  class Where:
@@ -535,7 +538,9 @@ class Where:
535
538
  return cls.__constructor('=', value)
536
539
 
537
540
  @classmethod
538
- def contains(cls, text: str, pos: Position = Position.Middle):
541
+ def contains(cls, text: str, pos: int | Position = Position.Middle):
542
+ if isinstance(pos, int):
543
+ pos = Position(pos)
539
544
  return cls(
540
545
  "LIKE '{}{}{}'".format(
541
546
  '%' if pos != Position.StartsWith else '',
@@ -593,10 +598,11 @@ class Where:
593
598
  main.values[FROM].append(f',{query.table_name} {query.alias}')
594
599
  for key in USUAL_KEYS:
595
600
  main.update_values(key, query.values.get(key, []))
596
- main.values.setdefault(WHERE, []).append('({a1}.{f1} = {a2}.{f2})'.format(
597
- a1=main.alias, f1=name,
598
- a2=query.alias, f2=query.key_field
599
- ))
601
+ if query.key_field:
602
+ main.values.setdefault(WHERE, []).append('({a1}.{f1} = {a2}.{f2})'.format(
603
+ a1=main.alias, f1=name,
604
+ a2=query.alias, f2=query.key_field
605
+ ))
600
606
 
601
607
  def add(self, name: str, main: SQLObject):
602
608
  func_type = FUNCTION_CLASS.get(name.lower())
@@ -652,7 +658,7 @@ class Case:
652
658
  '\n'.join(
653
659
  f'\t\tWHEN {field} {cond.content} THEN {res}'
654
660
  for res, cond in self.__conditions.items()
655
- ) + f'\n\t\tELSE {default}' if default else '',
661
+ ) + (f'\n\t\tELSE {default}' if default else ''),
656
662
  name
657
663
  )
658
664
  main.values.setdefault(SELECT, []).append(name)
@@ -663,28 +669,35 @@ class Options:
663
669
  self.__children: dict = values
664
670
 
665
671
  def add(self, logical_separator: str, main: SQLObject):
666
- if logical_separator not in ('AND', 'OR'):
672
+ if logical_separator.upper() not in ('AND', 'OR'):
667
673
  raise ValueError('`logical_separator` must be AND or OR')
668
- conditions: list[str] = []
674
+ temp = Select(f'{main.table_name} {main.alias}')
669
675
  child: Where
670
676
  for field, child in self.__children.items():
671
- conditions.append(' {} {} '.format(
672
- Field.format(field, main), child.content
673
- ))
677
+ child.add(field, temp)
674
678
  main.values.setdefault(WHERE, []).append(
675
- '(' + logical_separator.join(conditions) + ')'
679
+ '(' + f'\n\t{logical_separator} '.join(temp.values[WHERE]) + ')'
676
680
  )
677
681
 
678
682
 
679
683
  class Between:
684
+ is_literal: bool = False
685
+
680
686
  def __init__(self, start, end):
681
687
  if start > end:
682
688
  start, end = end, start
683
689
  self.start = start
684
690
  self.end = end
685
691
 
692
+ def literal(self) -> Where:
693
+ return Where('BETWEEN {} AND {}'.format(
694
+ self.start, self.end
695
+ ))
696
+
686
697
  def add(self, name: str, main:SQLObject):
687
- Where.gte(self.start).add(name, main),
698
+ if self.is_literal:
699
+ return self.literal().add(name, main)
700
+ Where.gte(self.start).add(name, main)
688
701
  Where.lte(self.end).add(name, main)
689
702
 
690
703
  class SameDay(Between):
@@ -695,6 +708,19 @@ class SameDay(Between):
695
708
  )
696
709
 
697
710
 
711
+ class Range(Case):
712
+ INC_FUNCTION = lambda x: x + 1
713
+
714
+ def __init__(self, field: str, values: dict):
715
+ super().__init__(field)
716
+ start = 0
717
+ cls = self.__class__
718
+ for label, value in sorted(values.items(), key=lambda item: item[1]):
719
+ self.when(
720
+ Between(start, value).literal(), label
721
+ )
722
+ start = cls.INC_FUNCTION(value)
723
+
698
724
 
699
725
  class Clause:
700
726
  @classmethod
@@ -810,8 +836,16 @@ class QueryLanguage:
810
836
  has_default = {key: bool(key == SELECT) for key in KEYWORD}
811
837
 
812
838
  @staticmethod
813
- def remove_alias(fld: str) -> str:
814
- return ''.join(re.split(r'\w+[.]', fld))
839
+ def remove_alias(text: str) -> str:
840
+ value, sep = '', ''
841
+ text = re.sub('[\n\t]', ' ', text)
842
+ if ':' in text:
843
+ text, value = text.split(':', maxsplit=1)
844
+ sep = ':'
845
+ return '{}{}{}'.format(
846
+ ''.join(re.split(r'\w+[.]', text)),
847
+ sep, value.replace("'", '"')
848
+ )
815
849
 
816
850
  def join_with_tabs(self, values: list, sep: str='') -> str:
817
851
  sep = sep + self.TABULATION
@@ -879,7 +913,8 @@ class MongoDBLanguage(QueryLanguage):
879
913
  LOGICAL_OP_TO_MONGO_FUNC = {
880
914
  '>': '$gt', '>=': '$gte',
881
915
  '<': '$lt', '<=': '$lte',
882
- '=': '$eq', '<>': '$ne',
916
+ '=': '$eq', '<>': '$ne',
917
+ 'like': '$regex', 'LIKE': '$regex',
883
918
  }
884
919
  OPERATORS = '|'.join(op for op in LOGICAL_OP_TO_MONGO_FUNC)
885
920
  REGEX = {
@@ -932,7 +967,7 @@ class MongoDBLanguage(QueryLanguage):
932
967
  field, *op, const = tokens
933
968
  op = ''.join(op)
934
969
  expr = '{begin}{op}:{const}{end}'.format(
935
- begin='{', const=const, end='}',
970
+ begin='{', const=const.replace('%', '.*'), end='}',
936
971
  op=cls.LOGICAL_OP_TO_MONGO_FUNC[op],
937
972
  )
938
973
  where_list.append(f'{field}:{expr}')
@@ -1041,6 +1076,55 @@ class Neo4JLanguage(QueryLanguage):
1041
1076
  return ''
1042
1077
 
1043
1078
 
1079
+ class DatabricksLanguage(QueryLanguage):
1080
+ pattern = '{_from}{where}{group_by}{order_by}{select}{limit}'
1081
+ has_default = {key: bool(key == SELECT) for key in KEYWORD}
1082
+
1083
+ def __init__(self, target: 'Select'):
1084
+ super().__init__(target)
1085
+ self.aggregation_fields = []
1086
+
1087
+ def add_field(self, values: list) -> str:
1088
+ AGG_FUNCS = '|'.join(cls.__name__ for cls in Aggregate.__subclasses__())
1089
+ # --------------------------------------------------------------
1090
+ def is_agg_field(fld: str) -> bool:
1091
+ return re.findall(fr'({AGG_FUNCS})[(]', fld, re.IGNORECASE)
1092
+ # --------------------------------------------------------------
1093
+ new_values = []
1094
+ for val in values:
1095
+ if is_agg_field(val):
1096
+ self.aggregation_fields.append(val)
1097
+ else:
1098
+ new_values.append(val)
1099
+ values = new_values
1100
+ return super().add_field(values)
1101
+
1102
+ def prefix(self, key: str) -> str:
1103
+ def get_aggregate() -> str:
1104
+ return 'AGGREGATE {} '.format(
1105
+ ','.join(self.aggregation_fields)
1106
+ )
1107
+ return '{}{}{}{}{}'.format(
1108
+ '|> ' if key != FROM else '',
1109
+ self.LINE_BREAK,
1110
+ get_aggregate() if key == GROUP_BY else '',
1111
+ key, self.TABULATION
1112
+ )
1113
+
1114
+ # def get_tables(self, values: list) -> str:
1115
+ # return self.join_with_tabs(values)
1116
+
1117
+ # def extract_conditions(self, values: list) -> str:
1118
+ # return self.join_with_tabs(values, ' AND ')
1119
+
1120
+ # def sort_by(self, values: list) -> str:
1121
+ # return self.join_with_tabs(values, ',')
1122
+
1123
+ def set_group(self, values: list) -> str:
1124
+ return self.join_with_tabs(values, ',')
1125
+
1126
+
1127
+
1044
1128
  class Parser:
1045
1129
  REGEX = {}
1046
1130
 
@@ -1396,7 +1480,18 @@ class MongoParser(Parser):
1396
1480
 
1397
1481
  def begin_conditions(self, value: str):
1398
1482
  self.where_list = {}
1483
+ self.field_method = self.first_ORfield
1399
1484
  return Where
1485
+
1486
+ def first_ORfield(self, text: str):
1487
+ if text.startswith('$'):
1488
+ return
1489
+ found = re.search(r'\w+[:]', text)
1490
+ if not found:
1491
+ return
1492
+ self.field_method = None
1493
+ p1, p2 = found.span()
1494
+ self.last_field = text[p1: p2-1]
1400
1495
 
1401
1496
  def increment_brackets(self, value: str):
1402
1497
  self.brackets[value] += 1
@@ -1405,6 +1500,7 @@ class MongoParser(Parser):
1405
1500
  self.method = self.new_query
1406
1501
  self.last_field = ''
1407
1502
  self.where_list = None
1503
+ self.field_method = None
1408
1504
  self.PARAM_BY_FUNCTION = {
1409
1505
  'find': Where, 'aggregate': GroupBy, 'sort': OrderBy
1410
1506
  }
@@ -1434,13 +1530,14 @@ class MongoParser(Parser):
1434
1530
  self.close_brackets(
1435
1531
  BRACKET_PAIR[token]
1436
1532
  )
1533
+ elif self.field_method:
1534
+ self.field_method(token)
1437
1535
  self.method = self.TOKEN_METHODS.get(token)
1438
1536
  # ----------------------------
1439
1537
 
1440
1538
 
1441
1539
  class Select(SQLObject):
1442
1540
  join_type: JoinType = JoinType.INNER
1443
- REGEX = {}
1444
1541
  EQUIVALENT_NAMES = {}
1445
1542
 
1446
1543
  def __init__(self, table_name: str='', **values):
@@ -1822,3 +1919,49 @@ def detect(text: str, join_queries: bool = True, format: str='') -> Select | lis
1822
1919
  return result
1823
1920
  # ===========================================================================================//
1824
1921
 
1922
+
1923
+ if __name__ == "__main__":
1924
+ # def identifica_suspeitos() -> Select:
1925
+ # """Mostra quais pessoas tem caracteríosticas iguais à descrição do suspeito"""
1926
+ # Select.join_type = JoinType.LEFT
1927
+ # return Select(
1928
+ # 'Suspeito s', id=Field,
1929
+ # _=Where.join(
1930
+ # Select('Pessoa p',
1931
+ # OR=Options(
1932
+ # pessoa=Where('= s.id'),
1933
+ # altura=Where.formula('ABS(% - s.{f}) < 0.5'),
1934
+ # peso=Where.formula('ABS(% - s.{f}) < 0.5'),
1935
+ # cabelo=Where.formula('% = s.{f}'),
1936
+ # olhos=Where.formula('% = s.{f}'),
1937
+ # sexo=Where.formula('% = s.{f}'),
1938
+ # ),
1939
+ # nome=Field
1940
+ # )
1941
+ # )
1942
+ # )
1943
+ # query = identifica_suspeitos()
1944
+ # print('='*50)
1945
+ # print(query)
1946
+ # print('-'*50)
1947
+ script = '''
1948
+ db.people.find({
1949
+ {
1950
+ $or: [
1951
+ status:{$eq:"B"},
1952
+ age:{$lt:50}
1953
+ ]
1954
+ },
1955
+ age:{$gte:18}, status:{$eq:"A"}
1956
+ },{
1957
+ name: 1, user_id: 1
1958
+ }).sort({
1959
+ '''
1960
+ print('='*50)
1961
+ q1 = Select.parse(script, MongoParser)[0]
1962
+ print(q1)
1963
+ print('-'*50)
1964
+ q2 = q1.translate_to(MongoDBLanguage)
1965
+ print(q2)
1966
+ # print('-'*50)
1967
+ print('='*50)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: sql_blocks
3
- Version: 1.25.47999999999
3
+ Version: 1.25.514999999999
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
@@ -15,6 +15,10 @@ License-File: LICENSE
15
15
 
16
16
  # SQL_Blocks
17
17
 
18
+ ## _SQL_Blocks_ is useful for building complex SQL commands through smaller query blocks:
19
+
20
+ ---
21
+
18
22
  ### 1 - You can assemble a simple object that will then be converted into an SQL command:
19
23
 
20
24
  > a = Select('Actor') # --> SELECT * FROM Actor act
@@ -411,6 +415,31 @@ m2 = Select(
411
415
  )
412
416
  )
413
417
 
418
+ 10.1 - If the labels used in the CASE are based on ranges of values ​​in sequence, you can use the **Range class**:
419
+
420
+ query = Select(
421
+ 'People p',
422
+ age_group=Range('age',{ # <<----------
423
+ 'adult': 50,
424
+ 'teenager': 17,
425
+ 'child': 10,
426
+ 'elderly': 70,
427
+ 'young': 21,
428
+ })
429
+ )
430
+ is equivalent to...
431
+ ```
432
+ SELECT
433
+ CASE
434
+ WHEN p.age BETWEEN 0 AND 10 THEN 'child'
435
+ WHEN p.age BETWEEN 11 AND 17 THEN 'teenager'
436
+ WHEN p.age BETWEEN 18 AND 21 THEN 'young'
437
+ WHEN p.age BETWEEN 22 AND 50 THEN 'adult'
438
+ WHEN p.age BETWEEN 51 AND 70 THEN 'elderly'
439
+ END AS age_group
440
+ FROM
441
+ People p
442
+ ```
414
443
  ---
415
444
 
416
445
  ### 11 - optimize method
@@ -0,0 +1,7 @@
1
+ sql_blocks/__init__.py,sha256=5ItzGCyqqa6kwY8wvF9kapyHsAiWJ7KEXCcC-OtdXKg,37
2
+ sql_blocks/sql_blocks.py,sha256=8msHsR5Ttp8vpCJbhU7wd91IP-TboC0XAc1204kLKXE,65953
3
+ sql_blocks-1.25.514999999999.dist-info/LICENSE,sha256=6kbiFSfobTZ7beWiKnHpN902HgBx-Jzgcme0SvKqhKY,1091
4
+ sql_blocks-1.25.514999999999.dist-info/METADATA,sha256=vxHahM3KUO84oALwycgcIdR2szRmrKUo-9RjDZffWhk,22242
5
+ sql_blocks-1.25.514999999999.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
6
+ sql_blocks-1.25.514999999999.dist-info/top_level.txt,sha256=57AbUvUjYNy4m1EqDaU3WHeP-uyIAfV0n8GAUp1a1YQ,11
7
+ sql_blocks-1.25.514999999999.dist-info/RECORD,,
@@ -1,7 +0,0 @@
1
- sql_blocks/__init__.py,sha256=5ItzGCyqqa6kwY8wvF9kapyHsAiWJ7KEXCcC-OtdXKg,37
2
- sql_blocks/sql_blocks.py,sha256=Ho1Q7yej4MoDsXC9nvtnb2nsHjAk2MNi0zkWj65uYTk,61017
3
- sql_blocks-1.25.47999999999.dist-info/LICENSE,sha256=6kbiFSfobTZ7beWiKnHpN902HgBx-Jzgcme0SvKqhKY,1091
4
- sql_blocks-1.25.47999999999.dist-info/METADATA,sha256=Oh1WCd2FRe1OToEyxH4rvf7l5dREZ8JDbESunVoIFxg,21270
5
- sql_blocks-1.25.47999999999.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
6
- sql_blocks-1.25.47999999999.dist-info/top_level.txt,sha256=57AbUvUjYNy4m1EqDaU3WHeP-uyIAfV0n8GAUp1a1YQ,11
7
- sql_blocks-1.25.47999999999.dist-info/RECORD,,