sql-blocks 0.2.9__tar.gz → 0.31.13__tar.gz

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: sql_blocks
3
- Version: 0.2.9
3
+ Version: 0.31.13
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
@@ -417,3 +417,102 @@ ORDER BY
417
417
  * `<-` connects to the table on the left
418
418
  * `->` connects to the table on the right
419
419
  * `^` Put the field in the ORDER BY clause
420
+ * `@` Immediately after the table name, it indicates the grouping field.
421
+ * `$` For SQL functions like **avg**$_field_, **sum**$_field_, **count**$_field_...
422
+
423
+
424
+ ---
425
+ ## `detect` function
426
+
427
+ It is useful to write a query in a few lines, without specifying the script type (cypher, mongoDB, SQL, Neo4J...)
428
+ ### Examples:
429
+
430
+ > **1 - Relationship**
431
+ ```
432
+ query = detect(
433
+ 'MATCH(c:Customer)<-[:Order]->(p:Product)RETURN c, p'
434
+ )
435
+ print(query)
436
+ ```
437
+ ##### output:
438
+ SELECT * FROM
439
+ Order ord
440
+ LEFT JOIN Customer cus ON (ord.customer_id = cus.id)
441
+ RIGHT JOIN Product pro ON (ord.product_id = pro.id)
442
+ > **2 - Grouping**
443
+ ```
444
+ query = detect(
445
+ 'People@gender(avg$age?region="SOUTH"^count$qtde)'
446
+ )
447
+ print(query)
448
+ ```
449
+ ##### output:
450
+ SELECT
451
+ peo.gender,
452
+ Avg(peo.age),
453
+ Count(*) as qtde
454
+ FROM
455
+ People peo
456
+ WHERE
457
+ peo.region = "SOUTH"
458
+ GROUP BY
459
+ peo.gender
460
+ ORDER BY
461
+ peo.qtde
462
+
463
+ > **3 - Many conditions...**
464
+ ```
465
+ print( detect('''
466
+ db.people.find({
467
+ {
468
+ $or: [
469
+ status:{$eq:"B"},
470
+ age:{$lt:50}
471
+ ]
472
+ },
473
+ age:{$gte:18}, status:{$eq:"A"}
474
+ },{
475
+ name: 1, user_id: 1
476
+ }).sort({
477
+ user_id: -1
478
+ })
479
+ ''') )
480
+ ```
481
+ #### output:
482
+ SELECT
483
+ peo.name,
484
+ peo.user_id
485
+ FROM
486
+ people peo
487
+ WHERE
488
+ ( peo. = 'B' OR peo.age < 50 ) AND
489
+ peo.age >= 18 AND
490
+ peo.status = 'A'
491
+ ORDER BY
492
+ peo.user_id DESC
493
+
494
+ > **4 - Relations with same table twice (or more)**
495
+
496
+ Automatically assigns aliases to each side of the relationship (In this example, one user invites another to add to their contact list)
497
+ ```
498
+ print( detect(
499
+ 'User(^name,id) <-Contact(requester,guest)-> User(id,name)'
500
+ # ^^^ u1 ^^^ u2
501
+ ) )
502
+ ```
503
+ SELECT
504
+ u1.name,
505
+ u2.name
506
+ FROM
507
+ Contact con
508
+ RIGHT JOIN User u2 ON (con.guest = u2.id)
509
+ LEFT JOIN User u1 ON (con.requester = u1.id)
510
+ ORDER BY
511
+ u1.name
512
+
513
+ ---
514
+ ### `translate_to` method
515
+ It consists of the inverse process of parsing: From a Select object, it returns the text to a script in any of the languages ​​below:
516
+ * QueryLanguage - default
517
+ * MongoDBLanguage
518
+ * Neo4JLanguage
@@ -402,3 +402,102 @@ ORDER BY
402
402
  * `<-` connects to the table on the left
403
403
  * `->` connects to the table on the right
404
404
  * `^` Put the field in the ORDER BY clause
