sql-blocks 0.2.6__py3-none-any.whl → 0.2.9__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
@@ -7,20 +7,18 @@ PATTERN_SUFFIX = '( [A-Za-z_]+)'
7
7
  DISTINCT_PREFX = '(DISTINCT|distinct)'
8
8
 
9
9
  KEYWORD = {
10
- 'SELECT': (',{}', 'SELECT *', DISTINCT_PREFX),
11
- 'FROM': ('{}', '', PATTERN_SUFFIX),
12
- 'WHERE': ('{}AND ', '', r'["\']'),
13
- 'GROUP BY': (',{}', '', PATTERN_SUFFIX),
14
- 'ORDER BY': (',{}', '', PATTERN_SUFFIX),
15
- 'LIMIT': (' ', '', ''),
16
- }
17
- # ^ ^ ^
18
- # | | |
19
- # | | +----- pattern to compare fields
20
- # | |
21
- # | +----- default when empty (SELECT * ...)
22
- # |
23
- # +-------- separator
10
+ 'SELECT': (',{}', DISTINCT_PREFX),
11
+ 'FROM': ('{}', PATTERN_SUFFIX),
12
+ 'WHERE': ('{}AND ', ''),
13
+ 'GROUP BY': (',{}', PATTERN_SUFFIX),
14
+ 'ORDER BY': (',{}', PATTERN_SUFFIX),
15
+ 'LIMIT': (' ', ''),
16
+ }
17
+ # ^ ^
18
+ # | |
19
+ # | +----- pattern to compare fields
20
+ # |
21
+ # +-------- separator
24
22
 
25
23
  SELECT, FROM, WHERE, GROUP_BY, ORDER_BY, LIMIT = KEYWORD.keys()
26
24
  USUAL_KEYS = [SELECT, WHERE, GROUP_BY, ORDER_BY, LIMIT]
@@ -70,6 +68,8 @@ class SQLObject:
70
68
 
71
69
  def diff(self, key: str, search_list: list, exact: bool=False) -> set:
72
70
  def disassemble(source: list) -> list:
71
+ if not exact:
72
+ return source
73
73
  result = []
74
74
  for fld in source:
75
75
  result += re.split(r'([=()]|<>|\s+ON\s+|\s+on\s+)', fld)
@@ -89,10 +89,10 @@ class SQLObject:
89
89
  for string in disassemble(source)
90
90
  for fld in re.split(separator, string)
91
91
  )
92
- pattern = KEYWORD[key][2]
92
+ pattern = KEYWORD[key][1]
93
93
  if exact:
94
94
  if key == WHERE:
95
- pattern = ' '
95
+ pattern = r'["\']| '
96
96
  pattern += f'|{PATTERN_PREFIX}'
97
97
  separator = self.get_separator(key)
98
98
  s1 = field_set(search_list)
@@ -418,6 +418,222 @@ class Rule:
418
418
  def apply(cls, target: 'Select'):
419
419
  ...
420
420
 
