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.
- {ransacklib-1.1.0.dev2/ransacklib.egg-info → ransacklib-1.1.0.dev4}/PKG-INFO +1 -1
- {ransacklib-1.1.0.dev2 → ransacklib-1.1.0.dev4}/pyproject.toml +1 -1
- {ransacklib-1.1.0.dev2 → ransacklib-1.1.0.dev4}/ransack/operator.py +26 -0
- {ransacklib-1.1.0.dev2 → ransacklib-1.1.0.dev4}/ransack/transformer.py +106 -56
- {ransacklib-1.1.0.dev2 → ransacklib-1.1.0.dev4/ransacklib.egg-info}/PKG-INFO +1 -1
- {ransacklib-1.1.0.dev2 → ransacklib-1.1.0.dev4}/tests/test_transformer.py +0 -208
- {ransacklib-1.1.0.dev2 → ransacklib-1.1.0.dev4}/LICENSE +0 -0
- {ransacklib-1.1.0.dev2 → ransacklib-1.1.0.dev4}/README.rst +0 -0
- {ransacklib-1.1.0.dev2 → ransacklib-1.1.0.dev4}/ransack/__init__.py +0 -0
- {ransacklib-1.1.0.dev2 → ransacklib-1.1.0.dev4}/ransack/exceptions.py +0 -0
- {ransacklib-1.1.0.dev2 → ransacklib-1.1.0.dev4}/ransack/function.py +0 -0
- {ransacklib-1.1.0.dev2 → ransacklib-1.1.0.dev4}/ransack/parser.py +0 -0
- {ransacklib-1.1.0.dev2 → ransacklib-1.1.0.dev4}/ransack/py.typed +0 -0
- {ransacklib-1.1.0.dev2 → ransacklib-1.1.0.dev4}/ransacklib.egg-info/SOURCES.txt +0 -0
- {ransacklib-1.1.0.dev2 → ransacklib-1.1.0.dev4}/ransacklib.egg-info/dependency_links.txt +0 -0
- {ransacklib-1.1.0.dev2 → ransacklib-1.1.0.dev4}/ransacklib.egg-info/requires.txt +0 -0
- {ransacklib-1.1.0.dev2 → ransacklib-1.1.0.dev4}/ransacklib.egg-info/top_level.txt +0 -0
- {ransacklib-1.1.0.dev2 → ransacklib-1.1.0.dev4}/setup.cfg +0 -0
- {ransacklib-1.1.0.dev2 → ransacklib-1.1.0.dev4}/tests/test_operator.py +0 -0
- {ransacklib-1.1.0.dev2 → ransacklib-1.1.0.dev4}/tests/test_parser.py +0 -0
|
@@ -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 (
|
|
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
|
-
|
|
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 (
|
|
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 (
|
|
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}
|
|
849
|
+
return (f"%s::{cast}", [token.real_value], type(token.real_value))
|
|
857
850
|
|
|
858
|
-
def neg(self, tree: Tree) -> tuple[str, Any]:
|
|
859
|
-
|
|
860
|
-
return (f"-{
|
|
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
|
|
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
|
|
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(
|
|
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
|
-
|
|
963
|
-
return f"{
|
|
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
|
-
|
|
967
|
-
|
|
968
|
-
return f"COALESCE({
|
|
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
|
|
@@ -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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|