flowquery 1.0.43 → 1.0.44

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 (76) hide show
  1. package/dist/flowquery.min.js +1 -1
  2. package/dist/parsing/functions/join.d.ts.map +1 -1
  3. package/dist/parsing/functions/join.js +6 -3
  4. package/dist/parsing/functions/join.js.map +1 -1
  5. package/dist/parsing/functions/keys.d.ts.map +1 -1
  6. package/dist/parsing/functions/keys.js +3 -5
  7. package/dist/parsing/functions/keys.js.map +1 -1
  8. package/dist/parsing/functions/range.d.ts.map +1 -1
  9. package/dist/parsing/functions/range.js +11 -3
  10. package/dist/parsing/functions/range.js.map +1 -1
  11. package/dist/parsing/functions/replace.d.ts.map +1 -1
  12. package/dist/parsing/functions/replace.js +8 -3
  13. package/dist/parsing/functions/replace.js.map +1 -1
  14. package/dist/parsing/functions/round.d.ts.map +1 -1
  15. package/dist/parsing/functions/round.js +5 -4
  16. package/dist/parsing/functions/round.js.map +1 -1
  17. package/dist/parsing/functions/size.d.ts.map +1 -1
  18. package/dist/parsing/functions/size.js +5 -4
  19. package/dist/parsing/functions/size.js.map +1 -1
  20. package/dist/parsing/functions/split.d.ts.map +1 -1
  21. package/dist/parsing/functions/split.js +12 -4
  22. package/dist/parsing/functions/split.js.map +1 -1
  23. package/dist/parsing/functions/string_distance.d.ts.map +1 -1
  24. package/dist/parsing/functions/string_distance.js +3 -0
  25. package/dist/parsing/functions/string_distance.js.map +1 -1
  26. package/dist/parsing/functions/stringify.d.ts.map +1 -1
  27. package/dist/parsing/functions/stringify.js +7 -6
  28. package/dist/parsing/functions/stringify.js.map +1 -1
  29. package/dist/parsing/functions/substring.d.ts.map +1 -1
  30. package/dist/parsing/functions/substring.js +3 -0
  31. package/dist/parsing/functions/substring.js.map +1 -1
  32. package/dist/parsing/functions/to_json.d.ts.map +1 -1
  33. package/dist/parsing/functions/to_json.js +5 -4
  34. package/dist/parsing/functions/to_json.js.map +1 -1
  35. package/dist/parsing/functions/to_lower.d.ts.map +1 -1
  36. package/dist/parsing/functions/to_lower.js +3 -0
  37. package/dist/parsing/functions/to_lower.js.map +1 -1
  38. package/dist/parsing/functions/to_string.js +1 -1
  39. package/dist/parsing/functions/to_string.js.map +1 -1
  40. package/dist/parsing/functions/trim.d.ts.map +1 -1
  41. package/dist/parsing/functions/trim.js +3 -0
  42. package/dist/parsing/functions/trim.js.map +1 -1
  43. package/docs/flowquery.min.js +1 -1
  44. package/flowquery-py/pyproject.toml +1 -1
  45. package/flowquery-py/src/parsing/functions/join.py +2 -0
  46. package/flowquery-py/src/parsing/functions/keys.py +1 -1
  47. package/flowquery-py/src/parsing/functions/range_.py +2 -0
  48. package/flowquery-py/src/parsing/functions/replace.py +2 -0
  49. package/flowquery-py/src/parsing/functions/round_.py +2 -0
  50. package/flowquery-py/src/parsing/functions/size.py +2 -0
  51. package/flowquery-py/src/parsing/functions/split.py +2 -0
  52. package/flowquery-py/src/parsing/functions/string_distance.py +5 -1
  53. package/flowquery-py/src/parsing/functions/stringify.py +2 -0
  54. package/flowquery-py/src/parsing/functions/substring.py +2 -0
  55. package/flowquery-py/src/parsing/functions/to_json.py +2 -0
  56. package/flowquery-py/src/parsing/functions/to_lower.py +2 -0
  57. package/flowquery-py/src/parsing/functions/to_string.py +1 -1
  58. package/flowquery-py/src/parsing/functions/trim.py +2 -0
  59. package/flowquery-py/tests/compute/test_runner.py +128 -0
  60. package/flowquery-vscode/flowQueryEngine/flowquery.min.js +1 -1
  61. package/package.json +1 -1
  62. package/src/parsing/functions/join.ts +8 -5
  63. package/src/parsing/functions/keys.ts +4 -6
  64. package/src/parsing/functions/range.ts +12 -4
  65. package/src/parsing/functions/replace.ts +11 -4
  66. package/src/parsing/functions/round.ts +6 -5
  67. package/src/parsing/functions/size.ts +6 -5
  68. package/src/parsing/functions/split.ts +14 -6
  69. package/src/parsing/functions/string_distance.ts +3 -0
  70. package/src/parsing/functions/stringify.ts +9 -8
  71. package/src/parsing/functions/substring.ts +3 -0
  72. package/src/parsing/functions/to_json.ts +6 -5
  73. package/src/parsing/functions/to_lower.ts +3 -0
  74. package/src/parsing/functions/to_string.ts +1 -1
  75. package/src/parsing/functions/trim.ts +3 -0
  76. package/tests/compute/runner.test.ts +114 -0
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "flowquery"
3
- version = "1.0.33"
3
+ version = "1.0.34"
4
4
  description = "A declarative query language for data processing pipelines"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10"