421
+ class QueryLanguage:
422
+ pattern = '{select}{_from}{where}{group_by}{order_by}'
423
+ has_default = {key: bool(key == SELECT) for key in KEYWORD}
424
+
425
+ @staticmethod
426
+ def remove_alias(fld: str) -> str:
427
+ return ''.join(re.split(r'\w+[.]', fld))
428
+
429
+ def join_with_tabs(self, values: list, sep: str='') -> str:
430
+ sep = sep + self.TABULATION
431
+ return sep.join(v for v in values if v)
432
+
433
+ def add_field(self, values: list) -> str:
434
+ if not values:
435
+ return '*'
436
+ return self.join_with_tabs(values, ',')
437
+
438
+ def get_tables(self, values: list) -> str:
439
+ return self.join_with_tabs(values)
440
+
441
+ def extract_conditions(self, values: list) -> str:
442
+ return self.join_with_tabs(values, ' AND ')
443
+
444
+ def sort_by(self, values: list) -> str:
445
+ return self.join_with_tabs(values)
446
+
447
+ def set_group(self, values: list) -> str:
448
+ return self.join_with_tabs(values, ',')
449
+
450
+ def __init__(self, target: 'Select'):
451
+ self.KEYWORDS = [SELECT, FROM, WHERE, GROUP_BY, ORDER_BY]
452
+ self.TABULATION = '\n\t' if target.break_lines else ' '
453
+ self.LINE_BREAK = '\n' if target.break_lines else ' '
454
+ self.TOKEN_METHODS = {
455
+ SELECT: self.add_field, FROM: self.get_tables,
456
+ WHERE: self.extract_conditions,
457
+ ORDER_BY: self.sort_by, GROUP_BY: self.set_group,
458
+ }
459
+ self.result = {}
460
+ self.target = target
461
+
462
+ def pair(self, key: str) -> str:
463
+ if key == FROM:
464
+ return '_from'
465
+ return key.lower().replace(' ', '_')
466
+
467
+ def prefix(self, key: str) -> str:
468
+ return self.LINE_BREAK + key + self.TABULATION
469
+
470
+ def convert(self) -> str:
471
+ for key in self.KEYWORDS:
472
+ method = self.TOKEN_METHODS.get(key)
473
+ ref = self.pair(key)
474
+ values = self.target.values.get(key, [])
475
+ if not method or (not values and not self.has_default[key]):
476
+ self.result[ref] = ''
477
+ continue
478
+ text = self.prefix(key) + method(values)
479
+ self.result[ref] = text
480
+ return self.pattern.format(**self.result).strip()
481
+
482
+ class MongoDBLanguage(QueryLanguage):
483
+ pattern = '{_from}.{function}({where}{select}{group_by}){order_by}'
484
+ has_default = {key: False for key in KEYWORD}
485
+ LOGICAL_OP_TO_MONGO_FUNC = {
486
+ '>': '$gt', '>=': '$gte',
487
+ '<': '$lt', '<=': '$lte',
488
+ '=': '$eq', '<>': '$ne',
489
+ }
490
+ OPERATORS = '|'.join(op for op in LOGICAL_OP_TO_MONGO_FUNC)
491
+ REGEX = {
492
+ 'options': re.compile(r'\s+or\s+|\s+OR\s+'),
493
+ 'condition': re.compile(fr'({OPERATORS})')
494
+ }
495
+
496
+ def join_with_tabs(self, values: list, sep: str=',') -> str:
497
+ def format_field(fld):
498
+ return '{indent}{fld}'.format(
499
+ fld=self.remove_alias(fld),
500
+ indent=self.TABULATION
501
+ )
502
+ return '{begin}{content}{line_break}{end}'.format(
503
+ begin='{',
504
+ content= sep.join(
505
+ format_field(fld) for fld in values if fld
506
+ ),
507
+ end='}', line_break=self.LINE_BREAK,
508
+ )
509
+
510
+ def add_field(self, values: list) -> str:
511
+ if self.result['function'] == 'aggregate':
512
+ return ''
513
+ return ',{content}'.format(
514
+ content=self.join_with_tabs([f'{fld}: 1' for fld in values]),
515
+ )
516
+
517
+ def get_tables(self, values: list) -> str:
518
+ return values[0].split()[0].lower()
519
+
520
+ @classmethod
521
+ def mongo_where_list(cls, values: list) -> list:
522
+ OR_REGEX = cls.REGEX['options']
523
+ where_list = []
524
+ for condition in values:
525
+ if OR_REGEX.findall(condition):
526
+ condition = re.sub('[()]', '', condition)
527
+ expr = '{begin}$or: [{content}]{end}'.format(
528
+ content=','.join(
529
+ cls.mongo_where_list( OR_REGEX.split(condition) )
530
+ ), begin='{', end='}',
531
+ )
532
+ where_list.append(expr)
533
+ continue
534
+ tokens = cls.REGEX['condition'].split(
535
+ cls.remove_alias(condition)
536
+ )
537
+ tokens = [t.strip() for t in tokens if t]
538
+ field, *op, const = tokens
539
+ op = ''.join(op)
540
+ expr = '{begin}{op}:{const}{end}'.format(
541
+ begin='{', const=const, end='}',
542
+ op=cls.LOGICAL_OP_TO_MONGO_FUNC[op],
543
+ )
544
+ where_list.append(f'{field}:{expr}')
545
+ return where_list
546
+
547
+ def extract_conditions(self, values: list) -> str:
548
+ return self.join_with_tabs(
549
+ self.mongo_where_list(values)
550
+ )
551
+
552
+ def sort_by(self, values: list) -> str:
553
+ return ".sort({begin}{indent}{field}:{flag}{line_break}{end})".format(
554
+ begin='{', field=self.remove_alias(values[0].split()[0]),
555
+ flag=-1 if OrderBy.sort == SortType.DESC else 1,
556
+ end='}', indent=self.TABULATION, line_break=self.LINE_BREAK,
557
+ )
558
+
559
+ def set_group(self, values: list) -> str:
560
+ self.result['function'] = 'aggregate'
561
+ return '{"$group" : {_id:"$%%", count:{$sum:1}}}'.replace(
562
+ '%%', self.remove_alias( values[0] )
563
+ )
564
+
565
+ def __init__(self, target: 'Select'):
566
+ super().__init__(target)
567
+ self.result['function'] = 'find'
568
+ self.KEYWORDS = [GROUP_BY, SELECT, FROM, WHERE, ORDER_BY]
569
+
570
+ def prefix(self, key: str):
571
+ return ''
572
+
573
+
574
+ class Neo4JLanguage(QueryLanguage):
575
+ pattern = 'MATCH {_from} RETURN {aliases}'
576
+ has_default = {key: False for key in KEYWORD}
577
+
578
+ def add_field(self, values: list) -> str:
579
+ return ''
580
+
581
+ def get_tables(self, values: list) -> str:
582
+ NODE_FORMAT = dict(
583
+ left='({}:{}{})<-',
584
+ core='[{}:{}{}]',
585
+ right='->({}:{}{})'
586
+ )
587
+ nodes = {k: '' for k in NODE_FORMAT}
588
+ for txt in values:
589
+ found = re.search(
590
+ r'^(left|right)\s+', txt, re.IGNORECASE
591
+ )
592
+ pos = 'core'
593
+ if found:
594
+ start, end = found.span()
595
+ pos = txt[start:end-1].lower()
596
+ tokens = re.split(r'JOIN\s+|ON\s+', txt[end:])
597
+ txt = tokens[1].strip()
598
+ table_name, *alias = txt.split()
599
+ if alias:
600
+ alias = alias[0]
601
+ else:
602
+ alias = SQLObject.ALIAS_FUNC(table_name)
603
+ condition = self.aliases.get(alias, '')
604
+ if not condition:
605
+ self.aliases[alias] = ''
606
+ nodes[pos] = NODE_FORMAT[pos].format(alias, table_name, condition)
607
+ self.result['aliases'] = ','.join(self.aliases.keys())
608
+ return '{left}{core}{right}'.format(**nodes)
609
+
610
+ def extract_conditions(self, values: list) -> str:
611
+ for condition in values:
612
+ other_comparisions = any(
613
+ char in condition for char in '<>%'
614
+ )
615
+ if '=' not in condition or other_comparisions:
616
+ raise NotImplementedError('Only comparisons with equality are available for now.')
617
+ alias, field, const = re.split(r'[.=]', condition)
618
+ begin, end = '{', '}'
619
+ self.aliases[alias] = f'{begin}{field}:{const}{end}'
620
+ return '' # --- WHERE [*other_comparisions*] ...
621
+
622
+ def sort_by(self, values: list) -> str:
623
+ return ''
624
+
625
+ def set_group(self, values: list) -> str:
626
+ return ''
627
+
628
+ def __init__(self, target: 'Select'):
629
+ super().__init__(target)
630
+ self.aliases = {}
631
+ self.KEYWORDS = [WHERE, FROM]
632
+
633
+ def prefix(self, key: str):
634
+ return ''
635
+
636
+
421
637
  class Parser:
