ransacklib 1.1.0.dev3__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.dev3/ransacklib.egg-info → ransacklib-1.1.0.dev4}/PKG-INFO +1 -1
- {ransacklib-1.1.0.dev3 → ransacklib-1.1.0.dev4}/pyproject.toml +1 -1
- {ransacklib-1.1.0.dev3 → ransacklib-1.1.0.dev4}/ransack/operator.py +1 -1
- {ransacklib-1.1.0.dev3 → ransacklib-1.1.0.dev4}/ransack/transformer.py +86 -66
- {ransacklib-1.1.0.dev3 → ransacklib-1.1.0.dev4/ransacklib.egg-info}/PKG-INFO +1 -1
- {ransacklib-1.1.0.dev3 → ransacklib-1.1.0.dev4}/tests/test_transformer.py +0 -208
- {ransacklib-1.1.0.dev3 → ransacklib-1.1.0.dev4}/LICENSE +0 -0
- {ransacklib-1.1.0.dev3 → ransacklib-1.1.0.dev4}/README.rst +0 -0
- {ransacklib-1.1.0.dev3 → ransacklib-1.1.0.dev4}/ransack/__init__.py +0 -0
- {ransacklib-1.1.0.dev3 → ransacklib-1.1.0.dev4}/ransack/exceptions.py +0 -0
- {ransacklib-1.1.0.dev3 → ransacklib-1.1.0.dev4}/ransack/function.py +0 -0
- {ransacklib-1.1.0.dev3 → ransacklib-1.1.0.dev4}/ransack/parser.py +0 -0
- {ransacklib-1.1.0.dev3 → ransacklib-1.1.0.dev4}/ransack/py.typed +0 -0
- {ransacklib-1.1.0.dev3 → ransacklib-1.1.0.dev4}/ransacklib.egg-info/SOURCES.txt +0 -0
- {ransacklib-1.1.0.dev3 → ransacklib-1.1.0.dev4}/ransacklib.egg-info/dependency_links.txt +0 -0
- {ransacklib-1.1.0.dev3 → ransacklib-1.1.0.dev4}/ransacklib.egg-info/requires.txt +0 -0
- {ransacklib-1.1.0.dev3 → ransacklib-1.1.0.dev4}/ransacklib.egg-info/top_level.txt +0 -0
- {ransacklib-1.1.0.dev3 → ransacklib-1.1.0.dev4}/setup.cfg +0 -0
- {ransacklib-1.1.0.dev3 → ransacklib-1.1.0.dev4}/tests/test_operator.py +0 -0
- {ransacklib-1.1.0.dev3 → ransacklib-1.1.0.dev4}/tests/test_parser.py +0 -0
|
@@ -567,7 +567,7 @@ def _operator_map_sql(op, l_sql, r_sql, t1, t2) -> tuple[str, Any]:
|
|
|
567
567
|
(Number, Number): (f"{l_sql} / {r_sql}", Number),
|
|
568
568
|
},
|
|
569
569
|
"%": {
|
|
570
|
-
(Number, Number): (f"{l_sql} % {r_sql}",
|
|
570
|
+
(Number, Number): (f"{l_sql} % {r_sql}", Number),
|
|
571
571
|
},
|
|
572
572
|
}
|
|
573
573
|
if op == "==":
|
|
@@ -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}
|
|
857
|
-
|
|
858
|
-
def neg(self, tree: Tree) -> tuple[str, Any]:
|
|
859
|
-
value, type_ = self.visit(tree)
|
|
860
|
-
return (f"-{value}", type_)
|
|
849
|
+
return (f"%s::{cast}", [token.real_value], type(token.real_value))
|
|
861
850
|
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
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,75 +910,89 @@ 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)
|
|
924
916
|
|
|
925
|
-
|
|
917
|
+
sql, type_ = binary_operation_sql(operator, l_sql, r_sql, l_type, r_type)
|
|
926
918
|
|
|
927
|
-
|
|
919
|
+
return sql, l_params + r_params, type_
|
|
920
|
+
|
|
921
|
+
def add(self, l_tree: Tree, r_tree: Tree) -> tuple[str, list[Any], Any]:
|
|
928
922
|
return self._binary_operation("+", l_tree, r_tree)
|
|
929
923
|
|
|
930
|
-
def sub(self, l_tree: Tree, r_tree: Tree) -> tuple[str, Any]:
|
|
924
|
+
def sub(self, l_tree: Tree, r_tree: Tree) -> tuple[str, list[Any], Any]:
|
|
931
925
|
return self._binary_operation("-", l_tree, r_tree)
|
|
932
926
|
|
|
933
|
-
def mul(self, l_tree: Tree, r_tree: Tree) -> tuple[str, Any]:
|
|
927
|
+
def mul(self, l_tree: Tree, r_tree: Tree) -> tuple[str, list[Any], Any]:
|
|
934
928
|
return self._binary_operation("*", l_tree, r_tree)
|
|
935
929
|
|
|
936
|
-
def div(self, l_tree: Tree, r_tree: Tree) -> tuple[str, Any]:
|
|
930
|
+
def div(self, l_tree: Tree, r_tree: Tree) -> tuple[str, list[Any], Any]:
|
|
937
931
|
return self._binary_operation("/", l_tree, r_tree)
|
|
938
932
|
|
|
939
|
-
def mod(self, l_tree: Tree, r_tree: Tree) -> tuple[str, Any]:
|
|
940
|
-
return self._binary_operation("
|
|
933
|
+
def mod(self, l_tree: Tree, r_tree: Tree) -> tuple[str, list[Any], Any]:
|
|
934
|
+
return self._binary_operation("%", l_tree, r_tree)
|
|
941
935
|
|
|
942
|
-
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]]:
|
|
943
937
|
return self._binary_operation("==", l_tree, r_tree)
|
|
944
938
|
|
|
945
|
-
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]]:
|
|
946
940
|
return self._binary_operation("in", l_tree, r_tree)
|
|
947
941
|
|
|
948
|
-
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]]:
|
|
949
943
|
return self._binary_operation("=", l_tree, r_tree)
|
|
950
944
|
|
|
951
|
-
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]]:
|
|
952
946
|
return self._binary_operation(">", l_tree, r_tree)
|
|
953
947
|
|
|
954
|
-
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]]:
|
|
955
949
|
return self._binary_operation(">=", l_tree, r_tree)
|
|
956
950
|
|
|
957
|
-
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]]:
|
|
958
952
|
return self._binary_operation("<", l_tree, r_tree)
|
|
959
953
|
|
|
960
|
-
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]]:
|
|
961
955
|
return self._binary_operation("<=", l_tree, r_tree)
|
|
962
956
|
|
|
963
|
-
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]]:
|
|
964
958
|
return self._binary_operation("or", l_tree, r_tree)
|
|
965
959
|
|
|
966
|
-
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]]:
|
|
967
961
|
return self._binary_operation("and", l_tree, r_tree)
|
|
968
962
|
|
|
969
|
-
def not_op(self, tree: Tree) -> tuple[str, type[bool]]:
|
|
970
|
-
sql, type_ = self.visit(tree)
|
|
971
|
-
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_
|
|
972
966
|
|
|
973
|
-
def contains_op(
|
|
967
|
+
def contains_op(
|
|
968
|
+
self, l_tree: Tree, r_tree: Tree
|
|
969
|
+
) -> tuple[str, list[Any], type[bool]]:
|
|
974
970
|
return self._binary_operation("contains", l_tree, r_tree)
|
|
975
971
|
|
|
976
|
-
def exists_op(self, path: Token) -> tuple[str, type[bool]]:
|
|
977
|
-
|
|
978
|
-
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
|
|
979
975
|
|
|
980
976
|
def exists_with_default(self, path: Token, default: Any) -> Any:
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
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)
|
|
984
993
|
|
|
985
|
-
def function_(self, name: TokenWrapper, args: Tree | None) -> tuple[str, Any]:
|
|
986
|
-
function_name = cast("str", name.real_value)
|
|
987
|
-
args = [self.visit(x) for x in (args.children if args else [])] # type: ignore
|
|
988
994
|
if function_name == "now":
|
|
989
|
-
return "localtimestamp", datetime
|
|
995
|
+
return "localtimestamp", [], datetime
|
|
990
996
|
raise EvaluationError(
|
|
991
997
|
f"Function '{name.real_value}' was not found.",
|
|
992
998
|
line=name.line,
|
|
@@ -996,3 +1002,17 @@ class SQLInterpreter(Interpreter):
|
|
|
996
1002
|
end_column=name.end_column,
|
|
997
1003
|
end_pos=name.end_pos,
|
|
998
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
|