ransacklib 1.1.0.dev2__tar.gz → 1.1.0.dev4__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.
Files changed (20) hide show
  1. {ransacklib-1.1.0.dev2/ransacklib.egg-info → ransacklib-1.1.0.dev4}/PKG-INFO +1 -1
  2. {ransacklib-1.1.0.dev2 → ransacklib-1.1.0.dev4}/pyproject.toml +1 -1
  3. {ransacklib-1.1.0.dev2 → ransacklib-1.1.0.dev4}/ransack/operator.py +26 -0
  4. {ransacklib-1.1.0.dev2 → ransacklib-1.1.0.dev4}/ransack/transformer.py +106 -56
  5. {ransacklib-1.1.0.dev2 → ransacklib-1.1.0.dev4/ransacklib.egg-info}/PKG-INFO +1 -1
  6. {ransacklib-1.1.0.dev2 → ransacklib-1.1.0.dev4}/tests/test_transformer.py +0 -208
  7. {ransacklib-1.1.0.dev2 → ransacklib-1.1.0.dev4}/LICENSE +0 -0
  8. {ransacklib-1.1.0.dev2 → ransacklib-1.1.0.dev4}/README.rst +0 -0
  9. {ransacklib-1.1.0.dev2 → ransacklib-1.1.0.dev4}/ransack/__init__.py +0 -0
  10. {ransacklib-1.1.0.dev2 → ransacklib-1.1.0.dev4}/ransack/exceptions.py +0 -0
  11. {ransacklib-1.1.0.dev2 → ransacklib-1.1.0.dev4}/ransack/function.py +0 -0
  12. {ransacklib-1.1.0.dev2 → ransacklib-1.1.0.dev4}/ransack/parser.py +0 -0
  13. {ransacklib-1.1.0.dev2 → ransacklib-1.1.0.dev4}/ransack/py.typed +0 -0
  14. {ransacklib-1.1.0.dev2 → ransacklib-1.1.0.dev4}/ransacklib.egg-info/SOURCES.txt +0 -0
  15. {ransacklib-1.1.0.dev2 → ransacklib-1.1.0.dev4}/ransacklib.egg-info/dependency_links.txt +0 -0
  16. {ransacklib-1.1.0.dev2 → ransacklib-1.1.0.dev4}/ransacklib.egg-info/requires.txt +0 -0
  17. {ransacklib-1.1.0.dev2 → ransacklib-1.1.0.dev4}/ransacklib.egg-info/top_level.txt +0 -0
  18. {ransacklib-1.1.0.dev2 → ransacklib-1.1.0.dev4}/setup.cfg +0 -0
  19. {ransacklib-1.1.0.dev2 → ransacklib-1.1.0.dev4}/tests/test_operator.py +0 -0
  20. {ransacklib-1.1.0.dev2 → ransacklib-1.1.0.dev4}/tests/test_parser.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ransacklib
3
- Version: 1.1.0.dev2
3
+ Version: 1.1.0.dev4
4
4
  Summary: A modern, extensible language for manipulation with structured data
5
5
  Author-email: "Rajmund H. Hruška" <rajmund.hruska@cesnet.cz>
6
6
  License-Expression: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "ransacklib"
7
- version = "1.1.0.dev2"
7
+ version = "1.1.0.dev4"
8
8
  description = "A modern, extensible language for manipulation with structured data"
9
9
  license = "MIT"
10
10
  readme = "README.rst"
@@ -546,10 +546,36 @@ def _operator_map_sql(op, l_sql, r_sql, t1, t2) -> tuple[str, Any]:
546
546
  "or": {(bool, bool): f"{l_sql} OR {r_sql}"},
547
547
  "contains": {(str, str): f"position({r_sql} in {l_sql})>0"},
548
548
  }