422
638
  REGEX = {}
423
639
 
@@ -451,6 +667,13 @@ class Parser:
451
667
  )
452
668
 
453
669
 
670
+ class JoinType(Enum):
671
+ INNER = ''
672
+ LEFT = 'LEFT '
673
+ RIGHT = 'RIGHT '
674
+ FULL = 'FULL '
675
+
676
+
454
677
  class SQLParser(Parser):
455
678
  REGEX = {}
456
679
 
@@ -535,14 +758,16 @@ class Cypher(Parser):
535
758
  self.TOKEN_METHODS = {
536
759
  '(': self.add_field, '?': self.add_where,
537
760
  ',': self.add_field, '^': self.add_order,
538
- ')': self.new_query, '->': self.left_ftable,
539
- '<-': self.right_ftable,
761
+ ')': self.new_query, '<-': self.left_ftable,
762
+ '->': self.right_ftable,
540
763
  }
541
764
  self.method = self.new_query
542
765
 
543
- def new_query(self, token: str):
766
+ def new_query(self, token: str, join_type = JoinType.INNER):
544
767
  if token.isidentifier():
545
- self.queries.append( self.class_type(token) )
768
+ query = self.class_type(token)
769
+ self.queries.append(query)
770
+ query.join_type = join_type
546
771
 
547
772
  def add_where(self, token: str):