405
+ * `@` Immediately after the table name, it indicates the grouping field.
406
+ * `$` For SQL functions like **avg**$_field_, **sum**$_field_, **count**$_field_...
407
+
408
+
409
+ ---
410
+ ## `detect` function
411
+
412
+ It is useful to write a query in a few lines, without specifying the script type (cypher, mongoDB, SQL, Neo4J...)
413
+ ### Examples:
414
+
415
+ > **1 - Relationship**
416
+ ```
417
+ query = detect(
418
+ 'MATCH(c:Customer)<-[:Order]->(p:Product)RETURN c, p'
419
+ )
420
+ print(query)
421
+ ```
422
+ ##### output:
423
+ SELECT * FROM
424
+ Order ord
425
+ LEFT JOIN Customer cus ON (ord.customer_id = cus.id)
426
+ RIGHT JOIN Product pro ON (ord.product_id = pro.id)
427
+ > **2 - Grouping**
428
+ ```
429
+ query = detect(
430
+ 'People@gender(avg$age?region="SOUTH"^count$qtde)'
431
+ )
432
+ print(query)
433
+ ```
434
+ ##### output:
435
+ SELECT
436
+ peo.gender,
437
+ Avg(peo.age),
438
+ Count(*) as qtde
439
+ FROM
440
+ People peo
441
+ WHERE
442
+ peo.region = "SOUTH"
443
+ GROUP BY
444
+ peo.gender
445
+ ORDER BY
446
+ peo.qtde
447
+
448
+ > **3 - Many conditions...**
449
+ ```
450
+ print( detect('''
451
+ db.people.find({
452
+ {
453
+ $or: [
454
+ status:{$eq:"B"},
455
+ age:{$lt:50}
456
+ ]
457
+ },
458
+ age:{$gte:18}, status:{$eq:"A"}
459
+ },{
460
+ name: 1, user_id: 1
461
+ }).sort({
462
+ user_id: -1
463
+ })
464
+ ''') )
465
+ ```
466
+ #### output:
467
+ SELECT
468
+ peo.name,
469
+ peo.user_id
470
+ FROM
471
+ people peo
472
+ WHERE
473
+ ( peo. = 'B' OR peo.age < 50 ) AND
474
+ peo.age >= 18 AND
475
+ peo.status = 'A'
476
+ ORDER BY
477
+ peo.user_id DESC
478
+
479
+ > **4 - Relations with same table twice (or more)**
480
+
481
+ Automatically assigns aliases to each side of the relationship (In this example, one user invites another to add to their contact list)
482
+ ```
483
+ print( detect(
484
+ 'User(^name,id) <-Contact(requester,guest)-> User(id,name)'
485
+ # ^^^ u1 ^^^ u2
486
+ ) )
487
+ ```
488
+ SELECT
489
+ u1.name,
490
+ u2.name
491
+ FROM
492
+ Contact con
493
+ RIGHT JOIN User u2 ON (con.guest = u2.id)
494
+ LEFT JOIN User u1 ON (con.requester = u1.id)
495
+ ORDER BY
496
+ u1.name
497
+
498
+ ---
499
+ ### `translate_to` method
500
+ It consists of the inverse process of parsing: From a Select object, it returns the text to a script in any of the languages ​​below:
501
+ * QueryLanguage - default
502
+ * MongoDBLanguage
503
+ * Neo4JLanguage
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "sql_blocks"
3
- version = "0.2.9"
3
+ version = "0.31.13"
4
4
  authors = [
5
5
  { name="Julio Cascalles", email="julio.cascalles@outlook.com" },
6
6
  ]
@@ -3,7 +3,7 @@ from setuptools import setup
3
3
 