549
+ d2: dict[str, dict] = {
550
+ "+": {
551
+ (Number, Number): (f"{l_sql} + {r_sql}", Number),
552
+ (datetime, timedelta): (f"{l_sql} + {r_sql}", datetime),
553
+ (timedelta, timedelta): (f"{l_sql} + {r_sql}", timedelta),
554
+ },
555
+ "-": {
556
+ (Number, Number): (f"{l_sql} - {r_sql}", Number),
557
+ (datetime, timedelta): (f"{l_sql} - {r_sql}", datetime),
558
+ (timedelta, timedelta): (f"{l_sql} - {r_sql}", timedelta),
559
+ (datetime, datetime): (f"{l_sql} - {r_sql}", timedelta),
560
+ },
561
+ "*": {
562
+ (timedelta, Number): (f"{l_sql} * {r_sql}", timedelta),
563
+ (Number, Number): (f"{l_sql} * {r_sql}", Number),
564
+ },
565
+ "/": {
566
+ (timedelta, timedelta): (f"{l_sql} / {r_sql}", timedelta),
567
+ (Number, Number): (f"{l_sql} / {r_sql}", Number),
568
+ },
569
+ "%": {
570
+ (Number, Number): (f"{l_sql} % {r_sql}", Number),
571
+ },
572
+ }
549
573
  if op == "==":
550
574
  return (f"{l_sql} = {r_sql}", bool)
551
575
  if op == "in":
552
576
  return (d["="][(t1, t2)], bool)
577
+ if op in d2:
578
+ return d2[op][(t1, t2)]
553
579
  return (d[op][(t1, t2)], bool)
554
580
 
555
581
 
@@ -810,7 +810,7 @@ class SQLInterpreter(Interpreter):
810
810
  A class providing a method for translating the query from 'ransack' language to SQL.
811
811
  """
812
812
 
813
- def to_sql(self, tree: Tree, data: dict | None = None) -> str:
813
+ def to_sql(self, tree: Tree, data: dict | None = None) -> tuple[str, list[Any]]:
814
814
  """
815
815
  Translates the given tree to PostgreSQL query using the provided data.
816
816
 