@@ -42,6 +42,8 @@ class Join(Function):
42
42
  def value(self) -> Any:
43
43
  array = self.get_children()[0].value()
44
44
  delimiter = self.get_children()[1].value()
45
+ if array is None:
46
+ return None
45
47
  if not isinstance(array, list) or not isinstance(delimiter, str):
46
48
  raise ValueError("Invalid arguments for join function")
47
49
  return delimiter.join(str(item) for item in array)
@@ -28,7 +28,7 @@ class Keys(Function):
28
28
  def value(self) -> Any:
29
29
  obj = self.get_children()[0].value()
30
30
  if obj is None:
31
- return []
31
+ return None
32
32
  if not isinstance(obj, dict):
33
33
  raise ValueError("keys() expects an object, not an array or primitive")
34
34
  return list(obj.keys())
@@ -34,6 +34,8 @@ class Range(Function):
34
34
  def value(self) -> Any:
35
35
  start = self.get_children()[0].value()
36
36
  end = self.get_children()[1].value()
37
+ if start is None or end is None:
38
+ return None
37
39
  if not isinstance(start, (int, float)) or not isinstance(end, (int, float)):
38
40
  raise ValueError("Invalid arguments for range function")
39
41
  return list(range(int(start), int(end) + 1))
@@ -32,6 +32,8 @@ class Replace(Function):
32
32
  text = self.get_children()[0].value()
33
33
  pattern = self.get_children()[1].value()
34
34
  replacement = self.get_children()[2].value()
35
+ if text is None:
36
+ return None
35
37
  if not isinstance(text, str) or not isinstance(pattern, str) or not isinstance(replacement, str):
36
38
  raise ValueError("Invalid arguments for replace function")
37
39
  return re.sub(re.escape(pattern), replacement, text)
@@ -27,6 +27,8 @@ class Round(Function):
27
27
 
28
28
  def value(self) -> Any:
29
29
  val = self.get_children()[0].value()
30
+ if val is None:
31
+ return None
30
32
  if not isinstance(val, (int, float)):
31
33
  raise ValueError("Invalid argument for round function")
32
34
  return round(val)
@@ -27,6 +27,8 @@ class Size(Function):
27
27
 
28
28
  def value(self) -> Any:
29
29
  val = self.get_children()[0].value()
30
+ if val is None:
31
+ return None
30
32
  if not isinstance(val, (list, str)):
31
33
  raise ValueError("Invalid argument for size function")
32
34
  return len(val)
@@ -47,6 +47,8 @@ class Split(Function):
47
47
  def value(self) -> Any:
48
48
  text = self.get_children()[0].value()
49
49
  delimiter = self.get_children()[1].value()
50
+ if text is None:
51
+ return None
50
52
  if not isinstance(text, str) or not isinstance(delimiter, str):
51
53
  raise ValueError("Invalid arguments for split function")
52
54
  return text.split(delimiter)