4
4
  setup(
5
5
  name = 'sql_blocks',
6
- version = '0.2.9',
6
+ version = '0.31.13',
7
7
  author = 'Júlio Cascalles',
8
8
  author_email = 'julio.cascalles@outlook.com',
9
9
  packages = ['sql_blocks'],
@@ -219,6 +219,7 @@ class ForeignKey:
219
219
 
220
220
  @staticmethod
221
221
  def get_key(obj1: SQLObject, obj2: SQLObject) -> tuple:
222
+ # [To-Do] including alias will allow to relate the same table twice
222
223
  return obj1.table_name, obj2.table_name
223
224
 
224
225
  def add(self, name: str, main: SQLObject):
@@ -442,7 +443,7 @@ class QueryLanguage:
442
443
  return self.join_with_tabs(values, ' AND ')
443
444
 
444
445
  def sort_by(self, values: list) -> str:
445
- return self.join_with_tabs(values)
446
+ return self.join_with_tabs(values)
446
447
 
447
448
  def set_group(self, values: list) -> str:
448
449
  return self.join_with_tabs(values, ',')
@@ -475,8 +476,8 @@ class QueryLanguage:
475
476
  if not method or (not values and not self.has_default[key]):
476
477
  self.result[ref] = ''
477
478
  continue
478
- text = self.prefix(key) + method(values)
479
- self.result[ref] = text
479
+ text = method(values)
480
+ self.result[ref] = self.prefix(key) + text
480
481
  return self.pattern.format(**self.result).strip()
481
482
 
482
483
  class MongoDBLanguage(QueryLanguage):
@@ -572,11 +573,13 @@ class MongoDBLanguage(QueryLanguage):
572
573
 
573
574
 
574
575
  class Neo4JLanguage(QueryLanguage):
575
- pattern = 'MATCH {_from} RETURN {aliases}'
576
- has_default = {key: False for key in KEYWORD}
576
+ pattern = 'MATCH {_from}{where}RETURN {select}{order_by}'
577
+ has_default = {WHERE: False, FROM: False, ORDER_BY: True, SELECT: True}
577
578
 
578
579
  def add_field(self, values: list) -> str:
579
- return ''
580
+ if values:
581
+ return self.join_with_tabs(values, ',')
582
+ return self.TABULATION + ','.join(self.aliases.keys())
580
583
 
581
584
  def get_tables(self, values: list) -> str:
582
585
  NODE_FORMAT = dict(
@@ -589,12 +592,13 @@ class Neo4JLanguage(QueryLanguage):
589
592
  found = re.search(
590
593
  r'^(left|right)\s+', txt, re.IGNORECASE
591
594
  )
592
- pos = 'core'
595
+ pos, end, i = 'core', 0, 0
593
596
  if found:
594
597
  start, end = found.span()
595
598
  pos = txt[start:end-1].lower()
596
- tokens = re.split(r'JOIN\s+|ON\s+', txt[end:])
597
- txt = tokens[1].strip()
599
+ i = 1
600
+ tokens = re.split(r'JOIN\s+|ON\s+', txt[end:])
601
+ txt = tokens[i].strip()
598
602
  table_name, *alias = txt.split()
599
603
  if alias:
600
604
  alias = alias[0]
@@ -604,23 +608,27 @@ class Neo4JLanguage(QueryLanguage):
604
608
  if not condition:
605
609
  self.aliases[alias] = ''
606
610
  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)
611
+ return self.TABULATION + '{left}{core}{right}'.format(**nodes)
612
+
609
613
 
610
614
  def extract_conditions(self, values: list) -> str:
615
+ equalities = {}
616
+ where_list = []
611
617
  for condition in values:
612
618
  other_comparisions = any(
613
- char in condition for char in '<>%'
619
+ char in condition for char in '<>'
614
620
  )
621
+ where_list.append(condition)
615
622
  if '=' not in condition or other_comparisions:
616
- raise NotImplementedError('Only comparisons with equality are available for now.')
623
+ continue
617
624
  alias, field, const = re.split(r'[.=]', condition)
618
625
  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 ''
626
+ equalities[alias] = f'{begin}{field}:{const}{end}'
627
+ if len(equalities) == len(where_list):
628
+ self.aliases.update(equalities)
629
+ self.has_default[WHERE] = True
630
+ return self.LINE_BREAK
631
+ return self.join_with_tabs(where_list, ' AND ') + self.LINE_BREAK
624
632
 
625
633
  def set_group(self, values: list) -> str:
626
634
  return ''
@@ -628,9 +636,15 @@ class Neo4JLanguage(QueryLanguage):
628
636
  def __init__(self, target: 'Select'):
629
637
  super().__init__(target)
630
638
  self.aliases = {}
631
- self.KEYWORDS = [WHERE, FROM]
639
+ self.KEYWORDS = [WHERE, FROM, ORDER_BY, SELECT]
632
640
 
633
641
  def prefix(self, key: str):
642
+ default_prefix = any([
643
+ (key == WHERE and not self.has_default[WHERE]),
644
+ key == ORDER_BY
645
+ ])
646
+ if default_prefix:
647
+ return super().prefix(key)
634
648
  return ''
635
649
 
636
650
 
@@ -662,9 +676,10 @@ class Parser:
662
676
  return ''.join(result)
663
677
 
664
678
  def get_tokens(self, txt: str) -> list:
665
- return self.REGEX['separator'].split(
666
- self.remove_spaces(txt)
667
- )
679
+ return [
680
+ self.remove_spaces(t)
681
+ for t in self.REGEX['separator'].split(txt)
682
+ ]
668
683
 
669
684
 
670
685
  class JoinType(Enum):
@@ -748,12 +763,18 @@ class SQLParser(Parser):
748
763
  self.queries = list( result.values() )
749
764
 
750
765
 
751
- class Cypher(Parser):
766
+ class CypherParser(Parser):
752
767
  REGEX = {}
768
+ CHAR_SET = r'[(,?)^{}[\]]'
769
+ KEYWORDS = '|'.join(
770
+ fr'\s+{word}\s+'
771
+ for word in "where return WHERE RETURN and AND".split()
772
+ )
753
773
 
754
774
  def prepare(self):
755
- self.REGEX['separator'] = re.compile(r'([(,?)^{}[\]]|->|<-)')
775
+ self.REGEX['separator'] = re.compile(fr'({self.CHAR_SET}|->|<-|{self.KEYWORDS})')
756
776
  self.REGEX['condition'] = re.compile(r'(^\w+)|([<>=])')
777
+ self.REGEX['alias_pos'] = re.compile(r'(\w+)[.](\w+)')
757
778
  self.join_type = JoinType.INNER
758
779
  self.TOKEN_METHODS = {
759
780
  '(': self.add_field, '?': self.add_where,
@@ -762,24 +783,52 @@ class Cypher(Parser):
762
783
  '->': self.right_ftable,
763
784
  }
764
785
  self.method = self.new_query
786
+ self.aliases = {}
765
787
 
766
- def new_query(self, token: str, join_type = JoinType.INNER):
767
- if token.isidentifier():
768
- query = self.class_type(token)
769
- self.queries.append(query)
770
- query.join_type = join_type
788
+ def new_query(self, token: str, join_type = JoinType.INNER, alias: str=''):
789
+ token, *group_fields = token.split('@')
790
+ if not token.isidentifier():
791
+ return
792
+ table_name = f'{token} {alias}' if alias else token
793
+ query = self.class_type(table_name)
794
+ if not alias:
795
+ alias = query.alias
796
+ self.queries.append(query)
797
+ self.aliases[alias] = query
798
+ FieldList(group_fields, [Field, GroupBy]).add('', query)
799
+ query.join_type = join_type
771
800
 
772
801
  def add_where(self, token: str):
773
- field, *condition = [
774
- t for t in self.REGEX['condition'].split(token) if t
775
- ]
776
- Where(' '.join(condition)).add(field, self.queries[-1])
802
+ elements = [t for t in self.REGEX['alias_pos'].split(token) if t]
803
+ if len(elements) == 3:
804
+ alias, field, *condition = elements
805
+ query = self.aliases[alias]
806
+ else:
807
+ field, *condition = [
808
+ t for t in self.REGEX['condition'].split(token) if t
809
+ ]
810
+ query = self.queries[-1]
811
+ Where(' '.join(condition)).add(field, query)
777
812
 
778
813
  def add_order(self, token: str):
779
- FieldList(token, [Field, OrderBy]).add('', self.queries[-1])
814
+ self.add_field(token, [OrderBy])
780
815
 
781
- def add_field(self, token: str):
782
- FieldList(token, [Field]).add('', self.queries[-1])
816
+ def add_field(self, token: str, extra_classes: list['type']=[]):
817
+ if token in self.TOKEN_METHODS:
818
+ return
819
+ class_list = [Field]
820
+ if '$' in token:
821
+ func_name, token = token.split('$')
822
+ if func_name == 'count':
823
+ if not token:
824
+ token = 'count_1'
825
+ NamedField(token, Count).add('*', self.queries[-1])
826
+ class_list = []
827
+ else:
828
+ FUNCTION_CLASS = {f.__name__.lower(): f for f in Function.__subclasses__()}
829
+ class_list = [ FUNCTION_CLASS[func_name] ]
830
+ class_list += extra_classes
831
+ FieldList(token, class_list).add('', self.queries[-1])
783
832
 
784
833
  def left_ftable(self, token: str):
785
834
  if self.queries:
@@ -807,8 +856,11 @@ class Cypher(Parser):
807
856
  raise IndexError(f'Foreign Key not found for {curr.table_name}.')
808
857
  foreign_fld = curr.values[SELECT][0].split('.')[-1]
809
858
  curr.delete(foreign_fld, [SELECT])
859
+ if curr.join_type == JoinType.RIGHT:
860
+ pk_field, foreign_fld = foreign_fld, pk_field
810
861
  if curr.join_type == JoinType.RIGHT:
811
862
  curr, last = last, curr
863
+ # pk_field, foreign_fld = foreign_fld, pk_field
812
864
  k = ForeignKey.get_key(curr, last)
813
865
  ForeignKey.references[k] = (foreign_fld, pk_field)
814
866
 
@@ -831,21 +883,25 @@ class Cypher(Parser):
831
883
  self.method(token)
832
884
  if token in ')]' and has_side_table():
833
885
  self.add_foreign_key('')
834
- self.method = self.TOKEN_METHODS.get(token)
886
+ self.method = self.TOKEN_METHODS.get(token.upper())
835
887
  # ====================================
836
888
 
837
- class Neo4JParser(Cypher):
889
+ class Neo4JParser(CypherParser):
838
890
  def prepare(self):
839
891
  super().prepare()
840
892
  self.TOKEN_METHODS = {
841
- '(': self.new_query, '{': self.add_where,
842
- '<-': self.left_ftable, '->': self.right_ftable,
843
- '[': self.new_query
893
+ '(': self.new_query, '{': self.add_where, '[': self.new_query,
894
+ '<-': self.left_ftable, '->': self.right_ftable,
895
+ 'WHERE': self.add_where, 'AND': self.add_where,
844
896
  }
845
897
  self.method = None
898
+ self.aliases = {}
846
899
 
847
900
  def new_query(self, token: str, join_type = JoinType.INNER):
848
- super().new_query(token.split(':')[-1], join_type)
901
+ alias = ''
902
+ if ':' in token:
903
+ alias, token = token.split(':')
904
+ super().new_query(token, join_type, alias)
849
905
 
850
906
  def add_where(self, token: str):
851
907
  super().add_where(token.replace(':', '='))
@@ -973,6 +1029,7 @@ class MongoParser(Parser):
973
1029
  class Select(SQLObject):
974
1030
  join_type: JoinType = JoinType.INNER
975
1031
  REGEX = {}
1032
+ EQUIVALENT_NAMES = {}
976
1033
 
977
1034
  def __init__(self, table_name: str='', **values):
978
1035
  super().__init__(table_name)
@@ -988,7 +1045,7 @@ class Select(SQLObject):
988
1045
  new_tables = set([
989
1046
  '{jt}JOIN {tb} {a2} ON ({a1}.{f1} = {a2}.{f2})'.format(
990
1047
  jt=self.join_type.value,
991
- tb=self.table_name,
1048
+ tb=self.EQUIVALENT_NAMES.get(self.table_name, self.table_name),
992
1049
  a1=main.alias, f1=name,
993
1050
  a2=self.alias, f2=self.key_field
994
1051
  )
@@ -1151,3 +1208,48 @@ class RuleDateFuncReplace(Rule):
1151
1208
  temp = Select(f'{target.table_name} {target.alias}')
1152
1209
  Between(f'{year}-01-01', f'{year}-12-31').add(field, temp)
1153
1210
  target.values[WHERE][i] = ' AND '.join(temp.values[WHERE])
1211
+
1212
+
1213
+ def parser_class(text: str) -> Parser:
1214
+ PARSER_REGEX = [
1215
+ (r'select.*from', SQLParser),
1216
+ (r'[.](find|aggregate)[(]', MongoParser),
1217
+ (r'[(\[]\w*[:]\w+', Neo4JParser),
1218
+ (r'^\w+[@]*\w*[(]', CypherParser)
1219
+ ]
1220
+ text = Parser.remove_spaces(text)
1221
+ for regex, class_type in PARSER_REGEX:
1222
+ if re.findall(regex, text, re.IGNORECASE):
1223
+ return class_type
1224
+ return None
1225
+
1226
+
1227
+ def detect(text: str) -> Select:
1228
+ from collections import Counter
1229
+ parser = parser_class(text)
1230
+ if not parser:
1231
+ raise SyntaxError('Unknown parser class')
1232
+ if parser == CypherParser:
1233
+ for table, count in Counter( re.findall(r'(\w+)[(]', text) ).most_common():
1234
+ if count < 2:
1235
+ continue
1236
+ pos = [ f.span() for f in re.finditer(fr'({table})[(]', text) ]
1237
+ for begin, end in pos[::-1]:
1238
+ new_name = f'{table}_{count}' # See set_table (line 45)
1239
+ Select.EQUIVALENT_NAMES[new_name] = table
1240
+ text = text[:begin] + new_name + '(' + text[end:]
1241
+ count -= 1
1242
+ query_list = Select.parse(text, parser)
1243
+ result = query_list[0]
1244
+ for query in query_list[1:]:
1245
+ result += query
1246
+ return result
1247
+
1248
+
1249
+ if __name__ == "__main__":
1250
+ print('@'*100)
1251
+ print( detect(
1252
+ # 'User(^name?role="Manager",id)<-Contact(requester, guest)->User(id,name)'
1253
+ 'User(^name,id) <-Contact(requester,guest)-> User(id,name)'
1254
+ ) )
1255
+ print('@'*100)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: sql_blocks
3
- Version: 0.2.9
3
+ Version: 0.31.13
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
@@ -417,3 +417,102 @@ ORDER BY
417
417
  * `<-` connects to the table on the left
418
418
  * `->` connects to the table on the right
419
419
  * `^` Put the field in the ORDER BY clause
420
+ * `@` Immediately after the table name, it indicates the grouping field.
421
+ * `$` For SQL functions like **avg**$_field_, **sum**$_field_, **count**$_field_...
422
+
423
+
424
+ ---
425
+ ## `detect` function
426
+
427
+ It is useful to write a query in a few lines, without specifying the script type (cypher, mongoDB, SQL, Neo4J...)
428
+ ### Examples:
429
+
430
+ > **1 - Relationship**
431
+ ```
432
+ query = detect(
433
+ 'MATCH(c:Customer)<-[:Order]->(p:Product)RETURN c, p'
434
+ )
435
+ print(query)
436
+ ```
437
+ ##### output:
438
+ SELECT * FROM
439
+ Order ord
440
+ LEFT JOIN Customer cus ON (ord.customer_id = cus.id)
441
+ RIGHT JOIN Product pro ON (ord.product_id = pro.id)
442
+ > **2 - Grouping**
443
+ ```
444
+ query = detect(
445
+ 'People@gender(avg$age?region="SOUTH"^count$qtde)'
446
+ )
447
+ print(query)
448
+ ```
449
+ ##### output:
450
+ SELECT
451
+ peo.gender,
452
+ Avg(peo.age),
453
+ Count(*) as qtde
454
+ FROM
455
+ People peo
456
+ WHERE
457
+ peo.region = "SOUTH"
458
+ GROUP BY
459
+ peo.gender
460
+ ORDER BY
461
+ peo.qtde
462
+
463
+ > **3 - Many conditions...**
464
+ ```
465
+ print( detect('''
466
+ db.people.find({
467
+ {
468
+ $or: [
469
+ status:{$eq:"B"},
470
+ age:{$lt:50}
471
+ ]
472
+ },
473
+ age:{$gte:18}, status:{$eq:"A"}
474
+ },{
475
+ name: 1, user_id: 1
476
+ }).sort({
477
+ user_id: -1
478
+ })
479
+ ''') )
480
+ ```
481
+ #### output:
482
+ SELECT
483
+ peo.name,
484
+ peo.user_id
485
+ FROM
486
+ people peo
487
+ WHERE
488
+ ( peo. = 'B' OR peo.age < 50 ) AND
489
+ peo.age >= 18 AND
490
+ peo.status = 'A'
491
+ ORDER BY
492
+ peo.user_id DESC
493
+
494
+ > **4 - Relations with same table twice (or more)**
495
+
496
+ Automatically assigns aliases to each side of the relationship (In this example, one user invites another to add to their contact list)
497
+ ```
498
+ print( detect(
499
+ 'User(^name,id) <-Contact(requester,guest)-> User(id,name)'
500
+ # ^^^ u1 ^^^ u2
501
+ ) )
502
+ ```
503
+ SELECT
504
+ u1.name,
505
+ u2.name
506
+ FROM
507
+ Contact con
508
+ RIGHT JOIN User u2 ON (con.guest = u2.id)
509
+ LEFT JOIN User u1 ON (con.requester = u1.id)
510
+ ORDER BY
511
+ u1.name
512
+
513
+ ---
514
+ ### `translate_to` method
515
+ It consists of the inverse process of parsing: From a Select object, it returns the text to a script in any of the languages ​​below:
516
+ * QueryLanguage - default
517
+ * MongoDBLanguage
518
+ * Neo4JLanguage
File without changes
File without changes