@@ -823,52 +823,42 @@ class SQLInterpreter(Interpreter):
823
823
  """
824
824
  self.data = data if data is not None else {}
825
825
 
826
- result, _ = self.visit(tree)
826
+ result, params, _ = self.visit(tree)
827
827
 
828
828
  self.data = {}
829
- return result
829
+ return result, params
830
830
 
831
- def number(self, token: TokenWrapper) -> tuple[str, type[int | float]]:
832
- return (str(token.real_value), type(token.real_value))
831
+ def number(self, token: TokenWrapper) -> tuple[str, list[Any], type[int | float]]:
832
+ return ("%s", [token.real_value], type(token.real_value))
833
833
 
834
- def datetime(self, token: TokenWrapper) -> tuple[str, type[datetime]]:
835
- if token.real_value.tzinfo is not None:
836
- type_ = "TIMESTAMP WITH TIME ZONE"
837
- else:
838
- type_ = "TIMESTAMP"
839
- return (
840
- f"{type_} '{token.real_value.isoformat()}'",
841
- type(token.real_value),
842
- )
834
+ def datetime(self, token: TokenWrapper) -> tuple[str, list[Any], type[datetime]]:
835
+ return ("%s", [token.real_value], type(token.real_value))
843
836
 
844
- def timedelta_(self, token: TokenWrapper) -> tuple[str, type[timedelta]]:
845
- return (f"INTERVAL '{token.real_value!s}'", type(token.real_value))
837
+ def timedelta_(self, token: TokenWrapper) -> tuple[str, list[Any], type[timedelta]]:
838
+ return ("%s", [token.real_value], type(token.real_value))
846
839
 
847
- def string_(self, token: TokenWrapper) -> tuple[str, type[str]]:
848
- return (f"'{token.real_value}'", str)
840
+ def string_(self, token: TokenWrapper) -> tuple[str, list[Any], type[str]]:
841
+ return ("%s", [token.real_value], type(token.real_value))
849
842
 
850
- def ip(self, token: TokenWrapper) -> tuple[str, type[IP]]:
843
+ def ip(self, token: TokenWrapper) -> tuple[str, list[Any], type[IP]]:
851
844
  match token.real_value:
852
845
  case IP4() | IP6():
853
846
  cast = "ipaddress"
854
847
  case IP4Range() | IP6Range() | IP4Net() | IP6Net():
855
848
  cast = "iprange"
856
- return (f"{cast} '{token.real_value!s}'", type(token.real_value))
849
+ return (f"%s::{cast}", [token.real_value], type(token.real_value))
857
850
 
858
- def neg(self, tree: Tree) -> tuple[str, Any]:
859
- value, type_ = self.visit(tree)
860
- return (f"-{value}", type_)
861
-
862
- @v_args(tree=True)
863
- def list(self, data: Tree) -> tuple[str, type[list]]:
864
- elements = ", ".join(self.visit(x)[0] for x in data.children if x is not None)
865
- return (f"ARRAY[{elements}]", list)
851
+ def neg(self, tree: Tree) -> tuple[str, list[Any], Any]:
852
+ sql, params, type_ = self.visit(tree)
853
+ return (f"-{sql}", params, type_)
866
854
 
867
855
  def range_op(
868
856
  self, l_tree: Tree, r_tree: Tree
869
- ) -> tuple[str, type[tuple] | type[IP]]:
870
- l_sql, l_type = self.visit(l_tree)
871
- r_sql, r_type = self.visit(r_tree)
857
+ ) -> tuple[str, list[Any], type[tuple] | type[IP]]:
858
+ l_sql, l_params, l_type = self.visit(l_tree)
859
+ r_sql, r_params, r_type = self.visit(r_tree)
860
+
861
+ params = l_params + r_params
872
862
 
873
863
  if issubclass(l_type, int) and issubclass(r_type, int):
874
864
  range_type = "int4range"
@@ -882,6 +872,7 @@ class SQLInterpreter(Interpreter):
882
872
  elif issubclass(l_type, (IP4, IP6)) and issubclass(r_type, (IP4, IP6)):
883
873
  return (
884
874
  f"iprange({l_sql}, {r_sql})",
875
+ params,
885
876
  IP4Range if issubclass(l_type, IP4) else IP6Range,
886
877
  )
887
878
  else:
@@ -901,13 +892,14 @@ class SQLInterpreter(Interpreter):
901
892
  f"THEN {range_type}({l_sql}, {r_sql}, '[]') "
902
893
  f"ELSE {range_type}({r_sql}, {l_sql}, '[]') END"
903
894
  ),
895
+ params,
904
896
  tuple,
905
897
  )
906
898
 
907
- def var_from_data(self, var: TokenWrapper) -> tuple[str, Any]:
899
+ def var_from_data(self, var: TokenWrapper) -> tuple[str, list[Any], Any]:
908
900
  if var.real_value in self.data:
909
901
  column, type_ = self.data[var.real_value]
910
- return (f'"{column}"', type_)
902
+ return (f'"{column}"', [], type_)
911
903
  raise EvaluationError(
912
904
  f"The type of column '{var.real_value}' was not defined",
913
905
  line=var.line,
@@ -918,51 +910,109 @@ class SQLInterpreter(Interpreter):
918
910
  end_pos=var.end_pos,
919
911
  )
920
912
 
921
- def _binary_operation(self, operator, l_tree, r_tree) -> tuple[str, Any]:
922
- l_sql, l_type = self.visit(l_tree)
923
- r_sql, r_type = self.visit(r_tree)
913
+ def _binary_operation(self, operator, l_tree, r_tree) -> tuple[str, list[Any], Any]:
914
+ l_sql, l_params, l_type = self.visit(l_tree)
915
+ r_sql, r_params, r_type = self.visit(r_tree)
916
+
917
+ sql, type_ = binary_operation_sql(operator, l_sql, r_sql, l_type, r_type)
924
918
 
925
- return binary_operation_sql(operator, l_sql, r_sql, l_type, r_type)
919
+ return sql, l_params + r_params, type_
920
+
921
+ def add(self, l_tree: Tree, r_tree: Tree) -> tuple[str, list[Any], Any]:
922
+ return self._binary_operation("+", l_tree, r_tree)
923
+
924
+ def sub(self, l_tree: Tree, r_tree: Tree) -> tuple[str, list[Any], Any]:
925
+ return self._binary_operation("-", l_tree, r_tree)
926
+
927
+ def mul(self, l_tree: Tree, r_tree: Tree) -> tuple[str, list[Any], Any]:
928
+ return self._binary_operation("*", l_tree, r_tree)
929
+
930
+ def div(self, l_tree: Tree, r_tree: Tree) -> tuple[str, list[Any], Any]:
931
+ return self._binary_operation("/", l_tree, r_tree)
932
+
933
+ def mod(self, l_tree: Tree, r_tree: Tree) -> tuple[str, list[Any], Any]:
934
+ return self._binary_operation("%", l_tree, r_tree)
926
935
 
927
- def eq(self, l_tree: Tree, r_tree: Tree) -> tuple[str, type[bool]]:
936
+ def eq(self, l_tree: Tree, r_tree: Tree) -> tuple[str, list[Any], type[bool]]:
928
937
  return self._binary_operation("==", l_tree, r_tree)
929
938
 
930
- def in_op(self, l_tree: Tree, r_tree: Tree) -> tuple[str, type[bool]]:
939
+ def in_op(self, l_tree: Tree, r_tree: Tree) -> tuple[str, list[Any], type[bool]]:
931
940
  return self._binary_operation("in", l_tree, r_tree)
932
941
 
933
- def any_eq(self, l_tree: Tree, r_tree: Tree) -> tuple[str, type[bool]]:
942
+ def any_eq(self, l_tree: Tree, r_tree: Tree) -> tuple[str, list[Any], type[bool]]:
934
943
  return self._binary_operation("=", l_tree, r_tree)
935
944
 
936
- def gt(self, l_tree: Tree, r_tree: Tree) -> tuple[str, type[bool]]:
945
+ def gt(self, l_tree: Tree, r_tree: Tree) -> tuple[str, list[Any], type[bool]]:
937
946
  return self._binary_operation(">", l_tree, r_tree)
938
947
 
939
- def gte(self, l_tree: Tree, r_tree: Tree) -> tuple[str, type[bool]]:
948
+ def gte(self, l_tree: Tree, r_tree: Tree) -> tuple[str, list[Any], type[bool]]:
940
949
  return self._binary_operation(">=", l_tree, r_tree)
941
950
 
942
- def lt(self, l_tree: Tree, r_tree: Tree) -> tuple[str, type[bool]]:
951
+ def lt(self, l_tree: Tree, r_tree: Tree) -> tuple[str, list[Any], type[bool]]:
943
952
  return self._binary_operation("<", l_tree, r_tree)
944
953
 
945
- def lte(self, l_tree: Tree, r_tree: Tree) -> tuple[str, type[bool]]:
954
+ def lte(self, l_tree: Tree, r_tree: Tree) -> tuple[str, list[Any], type[bool]]:
946
955
  return self._binary_operation("<=", l_tree, r_tree)
947
956
 
948
- def or_op(self, l_tree: Tree, r_tree: Tree) -> tuple[str, type[bool]]:
957
+ def or_op(self, l_tree: Tree, r_tree: Tree) -> tuple[str, list[Any], type[bool]]:
949
958
  return self._binary_operation("or", l_tree, r_tree)
950
959
 
951
- def and_op(self, l_tree: Tree, r_tree: Tree) -> tuple[str, type[bool]]:
960
+ def and_op(self, l_tree: Tree, r_tree: Tree) -> tuple[str, list[Any], type[bool]]:
952
961
  return self._binary_operation("and", l_tree, r_tree)
953
962
 
954
- def not_op(self, tree: Tree) -> tuple[str, type[bool]]:
955
- sql, type_ = self.visit(tree)
956
- return (f"NOT {sql}", type_)
963
+ def not_op(self, tree: Tree) -> tuple[str, list[Any], type[bool]]:
964
+ sql, params, type_ = self.visit(tree)
965
+ return f"NOT {sql}", params, type_
957
966
 
958
- def contains_op(self, l_tree: Tree, r_tree: Tree) -> tuple[str, type[bool]]:
967
+ def contains_op(
968
+ self, l_tree: Tree, r_tree: Tree
969
+ ) -> tuple[str, list[Any], type[bool]]:
959
970
  return self._binary_operation("contains", l_tree, r_tree)
960
971
 
961
- def exists_op(self, path: Token) -> tuple[str, type[bool]]:
962
- var, _ = self.var_from_data(TokenWrapper(path, path))
963
- return f"{var!s} is not null", bool
972
+ def exists_op(self, path: Token) -> tuple[str, list[Any], type[bool]]:
973
+ var_sql, _, _ = self.var_from_data(TokenWrapper(path, path))
974
+ return f"{var_sql} is not null", [], bool
964
975
 
965
976
  def exists_with_default(self, path: Token, default: Any) -> Any:
966
- var, _ = self.var_from_data(TokenWrapper(path, path))
967
- sql, type_ = self.visit(default)
968
- return f"COALESCE({var!s}, {sql})", type_
977
+ var_sql, _, _ = self.var_from_data(TokenWrapper(path, path))
978
+ default_sql, default_params, default_type = self.visit(default)
979
+ return f"COALESCE({var_sql}, {default_sql})", default_params, default_type
980
+
981
+ def function_(
982
+ self, name: TokenWrapper, args: Tree | None
983
+ ) -> tuple[str, list[Any], Any]:
984
+ function_name = name.real_value
985
+
986
+ args_sql = []
987
+ params = []
988
+
989
+ for arg in args.children if args else []:
990
+ sql, p, _ = self.visit(arg)
991
+ args_sql.append(sql)
992
+ params.extend(p)
993
+
994
+ if function_name == "now":
995
+ return "localtimestamp", [], datetime
996
+ raise EvaluationError(
997
+ f"Function '{name.real_value}' was not found.",
998
+ line=name.line,
999
+ column=name.column,
1000
+ start_pos=name.start_pos,
1001
+ end_line=name.end_line,
1002
+ end_column=name.end_column,
1003
+ end_pos=name.end_pos,
1004
+ )
1005
+
1006
+ @v_args(tree=True)
1007
+ def list(self, data):
1008
+ parts = []
1009
+ params = []
1010
+
1011
+ for child in data.children:
1012
+ if child is None:
1013
+ continue
1014
+ sql, p, _ = self.visit(child)
1015
+ parts.append(sql)
1016
+ params.extend(p)
1017
+
1018
+ return f"ARRAY[{', '.join(parts)}]", params, list
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ransacklib
3
- Version: 1.1.0.dev2
3
+ Version: 1.1.0.dev4
4
4
  Summary: A modern, extensible language for manipulation with structured data
5
5
  Author-email: "Rajmund H. Hruška" <rajmund.hruska@cesnet.cz>
6
6
  License-Expression: MIT
@@ -9,7 +9,6 @@ from ransack.parser import Parser
9
9
  from ransack.transformer import (
10
10
  ExpressionTransformer,
11
11
  Filter,
12
- SQLInterpreter,
13
12
  TokenWrapper,
14
13
  get_values,
15
14
  )
@@ -506,210 +505,3 @@ class TestFilter:
506
505
  exp_tree = expr_transformer.transform(parser.parse_only("my_var == 14"))
507
506
  res = filter_.transform(exp_tree)
508
507
  assert res
509
-
510
-
511
- @pytest.fixture
512
- def sql():
513
- return SQLInterpreter()
514
-
515
-
516
- class TestSQLInterpreter:
517
- def parse_to_sql(self, parser, sql, expression):
518
- tree = parser.parse(expression)
519
- data = {
520
- "a_int": ("a", int),
521
- "b_int": ("b", int),
522
- "c_int": ("c", int),
523
- "d_int": ("d", int),
524
- "Description": ("description", str),
525
- }
526
- return sql.to_sql(tree, data)
527
-
528
- @pytest.mark.parametrize(
529
- ("expression", "expected"),
530
- [
531
- # Test basic types
532
- ("5", "5"),
533
- ("-12", "-12"),
534
- ("23.22", "23.22"),
535
- ("-0.224", "-0.224"),
536
- ("13e19", "1.3e+20"),
537
- ("2025-01-01", "TIMESTAMP '2025-01-01T00:00:00'"),
538
- (
539
- "2025-01-01T22:14:24.142+02:00",
540
- "TIMESTAMP WITH TIME ZONE '2025-01-01T22:14:24.142000+02:00'",
541
- ),
542
- ("01:23:34", "INTERVAL '1:23:34'"),
543
- ("10D23:59:59", "INTERVAL '10 days, 23:59:59'"),
544
- ("99:59:59", "INTERVAL '4 days, 3:59:59'"),
545
- ("'string'", "'string'"),
546
- ("192.168.0.0", "ipaddress '192.168.0.0'"),
547
- ("192.168.0.0/16", "iprange '192.168.0.0/16'"),
548
- ("192.168.0.10-192.168.0.50", "iprange '192.168.0.10-192.168.0.50'"),
549
- ("fd00::1", "ipaddress 'fd00::1'"),
550
- ("fd00::/8", "iprange 'fd00::/8'"),
551
- ("fd00::1-fd00::10", "iprange 'fd00::1-fd00::10'"),
552
- # Test collections
553
- ("[1, 2, 3]", "ARRAY[1, 2, 3]"),
554
- (
555
- "[2024-12-12, 2025-12-12]",
556
- (
557
- "ARRAY[TIMESTAMP '2024-12-12T00:00:00', "
558
- "TIMESTAMP '2025-12-12T00:00:00']"
559
- ),
560
- ),
561
- ("[]", "ARRAY[]"),
562
- (
563
- "100..1",
564
- (
565
- "CASE WHEN 100 < 1 "
566
- "THEN int4range(100, 1, '[]') "
567
- "ELSE int4range(1, 100, '[]') END"
568
- ),
569
- ),
570
- (
571
- "100.12..1.14",
572
- (
573
- "CASE WHEN 100.12 < 1.14 "
574
- "THEN numrange(100.12, 1.14, '[]') "
575
- "ELSE numrange(1.14, 100.12, '[]') END"
576
- ),
577
- ),
578
- (
579
- "-10..10",
580
- (
581
- "CASE WHEN -10 < 10 "
582
- "THEN int4range(-10, 10, '[]') "
583
- "ELSE int4range(10, -10, '[]') END"
584
- ),
585
- ),
586
- (
587
- "2024-01-01T00:00:00+00:00..2024-12-31T00:00:00Z",
588
- (
589
- "CASE WHEN TIMESTAMP WITH TIME ZONE '2024-01-01T00:00:00+00:00' "
590
- "< TIMESTAMP WITH TIME ZONE '2024-12-31T00:00:00+00:00' THEN "
591
- "tstzrange(TIMESTAMP WITH TIME ZONE '2024-01-01T00:00:00+00:00', "
592
- "TIMESTAMP WITH TIME ZONE '2024-12-31T00:00:00+00:00', '[]') ELSE "
593
- "tstzrange(TIMESTAMP WITH TIME ZONE '2024-12-31T00:00:00+00:00', "
594
- "TIMESTAMP WITH TIME ZONE '2024-01-01T00:00:00+00:00', '[]') END"
595
- ),
596
- ),
597
- (
598
- "192.168.0.10..192.168.0.50",
599
- "iprange(ipaddress '192.168.0.10', ipaddress '192.168.0.50')",
600
- ),
601
- ("fd00::1..fd00::10", "iprange(ipaddress 'fd00::1', ipaddress 'fd00::10')"),
602
- # Test variable access
603
- ("a_int", '"a"'),
604
- (
605
- "a_int .. b_int",
606
- (
607
- 'CASE WHEN "a" < "b" '
608
- 'THEN int4range("a", "b", \'[]\') '
609
- 'ELSE int4range("b", "a", \'[]\') END'
610
- ),
611
- ),
612
- # Test = operator
613
- ("1 = 1", "1 = 1"),
614
- ("1 = [1, 2, 3]", "1 = ANY(ARRAY[1, 2, 3])"),
615
- ("[1, 2, 3] = 1", "1 = ANY(ARRAY[1, 2, 3])"),
616
- ("[1, 2] = [3, 4]", "ARRAY[1, 2] && ARRAY[3, 4]"),
617
- (
618
- "1 = 1..10",
619
- (
620
- "CASE WHEN 1 < 10 "
621
- "THEN int4range(1, 10, '[]') "
622
- "ELSE int4range(10, 1, '[]') END @> 1"
623
- ),
624
- ),
625
- (
626
- "1..10 = 1",
627
- (
628
- "CASE WHEN 1 < 10 "
629
- "THEN int4range(1, 10, '[]') "
630
- "ELSE int4range(10, 1, '[]') END @> 1"
631
- ),
632
- ),
633
- ("0D12:42:24 = 12:42:24", "INTERVAL '12:42:24' = INTERVAL '12:42:24'"),
634
- (
635
- "a_int..b_int = c_int..d_int",
636
- (
637
- 'CASE WHEN "a" < "b" '
638
- 'THEN int4range("a", "b", \'[]\') '
639
- 'ELSE int4range("b", "a", \'[]\') END '
640
- '&& CASE WHEN "c" < "d" '
641
- 'THEN int4range("c", "d", \'[]\') '
642
- 'ELSE int4range("d", "c", \'[]\') END'
643
- ),
644
- ),
645
- (
646
- "10.10.10.10 = 10.10.10.10",
647
- "ipaddress '10.10.10.10' && ipaddress '10.10.10.10'",
648
- ),
649
- (
650
- "10.10.10.10 = 10.10.10.0 .. 10.10.10.20",
651
- (
652
- "ipaddress '10.10.10.10' && "
653
- "iprange(ipaddress '10.10.10.0', ipaddress '10.10.10.20')"
654
- ),
655
- ),
656
- (
657
- "192.168.0.5 = 192.168.0.0-192.168.0.255",
658
- "ipaddress '192.168.0.5' && iprange '192.168.0.0-192.168.0.255'",
659
- ),
660
- (
661
- "192.168.0.12 = 192.168.1.0/24",
662
- "ipaddress '192.168.0.12' && iprange '192.168.1.0/24'",
663
- ),
664
- (
665
- "10.10.10.10-10.10.10.20 = 10.10.10.15-10.10.10.25",
666
- (
667
- "iprange '10.10.10.10-10.10.10.20' && "
668
- "iprange '10.10.10.15-10.10.10.25'"
669
- ),
670
- ),
671
- (
672
- "10.10.10.10-10.10.10.20 = 192.168.0.0/16",
673
- "iprange '10.10.10.10-10.10.10.20' && iprange '192.168.0.0/16'",
674
- ),
675
- (
676
- "192.168.1.0/24 = 192.168.0.0/16",
677
- "iprange '192.168.1.0/24' && iprange '192.168.0.0/16'",
678
- ),
679
- (
680
- "192.168.1.10 = [192.168.0.0/24, 192.168.0.0/16]",
681
- (
682
- "ipaddress '192.168.1.10' && "
683
- "ANY(ARRAY[iprange '192.168.0.0/24', iprange '192.168.0.0/16'])"
684
- ),
685
- ),
686
- # Test comparisons
687
- ("2 >= 2 and 4 <= 12.89 or 4 == 4", "2 >= 2 AND 4 <= 12.89 OR 4 = 4"),
688
- ("3 > 2 or 2 < 1 and 1 > 2", "3 > 2 OR 2 < 1 AND 1 > 2"),
689
- ("2 < 3 and 3 > 1 or 1 < 0", "2 < 3 AND 3 > 1 OR 1 < 0"),
690
- ("3 > 2 and not 2 < 1", "3 > 2 AND NOT 2 < 1"),
691
- ],
692
- )
693
- def test_to_sql(self, parser, sql, expression, expected):
694
- result = self.parse_to_sql(parser, sql, expression)
695
- assert result == expected
696
-
697
- def to_sql_and_expect_error(self, parser, sql, expression):
698
- """Parse the expression and expect an exception to be raised."""
699
- tree = parser.parse(expression)
700
- with pytest.raises(RansackError):
701
- sql.to_sql(tree)
702
-
703
- @pytest.mark.parametrize(
704
- "expression",
705
- [
706
- "2000-12-24..192.168.0.50", # Unsupported type of range
707
- "not_there", # Unknown column
708
- ],
709
- )
710
- def test_invalid_cases(self, parser, sql, expression):
711
- self.to_sql_and_expect_error(
712
- parser,
713
- sql,
714
- expression,
715
- )
File without changes