sql-blocks 0.2.3__py3-none-any.whl → 0.2.6__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
@@ -9,7 +9,7 @@ DISTINCT_PREFX = '(DISTINCT|distinct)'
9
9
  KEYWORD = {
10
10
  'SELECT': (',{}', 'SELECT *', DISTINCT_PREFX),
11
11
  'FROM': ('{}', '', PATTERN_SUFFIX),
12
- 'WHERE': ('{}AND ', '', ''),
12
+ 'WHERE': ('{}AND ', '', r'["\']'),
13
13
  'GROUP BY': (',{}', '', PATTERN_SUFFIX),
14
14
  'ORDER BY': (',{}', '', PATTERN_SUFFIX),
15
15
  'LIMIT': (' ', '', ''),
@@ -69,27 +69,32 @@ class SQLObject:
69
69
  return KEYWORD[key][0].format(appendix.get(key, ''))
70
70
 
71
71
  def diff(self, key: str, search_list: list, exact: bool=False) -> set:
72
+ def disassemble(source: list) -> list:
73
+ result = []
74
+ for fld in source:
75
+ result += re.split(r'([=()]|<>|\s+ON\s+|\s+on\s+)', fld)
76
+ return result
72
77
  def cleanup(fld: str) -> str:
73
78
  if exact:
74
79
  fld = fld.lower()
75
80
  return fld.strip()
76
81
  def is_named_field(fld: str) -> bool:
77
82
  return key == SELECT and re.search(' as | AS ', fld)
78
- pattern = KEYWORD[key][2]
79
- if exact:
80
- if key == WHERE:
81
- pattern = ' '
82
- pattern += f'|{PATTERN_PREFIX}'
83
- separator = self.get_separator(key)
84
83
  def field_set(source: list) -> set:
85
84
  return set(
86
85
  (
87
86
  fld if is_named_field(fld) else
88
87
  re.sub(pattern, '', cleanup(fld))
89
88
  )
90
- for string in source
89
+ for string in disassemble(source)
91
90
  for fld in re.split(separator, string)
92
91
  )
92
+ pattern = KEYWORD[key][2]
93
+ if exact:
94
+ if key == WHERE:
95
+ pattern = ' '
96
+ pattern += f'|{PATTERN_PREFIX}'
97
+ separator = self.get_separator(key)
93
98
  s1 = field_set(search_list)
94
99
  s2 = field_set(self.values.get(key, []))
95
100
  if exact:
@@ -421,14 +426,30 @@ class Parser:
421
426
 
422
427
  def __init__(self, txt: str, class_type):
423
428
  self.queries = []
424
- if not self.REGEX:
425
- self.prepare()
429
+ self.prepare()
426
430
  self.class_type = class_type
427
431
  self.eval(txt)
428
432
 
429
433
  def eval(self, txt: str):
430
434
  ...
431
435
 
436
+ @staticmethod
437
+ def remove_spaces(script: str) -> str:
438
+ is_string = False
439
+ result = []
440
+ for token in re.split(r'(")', script):
441
+ if token == '"':
442
+ is_string = not is_string
443
+ if not is_string:
444
+ token = re.sub(r'\s+', '', token)
445
+ result.append(token)
446
+ return ''.join(result)
447
+
448
+ def get_tokens(self, txt: str) -> list:
449
+ return self.REGEX['separator'].split(
450
+ self.remove_spaces(txt)
451
+ )
452
+
432
453
 
433
454
  class SQLParser(Parser):
434
455
  REGEX = {}
@@ -506,17 +527,18 @@ class SQLParser(Parser):
506
527
 
507
528
  class Cypher(Parser):
508
529
  REGEX = {}
509
- TOKEN_METHODS = {}
510
530
 
511
531
  def prepare(self):
512
- self.REGEX['separator'] = re.compile(r'([(,?)^]|->|<-)')
532
+ self.REGEX['separator'] = re.compile(r'([(,?)^{}[\]]|->|<-)')
513
533
  self.REGEX['condition'] = re.compile(r'(^\w+)|([<>=])')
534
+ self.join_type = JoinType.INNER
514
535
  self.TOKEN_METHODS = {
515
536
  '(': self.add_field, '?': self.add_where,
516
537
  ',': self.add_field, '^': self.add_order,
517
538
  ')': self.new_query, '->': self.left_ftable,
518
539
  '<-': self.right_ftable,
519
540
  }
541
+ self.method = self.new_query
520
542
 
521
543
  def new_query(self, token: str):
522
544
  if token.isidentifier():
@@ -542,31 +564,170 @@ class Cypher(Parser):
542
564
  self.new_query(token)
543
565
  self.join_type = JoinType.RIGHT
544
566
 
545
- def add_foreign_key(self, token: str):
567
+ def add_foreign_key(self, token: str, pk_field: str=''):
546
568
  curr, last = [self.queries[i] for i in (-1, -2)]
547
- pk_field = last.values[SELECT][-1].split('.')[-1]
548
- last.delete(pk_field, [SELECT])
569
+ if not pk_field:
570
+ if not last.values.get(SELECT):
571
+ return
572
+ pk_field = last.values[SELECT][-1].split('.')[-1]
573
+ last.delete(pk_field, [SELECT])
549
574
  if self.join_type == JoinType.RIGHT:
550
575
  curr, last = last, curr
551
- pk_field, token = token, pk_field
552
- last.key_field = pk_field
576
+ if '{}' in token:
577
+ token = token.format(
578
+ curr.table_name.lower()
579
+ )
553
580
  k = ForeignKey.get_key(last, curr)
554
- ForeignKey.references[k] = (pk_field, token)
581
+ ForeignKey.references[k] = (token, pk_field)
555
582
  self.join_type = JoinType.INNER
556
583
 
557
584
  def eval(self, txt: str):
558
- self.join_type = JoinType.INNER
559
- self.method = self.new_query
560
- for token in self.REGEX['separator'].split( re.sub(r'\s+', '', txt) ):
585
+ for token in self.get_tokens(txt):
561
586
  if not token:
562
587
  continue
563
588
  if self.method:
564
589
  self.method(token)
565
- if token == '(' and self.join_type != JoinType.INNER:
590
+ if token in '([' and self.join_type != JoinType.INNER:
566
591
  self.method = self.add_foreign_key
567
592
  else:
568
593
  self.method = self.TOKEN_METHODS.get(token)
569
594
 
595
+ class Neo4JParser(Cypher):
596
+ def prepare(self):
597
+ super().prepare()
598
+ self.TOKEN_METHODS = {
599
+ '(': self.new_query, '{': self.add_where,
600
+ '->': self.left_ftable, '<-': self.right_ftable,
601
+ '[': self.new_query
602
+ }
603
+ self.method = None
604
+
605
+ def new_query(self, token: str):
606
+ super().new_query(token.split(':')[-1])
607
+
608
+ def add_where(self, token: str):
609
+ super().add_where(token.replace(':', '='))
610
+
611
+ def add_foreign_key(self, token: str, pk_field: str='') -> tuple:
612
+ self.new_query(token)
613
+ return super().add_foreign_key('{}_id', 'id')
614
+
615
+ # ----------------------------
616
+ class MongoParser(Parser):
617
+ REGEX = {}
618
+
619
+ def prepare(self):
620
+ self.REGEX['separator'] = re.compile(r'([({[\]},)])')
621
+
622
+ def new_query(self, token: str):
623
+ if not token:
624
+ return
625
+ *table, function = token.split('.')
626
+ self.param_type = self.PARAM_BY_FUNCTION.get(function)
627
+ if not self.param_type:
628
+ raise SyntaxError(f'Unknown function {function}')
629
+ if table and table[0]:
630
+ self.queries.append( self.class_type(table[-1]) )
631
+
632
+ def param_is_where(self) -> bool:
633
+ return self.param_type == Where or isinstance(self.param_type, Where)
634
+
635
+ def next_param(self, token: str):
636
+ if self.param_type == GroupBy:
637
+ self.param_type = Field
638
+ self.get_param(token)
639
+
640
+ def get_param(self, token: str):
641
+ if not ':' in token:
642
+ return
643
+ field, value = token.split(':')
644
+ is_function = field.startswith('$')
645
+ if not value and not is_function:
646
+ if self.param_is_where():
647
+ self.last_field = field
648
+ return
649
+ if self.param_is_where():
650
+ if is_function:
651
+ function = field
652
+ field = self.last_field
653
+ self.last_field = ''
654
+ else:
655
+ function = '$eq'
656
+ if '"' in value:
657
+ value = value.replace('"', '')
658
+ elif value and value[0].isnumeric():
659
+ numeric_type = float if len(value.split('.')) == 2 else int
660
+ value = numeric_type(value)
661
+ self.param_type = self.CONDITIONS[function](value)
662
+ if function == '$or':
663
+ return
664
+ elif self.param_type == GroupBy:
665
+ if field != '_id':
666
+ return
667
+ field = re.sub('"|[$]', '', value)
668
+ elif self.param_type == OrderBy and value == '-1':
669
+ OrderBy.sort = SortType.DESC
670
+ elif field.startswith('$'):
671
+ field = '{}({})'.format(
672
+ field.replace('$', ''), value
673
+ )
674
+ if self.where_list is not None and self.param_is_where():
675
+ self.where_list[field] = self.param_type
676
+ return
677
+ self.param_type.add(field, self.queries[-1])
678
+
679
+ def close_brackets(self, token: str):
680
+ self.brackets[token] -= 1
681
+ if self.param_is_where() and self.brackets[token] == 0:
682
+ if self.where_list is not None:
683
+ Options(**self.where_list).add('OR', self.queries[-1])
684
+ self.where_list = None
685
+ if token == '{':
686
+ self.param_type = Field
687
+
688
+ def begin_conditions(self, value: str):
689
+ self.where_list = {}
690
+ return Where
691
+
692
+ def increment_brackets(self, value: str):
693
+ self.brackets[value] += 1
694
+
695
+ def eval(self, txt: str):
696
+ self.method = self.new_query
697
+ self.last_field = ''
698
+ self.where_list = None
699
+ self.PARAM_BY_FUNCTION = {
700
+ 'find': Where, 'aggregate': GroupBy, 'sort': OrderBy
701
+ }
702
+ BRACKET_PAIR = {'}': '{', ']': '['}
703
+ self.brackets = {char: 0 for char in BRACKET_PAIR.values()}
704
+ self.CONDITIONS = {
705
+ '$in': lambda value: contains(value),
706
+ '$gt': lambda value: gt(value),
707
+ '$gte' : lambda value: gte(value),
708
+ '$lt': lambda value: lt(value),
709
+ '$lte' : lambda value: lte(value),
710
+ '$eq': lambda value: eq(value),
711
+ '$ne': lambda value: Not.eq(value),
712
+ '$or': self.begin_conditions,
713
+ }
714
+ self.TOKEN_METHODS = {
715
+ '{': self.get_param, ',': self.next_param, ')': self.new_query,
716
+ }
717
+ for token in self.get_tokens(txt):
718
+ if not token:
719
+ continue
720
+ if self.method:
721
+ self.method(token)
722
+ if token in self.brackets:
723
+ self.increment_brackets(token)
724
+ elif token in BRACKET_PAIR:
725
+ self.close_brackets(
726
+ BRACKET_PAIR[token]
727
+ )
728
+ self.method = self.TOKEN_METHODS.get(token)
729
+ # ----------------------------
730
+
570
731
 
571
732
  class JoinType(Enum):
572
733
  INNER = ''
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: sql_blocks
3
- Version: 0.2.3
3
+ Version: 0.2.6
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
@@ -381,3 +381,39 @@ m2 = Select(
381
381
  created_at=[Field, GroupBy, OrderBy]
382
382
  )
383
383
  ```
384
+
385
+ ### 13 - Change parser engine
386
+ ```
387
+ a, c, m = Select.parse(
388
+ """
389
+ Actor(name, id ?age = 40)
390
+ <- Cast(actor_id, movie_id) ->
391
+ Movie(id ^title)
392
+ """,
393
+ Cypher
394
+ # ^^^ recognizes syntax like Neo4J queries
395
+ )
396
+ ```
397
+
398
+ **print(a+c+m)**
399
+ ```
400
+ SELECT
401
+ act.name,
402
+ mov.title
403
+ FROM
404
+ Cast cas
405
+ JOIN Movie mov ON (cas.movie_id = mov.id)
406
+ JOIN Actor act ON (cas.actor_id = act.id)
407
+ WHERE
408
+ act.age = 40
409
+ ORDER BY
410
+ mov.title
411
+ ```
412
+ ---
413
+ > **Separators and meaning:**
414
+ * `( )` Delimits a table and its fields
415
+ * `,` Separate fields
416
+ * `?` For simple conditions (> < = <>)
417
+ * `<-` connects to the table on the left
418
+ * `->` connects to the table on the right
419
+ * `^` Put the field in the ORDER BY clause
@@ -0,0 +1,7 @@
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,,
@@ -1,7 +0,0 @@
1
- sql_blocks/__init__.py,sha256=5ItzGCyqqa6kwY8wvF9kapyHsAiWJ7KEXCcC-OtdXKg,37
2
- sql_blocks/sql_blocks.py,sha256=xWP7coiy5Tj8YU_WgtkN8J8tc9-0_BaE-bYljPdEp6o,24361
3
- sql_blocks-0.2.3.dist-info/LICENSE,sha256=6kbiFSfobTZ7beWiKnHpN902HgBx-Jzgcme0SvKqhKY,1091
4
- sql_blocks-0.2.3.dist-info/METADATA,sha256=0bRpoB8KehBZ40kf-pXyOvrVcozTUZo-BsC3Aq5xn2I,8892
5
- sql_blocks-0.2.3.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
6
- sql_blocks-0.2.3.dist-info/top_level.txt,sha256=57AbUvUjYNy4m1EqDaU3WHeP-uyIAfV0n8GAUp1a1YQ,11
7
- sql_blocks-0.2.3.dist-info/RECORD,,