@@ -1,5 +1,7 @@
1
1
  """String distance function using Levenshtein distance."""
2
2
 
3
+ from typing import Optional
4
+
3
5
  from .function import Function
4
6
  from .function_metadata import FunctionDef
5
7
 
@@ -80,9 +82,11 @@ class StringDistance(Function):
80
82
  super().__init__("string_distance")
81
83
  self._expected_parameter_count = 2
82
84
 
83
- def value(self) -> float:
85
+ def value(self) -> Optional[float]:
84
86
  str1 = self.get_children()[0].value()
85
87
  str2 = self.get_children()[1].value()
88
+ if str1 is None or str2 is None:
89
+ return None
86
90
  if not isinstance(str1, str) or not isinstance(str2, str):
87
91
  raise ValueError("Invalid arguments for string_distance function: both arguments must be strings")
88
92
  return _levenshtein_distance(str1, str2)
@@ -42,6 +42,8 @@ class Stringify(Function):
42
42
  def value(self) -> Any:
43
43
  val = self.get_children()[0].value()
44
44
  indent = int(self.get_children()[1].value())
45
+ if val is None:
46
+ return None
45
47
  if not isinstance(val, (dict, list)):
46
48
  raise ValueError("Invalid argument for stringify function")
47
49
  return json.dumps(val, indent=indent, default=str)
@@ -52,6 +52,8 @@ class Substring(Function):
52
52
  original = children[0].value()
53
53
  start = children[1].value()
54
54
 
55
+ if original is None:
56
+ return None
55
57
  if not isinstance(original, str):