548
773
  field, *condition = [
@@ -557,59 +782,75 @@ class Cypher(Parser):
557
782
  FieldList(token, [Field]).add('', self.queries[-1])
558
783
 
559
784
  def left_ftable(self, token: str):
785
+ if self.queries:
786
+ self.queries[-1].join_type = JoinType.LEFT
560
787
  self.new_query(token)
561
- self.join_type = JoinType.LEFT
562
788
 
563
789
  def right_ftable(self, token: str):
564
- self.new_query(token)
565
- self.join_type = JoinType.RIGHT
790
+ self.new_query(token, JoinType.RIGHT)
566
791
 
567
792
  def add_foreign_key(self, token: str, pk_field: str=''):
568
793
  curr, last = [self.queries[i] for i in (-1, -2)]
569
794
  if not pk_field:
570
795
  if not last.values.get(SELECT):
571
- return
796
+ raise IndexError(f'Primary Key not found for {last.table_name}.')
572
797
  pk_field = last.values[SELECT][-1].split('.')[-1]
573
798
  last.delete(pk_field, [SELECT])
574
- if self.join_type == JoinType.RIGHT:
575
- curr, last = last, curr
576
799
  if '{}' in token:
577
- token = token.format(
800
+ foreign_fld = token.format(
801
+ last.table_name.lower()
802
+ if last.join_type == JoinType.LEFT else
578
803
  curr.table_name.lower()
579
804
  )
580
- k = ForeignKey.get_key(last, curr)
581
- ForeignKey.references[k] = (token, pk_field)
582
- self.join_type = JoinType.INNER
805
+ else:
806
+ if not curr.values.get(SELECT):
807
+ raise IndexError(f'Foreign Key not found for {curr.table_name}.')
808
+ foreign_fld = curr.values[SELECT][0].split('.')[-1]
809
+ curr.delete(foreign_fld, [SELECT])
810
+ if curr.join_type == JoinType.RIGHT:
811
+ curr, last = last, curr
812
+ k = ForeignKey.get_key(curr, last)
813
+ ForeignKey.references[k] = (foreign_fld, pk_field)
814
+
815
+ def fk_charset(self) -> str:
816
+ return '(['
583
817
 
584
818
  def eval(self, txt: str):
819
+ # ====================================
820
+ def has_side_table() -> bool:
821
+ count = 0 if len(self.queries) < 2 else sum(
822
+ q.join_type != JoinType.INNER
823
+ for q in self.queries[-2:]
824
+ )
825
+ return count > 0
826
+ # -----------------------------------
585
827
  for token in self.get_tokens(txt):
586
- if not token:
828
+ if not token or (token in '([' and self.method):
587
829
  continue
588
830
  if self.method:
589
831
  self.method(token)
590
- if token in '([' and self.join_type != JoinType.INNER:
591
- self.method = self.add_foreign_key
592
- else:
593
- self.method = self.TOKEN_METHODS.get(token)
832
+ if token in ')]' and has_side_table():
833
+ self.add_foreign_key('')
834
+ self.method = self.TOKEN_METHODS.get(token)
835
+ # ====================================
594
836
 
595
837
  class Neo4JParser(Cypher):
596
838
  def prepare(self):
597
839
  super().prepare()
598
840
  self.TOKEN_METHODS = {
599
841
  '(': self.new_query, '{': self.add_where,
600
- '->': self.left_ftable, '<-': self.right_ftable,
842
+ '<-': self.left_ftable, '->': self.right_ftable,
601
843
  '[': self.new_query
602
844
  }
603
845
  self.method = None
604
846
 
605
- def new_query(self, token: str):
606
- super().new_query(token.split(':')[-1])
847
+ def new_query(self, token: str, join_type = JoinType.INNER):
848
+ super().new_query(token.split(':')[-1], join_type)
607
849
 
608
850
  def add_where(self, token: str):
609
851
  super().add_where(token.replace(':', '='))
610
852
 
611
853
  def add_foreign_key(self, token: str, pk_field: str='') -> tuple:
612
- self.new_query(token)
613
854
  return super().add_foreign_key('{}_id', 'id')
614
855
 
615
856
  # ----------------------------
@@ -729,12 +970,6 @@ class MongoParser(Parser):
729
970
  # ----------------------------
730
971
 
731
972
 
732
- class JoinType(Enum):
733
- INNER = ''
734
- LEFT = 'LEFT '
735
- RIGHT = 'RIGHT '
736
- FULL = 'FULL '
737
-
738
973
  class Select(SQLObject):
739
974
  join_type: JoinType = JoinType.INNER
740
975
  REGEX = {}
@@ -784,16 +1019,7 @@ class Select(SQLObject):
784
1019
  return query
785
1020
 
786
1021
  def __str__(self) -> str:
787
- TABULATION = '\n\t' if self.break_lines else ' '
788
- LINE_BREAK = '\n' if self.break_lines else ' '
789
- DEFAULT = lambda key: KEYWORD[key][1]
790
- FMT_SEP = lambda key: KEYWORD[key][0].format(TABULATION)
791
- select, _from, where, groupBy, orderBy, limit = [
792
- DEFAULT(key) if not self.values.get(key) else "{}{}{}{}".format(
793
- LINE_BREAK, key, TABULATION, FMT_SEP(key).join(self.values[key])
794
- ) for key in KEYWORD
795
- ]
796
- return f'{select}{_from}{where}{groupBy}{orderBy}{limit}'.strip()
1022
+ return self.translate_to(QueryLanguage)
797
1023
 
798
1024
  def __call__(self, **values):
799
1025
  to_list = lambda x: x if isinstance(x, list) else [x]
@@ -836,6 +1062,8 @@ class Select(SQLObject):
836
1062
  class_types += [GroupBy]
837
1063
  FieldList(fields, class_types).add('', self)
838
1064
 
1065
+ def translate_to(self, language: QueryLanguage) -> str:
1066
+ return language(self).convert()
839
1067
 
840
1068
 
841
1069
  class SelectIN(Select):
@@ -863,7 +1091,7 @@ class RuleSelectIN(Rule):
863
1091
  @classmethod
864
1092
  def apply(cls, target: Select):
865
1093
  for i, condition in enumerate(target.values[WHERE]):
866
- tokens = re.split(' or | OR ', re.sub('\n|\t|[()]', ' ', condition))
1094
+ tokens = re.split(r'\s+or\s+|\s+OR\s+', re.sub('\n|\t|[()]', ' ', condition))
867
1095
  if len(tokens) < 2:
868
1096
  continue
869
1097
  fields = [t.split('=')[0].split('.')[-1].lower().strip() for t in tokens]
@@ -909,7 +1137,7 @@ class RuleDateFuncReplace(Rule):
909
1137
  """
910
1138
  SQL algorithm by Ralff Matias
911
1139
  """
912
- REGEX = re.compile(r'(\bYEAR[(]|\byear[(]|=|[)])')
1140
+ REGEX = re.compile(r'(YEAR[(]|year[(]|=|[)])')
913
1141
 
914
1142
  @classmethod
915
1143
  def apply(cls, target: Select):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: sql_blocks
3
- Version: 0.2.6
3
+ Version: 0.2.9
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
@@ -0,0 +1,7 @@
1
+ sql_blocks/__init__.py,sha256=5ItzGCyqqa6kwY8wvF9kapyHsAiWJ7KEXCcC-OtdXKg,37
2
+ sql_blocks/sql_blocks.py,sha256=CLkYCavcOlIieQkbersjZ30P_UvjFvrjXKxMdNURDVU,38486
3
+ sql_blocks-0.2.9.dist-info/LICENSE,sha256=6kbiFSfobTZ7beWiKnHpN902HgBx-Jzgcme0SvKqhKY,1091
4
+ sql_blocks-0.2.9.dist-info/METADATA,sha256=XeK2YuLwhnYlQVq8My7GZfBJQ_gysd7pHGJTBFzwDEI,9675
5
+ sql_blocks-0.2.9.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
6
+ sql_blocks-0.2.9.dist-info/top_level.txt,sha256=57AbUvUjYNy4m1EqDaU3WHeP-uyIAfV0n8GAUp1a1YQ,11
7
+ sql_blocks-0.2.9.dist-info/RECORD,,
@@ -1,7 +0,0 @@
1
- sql_blocks/__init__.py,sha256=5ItzGCyqqa6kwY8wvF9kapyHsAiWJ7KEXCcC-OtdXKg,37
2
- sql_blocks/sql_blocks.py,sha256=IT3XUhBdOA1MawoSaw-oMm1v4yv16fDwqcu21sGnIQs,30086
3
- sql_blocks-0.2.6.dist-info/LICENSE,sha256=6kbiFSfobTZ7beWiKnHpN902HgBx-Jzgcme0SvKqhKY,1091
4
- sql_blocks-0.2.6.dist-info/METADATA,sha256=4A--jCZFBGeVn_fXcF41szBc_4ReMJrVKr9WWqJZnZA,9675
5
- sql_blocks-0.2.6.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
6
- sql_blocks-0.2.6.dist-info/top_level.txt,sha256=57AbUvUjYNy4m1EqDaU3WHeP-uyIAfV0n8GAUp1a1YQ,11
7
- sql_blocks-0.2.6.dist-info/RECORD,,