56
58
  raise ValueError(
57
59
  "Invalid argument for substring function: expected a string as the first argument"
@@ -28,6 +28,8 @@ class ToJson(Function):
28
28
 
29
29
  def value(self) -> Any:
30
30
  text = self.get_children()[0].value()
31
+ if text is None:
32
+ return None
31
33
  if not isinstance(text, str):
32
34
  raise ValueError("Invalid arguments for tojson function")
33
35
  return json.loads(text)
@@ -30,6 +30,8 @@ class ToLower(Function):
30
30
 
31
31
  def value(self) -> Any:
32
32
  val = self.get_children()[0].value()
33
+ if val is None:
34
+ return None
33
35
  if not isinstance(val, str):
34
36
  raise ValueError("Invalid argument for toLower function: expected a string")
35
37
  return val.lower()
@@ -33,7 +33,7 @@ class ToString(Function):
33
33
  def value(self) -> Any:
34
34
  val = self.get_children()[0].value()
35
35
  if val is None:
36
- return "null"
36
+ return None
37
37
  if isinstance(val, bool):
38
38
  return str(val).lower()
39
39
  if isinstance(val, (dict, list)):
@@ -30,6 +30,8 @@ class Trim(Function):
30
30
 
31
31
  def value(self) -> Any:
32
32
  val = self.get_children()[0].value()
33
+ if val is None:
34
+ return None
33
35
  if not isinstance(val, str):
34
36
  raise ValueError("Invalid argument for trim function: expected a string")
35
37
  return val.strip()
@@ -849,6 +849,134 @@ class TestRunner:
849
849
  assert len(results) == 1
850
850
  assert results[0] == {"result": ""}
851
851
 
852
+ # --- Null propagation tests ---
853
+
854
+ @pytest.mark.asyncio
855
+ async def test_to_lower_with_null_returns_null(self):
856
+ """Test toLower with null returns null."""
857
+ runner = Runner("RETURN toLower(null) as result")
858
+ await runner.run()
859
+ results = runner.results
860
+ assert len(results) == 1
861
+ assert results[0] == {"result": None}
862
+
863
+ @pytest.mark.asyncio
864
+ async def test_trim_with_null_returns_null(self):
865
+ """Test trim with null returns null."""
866
+ runner = Runner("RETURN trim(null) as result")
867
+ await runner.run()
868
+ results = runner.results
869
+ assert len(results) == 1
870
+ assert results[0] == {"result": None}
871
+
872
+ @pytest.mark.asyncio
873
+ async def test_replace_with_null_returns_null(self):
874
+ """Test replace with null returns null."""
875
+ runner = Runner("RETURN replace(null, 'a', 'b') as result")
876
+ await runner.run()
877
+ results = runner.results
878
+ assert len(results) == 1
879
+ assert results[0] == {"result": None}
880
+
881
+ @pytest.mark.asyncio
882
+ async def test_substring_with_null_returns_null(self):
883
+ """Test substring with null returns null."""
884
+ runner = Runner("RETURN substring(null, 0, 3) as result")
885
+ await runner.run()
886
+ results = runner.results
887
+ assert len(results) == 1
888
+ assert results[0] == {"result": None}
889
+
890
+ @pytest.mark.asyncio
891
+ async def test_split_with_null_returns_null(self):
892
+ """Test split with null returns null."""
893
+ runner = Runner("RETURN split(null, ',') as result")
894
+ await runner.run()
895
+ results = runner.results
896
+ assert len(results) == 1
897
+ assert results[0] == {"result": None}
898
+
899
+ @pytest.mark.asyncio
900
+ async def test_size_with_null_returns_null(self):
901
+ """Test size with null returns null."""
902
+ runner = Runner("RETURN size(null) as result")
903
+ await runner.run()
904
+ results = runner.results
905
+ assert len(results) == 1
906
+ assert results[0] == {"result": None}
907
+
908
+ @pytest.mark.asyncio
909
+ async def test_round_with_null_returns_null(self):
910
+ """Test round with null returns null."""
911
+ runner = Runner("RETURN round(null) as result")
912
+ await runner.run()
913
+ results = runner.results
914
+ assert len(results) == 1
915
+ assert results[0] == {"result": None}
916
+
917
+ @pytest.mark.asyncio
918
+ async def test_join_with_null_returns_null(self):
919
+ """Test join with null returns null."""
920
+ runner = Runner("RETURN join(null, ',') as result")
921
+ await runner.run()
922
+ results = runner.results
923
+ assert len(results) == 1
924
+ assert results[0] == {"result": None}
925
+
926
+ @pytest.mark.asyncio
927
+ async def test_string_distance_with_null_returns_null(self):
928
+ """Test string_distance with null returns null."""
929
+ runner = Runner("RETURN string_distance(null, 'hello') as result")
930
+ await runner.run()
931
+ results = runner.results
932
+ assert len(results) == 1
933
+ assert results[0] == {"result": None}
934
+
935
+ @pytest.mark.asyncio
936
+ async def test_stringify_with_null_returns_null(self):
937
+ """Test stringify with null returns null."""
938
+ runner = Runner("RETURN stringify(null) as result")
939
+ await runner.run()
940
+ results = runner.results
941
+ assert len(results) == 1
942
+ assert results[0] == {"result": None}
943
+
944
+ @pytest.mark.asyncio
945
+ async def test_tojson_with_null_returns_null(self):
946
+ """Test tojson with null returns null."""
947
+ runner = Runner("RETURN tojson(null) as result")
948
+ await runner.run()
949
+ results = runner.results
950
+ assert len(results) == 1
951
+ assert results[0] == {"result": None}
952
+
953
+ @pytest.mark.asyncio
954
+ async def test_range_with_null_returns_null(self):
955
+ """Test range with null returns null."""
956
+ runner = Runner("RETURN range(null, 5) as result")
957
+ await runner.run()
958
+ results = runner.results
959
+ assert len(results) == 1
960
+ assert results[0] == {"result": None}
961
+
962
+ @pytest.mark.asyncio
963
+ async def test_to_string_with_null_returns_null(self):
964
+ """Test toString with null returns null."""
965
+ runner = Runner("RETURN toString(null) as result")
966
+ await runner.run()
967
+ results = runner.results
968
+ assert len(results) == 1
969
+ assert results[0] == {"result": None}
970
+
971
+ @pytest.mark.asyncio
972
+ async def test_keys_with_null_returns_null(self):
973
+ """Test keys with null returns null."""
974
+ runner = Runner("RETURN keys(null) as result")
975
+ await runner.run()
976
+ results = runner.results
977
+ assert len(results) == 1
978
+ assert results[0] == {"result": None}
979
+
852
980
  @pytest.mark.asyncio
853
981
  async def test_associative_array_with_key_which_is_keyword(self):
854
982
  """Test associative array with key which is keyword."""