real-ladybug 0.0.1.dev1__cp311-cp311-win_amd64.whl

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.

Potentially problematic release.


This version of real-ladybug might be problematic. Click here for more details.

Files changed (114) hide show
  1. real_ladybug/__init__.py +83 -0
  2. real_ladybug/_lbug.cp311-win_amd64.pyd +0 -0
  3. real_ladybug/_lbug.exp +0 -0
  4. real_ladybug/_lbug.lib +0 -0
  5. real_ladybug/async_connection.py +226 -0
  6. real_ladybug/connection.py +323 -0
  7. real_ladybug/constants.py +7 -0
  8. real_ladybug/database.py +307 -0
  9. real_ladybug/prepared_statement.py +51 -0
  10. real_ladybug/py.typed +0 -0
  11. real_ladybug/query_result.py +511 -0
  12. real_ladybug/torch_geometric_feature_store.py +185 -0
  13. real_ladybug/torch_geometric_graph_store.py +131 -0
  14. real_ladybug/torch_geometric_result_converter.py +282 -0
  15. real_ladybug/types.py +39 -0
  16. real_ladybug-0.0.1.dev1.dist-info/METADATA +88 -0
  17. real_ladybug-0.0.1.dev1.dist-info/RECORD +114 -0
  18. real_ladybug-0.0.1.dev1.dist-info/WHEEL +5 -0
  19. real_ladybug-0.0.1.dev1.dist-info/licenses/LICENSE +21 -0
  20. real_ladybug-0.0.1.dev1.dist-info/top_level.txt +3 -0
  21. real_ladybug-0.0.1.dev1.dist-info/zip-safe +1 -0
  22. real_ladybug-source/scripts/antlr4/hash.py +2 -0
  23. real_ladybug-source/scripts/antlr4/keywordhandler.py +47 -0
  24. real_ladybug-source/scripts/collect-extensions.py +68 -0
  25. real_ladybug-source/scripts/collect-single-file-header.py +126 -0
  26. real_ladybug-source/scripts/export-dbs.py +101 -0
  27. real_ladybug-source/scripts/export-import-test.py +345 -0
  28. real_ladybug-source/scripts/extension/purge-beta.py +34 -0
  29. real_ladybug-source/scripts/generate-cpp-docs/collect_files.py +122 -0
  30. real_ladybug-source/scripts/generate-tinysnb.py +34 -0
  31. real_ladybug-source/scripts/get-clangd-diagnostics.py +233 -0
  32. real_ladybug-source/scripts/migrate-lbug-db.py +308 -0
  33. real_ladybug-source/scripts/multiplatform-test-helper/collect-results.py +71 -0
  34. real_ladybug-source/scripts/multiplatform-test-helper/notify-discord.py +68 -0
  35. real_ladybug-source/scripts/pip-package/package_tar.py +90 -0
  36. real_ladybug-source/scripts/pip-package/setup.py +130 -0
  37. real_ladybug-source/scripts/run-clang-format.py +408 -0
  38. real_ladybug-source/scripts/setup-extension-repo.py +67 -0
  39. real_ladybug-source/scripts/test-simsimd-dispatch.py +45 -0
  40. real_ladybug-source/scripts/update-nightly-build-version.py +81 -0
  41. real_ladybug-source/third_party/brotli/scripts/dictionary/step-01-download-rfc.py +16 -0
  42. real_ladybug-source/third_party/brotli/scripts/dictionary/step-02-rfc-to-bin.py +34 -0
  43. real_ladybug-source/third_party/brotli/scripts/dictionary/step-03-validate-bin.py +35 -0
  44. real_ladybug-source/third_party/brotli/scripts/dictionary/step-04-generate-java-literals.py +85 -0
  45. real_ladybug-source/third_party/pybind11/tools/codespell_ignore_lines_from_errors.py +35 -0
  46. real_ladybug-source/third_party/pybind11/tools/libsize.py +36 -0
  47. real_ladybug-source/third_party/pybind11/tools/make_changelog.py +63 -0
  48. real_ladybug-source/tools/python_api/build/real_ladybug/__init__.py +83 -0
  49. real_ladybug-source/tools/python_api/build/real_ladybug/async_connection.py +226 -0
  50. real_ladybug-source/tools/python_api/build/real_ladybug/connection.py +323 -0
  51. real_ladybug-source/tools/python_api/build/real_ladybug/constants.py +7 -0
  52. real_ladybug-source/tools/python_api/build/real_ladybug/database.py +307 -0
  53. real_ladybug-source/tools/python_api/build/real_ladybug/prepared_statement.py +51 -0
  54. real_ladybug-source/tools/python_api/build/real_ladybug/py.typed +0 -0
  55. real_ladybug-source/tools/python_api/build/real_ladybug/query_result.py +511 -0
  56. real_ladybug-source/tools/python_api/build/real_ladybug/torch_geometric_feature_store.py +185 -0
  57. real_ladybug-source/tools/python_api/build/real_ladybug/torch_geometric_graph_store.py +131 -0
  58. real_ladybug-source/tools/python_api/build/real_ladybug/torch_geometric_result_converter.py +282 -0
  59. real_ladybug-source/tools/python_api/build/real_ladybug/types.py +39 -0
  60. real_ladybug-source/tools/python_api/src_py/__init__.py +83 -0
  61. real_ladybug-source/tools/python_api/src_py/async_connection.py +226 -0
  62. real_ladybug-source/tools/python_api/src_py/connection.py +323 -0
  63. real_ladybug-source/tools/python_api/src_py/constants.py +7 -0
  64. real_ladybug-source/tools/python_api/src_py/database.py +307 -0
  65. real_ladybug-source/tools/python_api/src_py/prepared_statement.py +51 -0
  66. real_ladybug-source/tools/python_api/src_py/py.typed +0 -0
  67. real_ladybug-source/tools/python_api/src_py/query_result.py +511 -0
  68. real_ladybug-source/tools/python_api/src_py/torch_geometric_feature_store.py +185 -0
  69. real_ladybug-source/tools/python_api/src_py/torch_geometric_graph_store.py +131 -0
  70. real_ladybug-source/tools/python_api/src_py/torch_geometric_result_converter.py +282 -0
  71. real_ladybug-source/tools/python_api/src_py/types.py +39 -0
  72. real_ladybug-source/tools/python_api/test/conftest.py +230 -0
  73. real_ladybug-source/tools/python_api/test/disabled_test_extension.py +73 -0
  74. real_ladybug-source/tools/python_api/test/ground_truth.py +430 -0
  75. real_ladybug-source/tools/python_api/test/test_arrow.py +694 -0
  76. real_ladybug-source/tools/python_api/test/test_async_connection.py +159 -0
  77. real_ladybug-source/tools/python_api/test/test_blob_parameter.py +145 -0
  78. real_ladybug-source/tools/python_api/test/test_connection.py +49 -0
  79. real_ladybug-source/tools/python_api/test/test_database.py +234 -0
  80. real_ladybug-source/tools/python_api/test/test_datatype.py +372 -0
  81. real_ladybug-source/tools/python_api/test/test_df.py +564 -0
  82. real_ladybug-source/tools/python_api/test/test_dict.py +112 -0
  83. real_ladybug-source/tools/python_api/test/test_exception.py +54 -0
  84. real_ladybug-source/tools/python_api/test/test_fsm.py +227 -0
  85. real_ladybug-source/tools/python_api/test/test_get_header.py +49 -0
  86. real_ladybug-source/tools/python_api/test/test_helper.py +8 -0
  87. real_ladybug-source/tools/python_api/test/test_issue.py +147 -0
  88. real_ladybug-source/tools/python_api/test/test_iteration.py +96 -0
  89. real_ladybug-source/tools/python_api/test/test_networkx.py +437 -0
  90. real_ladybug-source/tools/python_api/test/test_parameter.py +340 -0
  91. real_ladybug-source/tools/python_api/test/test_prepared_statement.py +117 -0
  92. real_ladybug-source/tools/python_api/test/test_query_result.py +54 -0
  93. real_ladybug-source/tools/python_api/test/test_query_result_close.py +44 -0
  94. real_ladybug-source/tools/python_api/test/test_scan_pandas.py +676 -0
  95. real_ladybug-source/tools/python_api/test/test_scan_pandas_pyarrow.py +714 -0
  96. real_ladybug-source/tools/python_api/test/test_scan_polars.py +165 -0
  97. real_ladybug-source/tools/python_api/test/test_scan_pyarrow.py +167 -0
  98. real_ladybug-source/tools/python_api/test/test_timeout.py +11 -0
  99. real_ladybug-source/tools/python_api/test/test_torch_geometric.py +640 -0
  100. real_ladybug-source/tools/python_api/test/test_torch_geometric_remote_backend.py +111 -0
  101. real_ladybug-source/tools/python_api/test/test_udf.py +207 -0
  102. real_ladybug-source/tools/python_api/test/test_version.py +6 -0
  103. real_ladybug-source/tools/python_api/test/test_wal.py +80 -0
  104. real_ladybug-source/tools/python_api/test/type_aliases.py +10 -0
  105. real_ladybug-source/tools/rust_api/update_version.py +47 -0
  106. real_ladybug-source/tools/shell/test/conftest.py +218 -0
  107. real_ladybug-source/tools/shell/test/test_helper.py +60 -0
  108. real_ladybug-source/tools/shell/test/test_shell_basics.py +325 -0
  109. real_ladybug-source/tools/shell/test/test_shell_commands.py +656 -0
  110. real_ladybug-source/tools/shell/test/test_shell_control_edit.py +438 -0
  111. real_ladybug-source/tools/shell/test/test_shell_control_search.py +468 -0
  112. real_ladybug-source/tools/shell/test/test_shell_esc_edit.py +232 -0
  113. real_ladybug-source/tools/shell/test/test_shell_esc_search.py +162 -0
  114. real_ladybug-source/tools/shell/test/test_shell_flags.py +645 -0
@@ -0,0 +1,111 @@
1
+ from __future__ import annotations
2
+
3
+ TINY_SNB_KNOWS_GROUND_TRUTH = {
4
+ 0: [2, 3, 5],
5
+ 2: [0, 3, 5],
6
+ 3: [0, 2, 5],
7
+ 5: [0, 2, 3],
8
+ 7: [8, 9],
9
+ }
10
+
11
+ TINY_SNB_PERSON_IDS_GROUND_TRUTH = [0, 2, 3, 5, 7, 8, 9, 10]
12
+
13
+ # TODO: FIX-ME. Re-enable the tests when StorageDriver is fixed.
14
+ # def test_remote_backend_graph_store(conn_db_readonly: ConnDB) -> None:
15
+ # _, db = conn_db_readonly
16
+ # fs, gs = db.get_torch_geometric_remote_backend()
17
+ # edge_idx = gs.get_edge_index(("person", "knows", "person"), layout="coo", is_sorted=False)
18
+ # assert edge_idx.shape == torch.Size([2, 14])
19
+ # for i in range(14):
20
+ # src = edge_idx[0, i].item()
21
+ # src_id = fs["person", "ID", src].item()
22
+ # dst = edge_idx[1, i].item()
23
+ # dst_id = fs["person", "ID", dst].item()
24
+ # assert src_id in TINY_SNB_KNOWS_GROUND_TRUTH
25
+ # assert dst_id in TINY_SNB_KNOWS_GROUND_TRUTH[src_id]
26
+ #
27
+ #
28
+ # def test_remote_backend_feature_store_idx(conn_db_readonly: ConnDB) -> None:
29
+ # _, db = conn_db_readonly
30
+ # fs, _ = db.get_torch_geometric_remote_backend()
31
+ # id_set = set(TINY_SNB_PERSON_IDS_GROUND_TRUTH)
32
+ # index_to_id = {}
33
+ # # Individual index access
34
+ # for i in range(len(id_set)):
35
+ # assert fs["person", "ID", i].item() in id_set
36
+ # id_set.remove(fs["person", "ID", i].item())
37
+ # index_to_id[i] = fs["person", "ID", i].item()
38
+ #
39
+ # # Range index access
40
+ # for i in range(len(index_to_id) - 3):
41
+ # ids = fs["person", "ID", i : i + 3]
42
+ # for j in range(3):
43
+ # assert ids[j].item() == index_to_id[i + j]
44
+ #
45
+ # # Multiple index access
46
+ # indicies = random.sample(range(len(index_to_id)), 4)
47
+ # ids = fs["person", "ID", indicies]
48
+ # for i in range(4):
49
+ # idx = indicies[i]
50
+ # assert ids[i].item() == index_to_id[idx]
51
+ #
52
+ # # No index access
53
+ # ids = fs["person", "ID", None]
54
+ # assert len(ids) == len(index_to_id)
55
+ # for i in range(len(ids)):
56
+ # assert ids[i].item() == index_to_id[i]
57
+ #
58
+ #
59
+ # def test_remote_backend_feature_store_types_1d(conn_db_readonly: ConnDB) -> None:
60
+ # _, db = conn_db_readonly
61
+ # fs, _ = db.get_torch_geometric_remote_backend()
62
+ # for i in range(3):
63
+ # assert fs["npyoned", "i64", i].item() - i == 1
64
+ # assert fs["npyoned", "i32", i].item() - i == 1
65
+ # assert fs["npyoned", "i16", i].item() - i == 1
66
+ # assert fs["npyoned", "f64", i].item() - i - 1 < 1e-6
67
+ # assert fs["npyoned", "f32", i].item() - i - 1 < 1e-6
68
+ #
69
+ #
70
+ # def test_remote_backend_feature_store_types_2d(conn_db_readonly: ConnDB) -> None:
71
+ # _, db = conn_db_readonly
72
+ # fs, _ = db.get_torch_geometric_remote_backend()
73
+ # for i in range(3):
74
+ # i64 = fs["npytwod", "i64", i]
75
+ # assert i64.shape == torch.Size([1, 3])
76
+ # base_number = (i * 3) + 1
77
+ # for j in range(3):
78
+ # assert i64[0, j].item() == base_number + j
79
+ # i32 = fs["npytwod", "i32", i]
80
+ # assert i32.shape == torch.Size([1, 3])
81
+ # for j in range(3):
82
+ # assert i32[0, j].item() == base_number + j
83
+ # i16 = fs["npytwod", "i16", i]
84
+ # assert i16.shape == torch.Size([1, 3])
85
+ # for j in range(3):
86
+ # assert i16[0, j].item() == base_number + j
87
+ # f64 = fs["npytwod", "f64", i]
88
+ # assert f64.shape == torch.Size([1, 3])
89
+ # for j in range(3):
90
+ # assert f64[0, j].item() - (base_number + j) - 1 < 1e-6
91
+ # f32 = fs["npytwod", "f32", i]
92
+ # assert f32.shape == torch.Size([1, 3])
93
+ # for j in range(3):
94
+ # assert f32[0, j].item() - (base_number + j) - 1 < 1e-6
95
+ #
96
+ #
97
+ # def test_remote_backend_20k(conn_db_readwrite: ConnDB) -> None:
98
+ # _, db = conn_db_readwrite
99
+ # conn = lb.Connection(db, num_threads=1)
100
+ # conn.execute("CREATE NODE TABLE npy20k (id INT64,f32 FLOAT[10],PRIMARY KEY(id));")
101
+ # conn.execute(
102
+ # f"""
103
+ # COPY npy20k FROM (
104
+ # "{LBUG_ROOT}/dataset/npy-20k/id_int64.npy",
105
+ # "{LBUG_ROOT}/dataset/npy-20k/two_dim_float.npy") BY COLUMN;
106
+ # """
107
+ # )
108
+ # del conn
109
+ # fs, _ = db.get_torch_geometric_remote_backend(8)
110
+ # for i in range(20000):
111
+ # assert fs["npy20k", "id", i].item() == i
@@ -0,0 +1,207 @@
1
+ from __future__ import annotations
2
+
3
+ from datetime import date, datetime, timedelta # noqa: TC003
4
+
5
+ import pandas as pd
6
+ import pyarrow as pa
7
+ import pytest
8
+ from real_ladybug import Type
9
+ from type_aliases import ConnDB
10
+
11
+
12
+ def udf_helper(conn, functionName, params, expectedResult):
13
+ plist = ", ".join(f"${i + 1}" for i in range(len(params)))
14
+ queryString = f"RETURN {functionName}({plist})"
15
+ result = conn.execute(queryString, {str(i + 1): params[i] for i in range(len(params))})
16
+ assert result.get_next()[0] == expectedResult
17
+
18
+
19
+ def udf_predicate_test(conn, functionName, params, expectedResult):
20
+ conn.execute("CREATE node table tbl (id INT64, primary key (id))")
21
+ for i in params:
22
+ conn.execute("CREATE (t:tbl {id: $num})", {"num": i})
23
+ result = conn.execute(f"MATCH (t:tbl) WHERE {functionName}(t.id % 8) RETURN COUNT(*)")
24
+ assert result.get_next()[0] == expectedResult
25
+
26
+
27
+ def test_udf(conn_db_readwrite: ConnDB) -> None:
28
+ conn, _ = conn_db_readwrite
29
+
30
+ def add5int(x: int) -> int:
31
+ print(x)
32
+ return x + 5
33
+
34
+ add5IntArgs = ["add5int", add5int]
35
+
36
+ def add5float(x: float) -> float:
37
+ return x + 5.0
38
+
39
+ add5FloatArgs = ["add5float", add5float]
40
+
41
+ def intToString(x: int) -> str:
42
+ return str(x)
43
+
44
+ intToStringArgs = ["intToString", intToString]
45
+
46
+ def sumOf7(a, b, c, d, e, f, g) -> int:
47
+ return a + b + c + d + e + f + g
48
+
49
+ sumOf7Args = ["sumOf7", sumOf7, [Type.INT64] * 7]
50
+
51
+ def dateToDatetime(a: date) -> datetime:
52
+ return datetime(a.year, a.month, a.day)
53
+
54
+ dateToDatetimeArgs = ["dateToDatetime", dateToDatetime]
55
+
56
+ def datetimeToDate(a: datetime) -> date:
57
+ return a.date()
58
+
59
+ datetimeToDateArgs = ["datetimeToDate", datetimeToDate, [Type.TIMESTAMP], Type.DATE]
60
+
61
+ def addToDate(a: datetime, b: timedelta) -> datetime:
62
+ return a + b
63
+
64
+ addToDateArgs = ["addToDate", addToDate, [Type.TIMESTAMP, Type.INTERVAL], Type.TIMESTAMP]
65
+
66
+ def concatThreeLists(a: list[int], b: list[int], c: list[int]) -> list[int]:
67
+ return a + b + c
68
+
69
+ concatThreeListsArgs = ["concatThreeLists", concatThreeLists]
70
+
71
+ def mergeMaps(a, b):
72
+ return a | b
73
+
74
+ mergeMapsArgs = ["mergeMaps", mergeMaps, ["MAP(STRING, INT64)"] * 2, "MAP(STRING, INT64)"]
75
+
76
+ def mergeMaps2(a: dict[str, int], b: dict[str, int]) -> dict[str, int]:
77
+ return a | b
78
+
79
+ mergeMaps2Args = ["mergeMaps2", mergeMaps2]
80
+
81
+ def selectIfSeven(a: int) -> bool:
82
+ return a == 7
83
+
84
+ selectIfSevenArgs = ["selectIfSeven", selectIfSeven]
85
+
86
+ def distancePrimer(pointA, pointB):
87
+ return {"x": (pointA["x"] - pointB["x"]) ** 2, "y": (pointA["y"] - pointB["y"]) ** 2}
88
+
89
+ distanceArgs = [
90
+ "UDFDist",
91
+ distancePrimer,
92
+ ["STRUCT(x INT32, y INT32)", "STRUCT(x INT32, y INT32)"],
93
+ "STRUCT(x INT32, y INT32)",
94
+ ]
95
+
96
+ conn.create_function(*add5IntArgs)
97
+ conn.create_function(*add5FloatArgs)
98
+ conn.create_function(*intToStringArgs)
99
+ conn.create_function(*sumOf7Args)
100
+ conn.create_function(*dateToDatetimeArgs)
101
+ conn.create_function(*datetimeToDateArgs)
102
+ conn.create_function(*addToDateArgs)
103
+ conn.create_function(*concatThreeListsArgs)
104
+ conn.create_function(*mergeMapsArgs)
105
+ conn.create_function(*mergeMaps2Args)
106
+ conn.create_function(*selectIfSevenArgs)
107
+ conn.create_function(*distanceArgs)
108
+
109
+ udf_helper(conn, "add5int", [10], 15)
110
+ udf_helper(conn, "add5int", [9], 14)
111
+ udf_helper(conn, "add5int", [1283], 1288)
112
+ udf_helper(conn, "add5float", [2.2], 7.2)
113
+ udf_helper(conn, "intToString", [123], "123")
114
+ udf_helper(conn, "sumOf7", [1, 2, 3, 4, 5, 6, 7], sum(range(8)))
115
+ udf_helper(conn, "dateToDatetime", [date(2024, 4, 25)], datetime(2024, 4, 25))
116
+ udf_helper(conn, "datetimeToDate", [datetime(2024, 4, 25, 15, 39, 20)], date(2024, 4, 25))
117
+ udf_helper(
118
+ conn,
119
+ "addToDate",
120
+ [datetime(2024, 4, 25, 15, 39, 20), datetime(2025, 5, 26) - datetime(2024, 4, 25, 15, 39, 20)],
121
+ datetime(2025, 5, 26),
122
+ )
123
+ udf_helper(conn, "concatThreeLists", [[1, 2, 3], [4, 5, 6], [-1, -2, -3]], [1, 2, 3, 4, 5, 6, -1, -2, -3])
124
+ udf_helper(
125
+ conn,
126
+ "mergeMaps",
127
+ [{"key": ["a", "b", "c"], "value": [1, 2, 3]}, {"key": ["x", "y", "z"], "value": [100, 200, 300]}],
128
+ {"a": 1, "b": 2, "c": 3, "x": 100, "y": 200, "z": 300},
129
+ )
130
+ udf_helper(
131
+ conn,
132
+ "mergeMaps2",
133
+ [{"key": ["a", "b", "c"], "value": [1, 2, 3]}, {"key": ["x", "y", "z"], "value": [100, 200, 300]}],
134
+ {"a": 1, "b": 2, "c": 3, "x": 100, "y": 200, "z": 300},
135
+ )
136
+ udf_predicate_test(conn, "selectIfSeven", range(65), 8)
137
+ udf_helper(conn, "UDFDist", [{"x": 1, "y": 10}, {"x": 4, "y": 6}], {"x": 9, "y": 16})
138
+
139
+ df = pd.DataFrame({"col": list(range(5000))})
140
+ result = conn.execute("LOAD FROM df RETURN add5int(col) as ans").get_as_df()
141
+ assert list(result["ans"]) == list(map(add5int, range(5000)))
142
+
143
+ df = pd.DataFrame({
144
+ "col1": pd.Series(
145
+ [{"a": 1, "b": 2, "c": 3}, {"l": 1, "m": 2, "n": 3}], dtype=pd.ArrowDtype(pa.map_(pa.string(), pa.int64()))
146
+ ),
147
+ "col2": pd.Series(
148
+ [{"x": -1, "y": -2, "z": -3}, {"one": -1, "two": -2, "three": -3}],
149
+ dtype=pd.ArrowDtype(pa.map_(pa.string(), pa.int64())),
150
+ ),
151
+ })
152
+ # result = conn.execute("LOAD FROM df RETURN mergeMaps(col1, col2) as ans").get_as_arrow()
153
+ # assert result["ans"].to_pylist() == [
154
+ # [("a", 1), ("b", 2), ("c", 3), ("x", -1), ("y", -2), ("z", -3)],
155
+ # [("l", 1), ("m", 2), ("n", 3), ("one", -1), ("two", -2), ("three", -3)],
156
+ # ]
157
+
158
+
159
+ def test_udf_null(conn_db_readwrite: ConnDB) -> None:
160
+ conn, _ = conn_db_readwrite
161
+
162
+ def get5(x: int) -> int:
163
+ return 5
164
+
165
+ conn.create_function("get5", get5)
166
+ assert conn.execute("RETURN get5(NULL)").get_next() == [None]
167
+
168
+ conn.remove_function("get5")
169
+ conn.create_function("get5", get5, default_null_handling=False)
170
+ assert conn.execute("RETURN get5(NULL)").get_next() == [5]
171
+
172
+
173
+ def test_udf_except(conn_db_readwrite: ConnDB) -> None:
174
+ class TestException(Exception):
175
+ pass
176
+
177
+ conn, _ = conn_db_readwrite
178
+
179
+ def throw() -> int:
180
+ errmsg = "test"
181
+ raise TestException(errmsg)
182
+
183
+ conn.create_function("testexcept", throw)
184
+
185
+ pytest.raises(RuntimeError, conn.execute, "RETURN testexcept()")
186
+
187
+ conn.remove_function("testexcept")
188
+ conn.create_function("testexcept", throw, catch_exceptions=True)
189
+
190
+ assert conn.execute("RETURN testexcept()").get_next() == [None]
191
+
192
+
193
+ def test_udf_remove(conn_db_readwrite: ConnDB) -> None:
194
+ conn, _ = conn_db_readwrite
195
+
196
+ def myfunction() -> int:
197
+ return 1
198
+
199
+ conn.create_function("myfunction", myfunction)
200
+
201
+ with pytest.raises(RuntimeError, match=r"Catalog exception: function notmyfunction doesn't exist."):
202
+ conn.remove_function("notmyfunction")
203
+
204
+ conn.remove_function("myfunction")
205
+
206
+ with pytest.raises(RuntimeError, match=r"Catalog exception: function list_create doesn't exist."):
207
+ conn.remove_function("list_create")
@@ -0,0 +1,6 @@
1
+ def test_version() -> None:
2
+ import real_ladybug as lb
3
+
4
+ assert lb.version != ""
5
+ assert lb.storage_version > 0
6
+ assert lb.version == lb.__version__
@@ -0,0 +1,80 @@
1
+ import subprocess
2
+ import sys
3
+ import time
4
+ from pathlib import Path
5
+ from textwrap import dedent
6
+
7
+ import real_ladybug as lb
8
+
9
+ from conftest import get_db_file_path
10
+
11
+
12
+ def run_query_in_new_process(tmp_path: Path, build_dir: Path, queries: str):
13
+ db_path = get_db_file_path(tmp_path)
14
+ code = (
15
+ dedent(
16
+ f"""
17
+ import sys
18
+ sys.path.append(r"{build_dir!s}")
19
+
20
+ import real_ladybug as lb
21
+ db = lb.Database(r"{db_path!s}")
22
+ """
23
+ )
24
+ + queries
25
+ )
26
+ return subprocess.Popen([sys.executable, "-c", code])
27
+
28
+
29
+ def run_query_then_kill(tmp_path: Path, build_dir: Path, queries: str):
30
+ proc = run_query_in_new_process(tmp_path, build_dir, queries)
31
+ time.sleep(5)
32
+ proc.kill()
33
+ proc.wait(5)
34
+ db_path = get_db_file_path(tmp_path)
35
+ # Force remove the lock file. Safe since proc.wait() ensures the process has terminated.
36
+ Path(f"{db_path!s}.lock").unlink(missing_ok=True)
37
+
38
+
39
+ # Kill the database while it's in the middle of executing a long persistent query
40
+ # When we reload the database we will replay from the WAL (which will be incomplete)
41
+ def test_replay_after_kill(tmp_path: Path, build_dir: Path) -> None:
42
+ queries = dedent("""
43
+ conn = lb.Connection(db)
44
+ conn.execute("CREATE NODE TABLE tab (id INT64, PRIMARY KEY (id));")
45
+ conn.execute("UNWIND RANGE(1,100000) AS x UNWIND RANGE(1, 100000) AS y CREATE (:tab {id: x * 100000 + y});")
46
+ """)
47
+ run_query_then_kill(tmp_path, build_dir, queries)
48
+ db_path = get_db_file_path(tmp_path)
49
+ with lb.Database(db_path) as db, lb.Connection(db) as conn:
50
+ # previously committed queries should be valid after replaying WAL
51
+ result = conn.execute("CALL show_tables() RETURN *")
52
+ assert result.has_next()
53
+ assert result.get_next()[1] == "tab"
54
+ assert not result.has_next()
55
+ result.close()
56
+
57
+
58
+ def test_replay_with_exception(tmp_path: Path, build_dir: Path) -> None:
59
+ queries = dedent("""
60
+ conn = lb.Connection(db)
61
+ conn.execute("CREATE NODE TABLE tab (id INT64, PRIMARY KEY (id));")
62
+ # some of these queries will throw exceptions
63
+ for i in range(10):
64
+ try:
65
+ conn.execute(f"CREATE (:tab {{id: {i // 2}}})")
66
+ assert i % 2 == 0
67
+ except:
68
+ assert i % 2 == 1
69
+ conn.execute("UNWIND RANGE(1,100000) AS x UNWIND RANGE(1, 100000) AS y CREATE (:tab {id: x * 100000 + y});")
70
+ """)
71
+ run_query_then_kill(tmp_path, build_dir, queries)
72
+ db_path = get_db_file_path(tmp_path)
73
+ with lb.Database(db_path) as db, lb.Connection(db) as conn:
74
+ # previously committed queries should be valid after replaying WAL
75
+ result = conn.execute("match (t:tab) where t.id <= 5 return t.id")
76
+ assert result.get_num_tuples() == 5
77
+ for i in range(5):
78
+ assert result.get_next()[0] == i
79
+ assert not result.has_next()
80
+ result.close()
@@ -0,0 +1,10 @@
1
+ import sys
2
+
3
+ from real_ladybug import Connection, Database
4
+
5
+ if sys.version_info >= (3, 10):
6
+ from typing import TypeAlias
7
+ else:
8
+ from typing_extensions import TypeAlias
9
+
10
+ ConnDB: TypeAlias = tuple[Connection, Database]
@@ -0,0 +1,47 @@
1
+ """Updates the version in Cargo.toml to match the version in the main CMakeLists.txt"""
2
+
3
+ import os
4
+ import re
5
+ from pathlib import Path
6
+
7
+ LBUG_RS_ROOT = Path(__file__).parent
8
+ LBUG_ROOT = LBUG_RS_ROOT.parent.parent
9
+
10
+
11
+ def get_lbug_version():
12
+ cmake_file = LBUG_ROOT / "CMakeLists.txt"
13
+ with open(cmake_file) as f:
14
+ for line in f:
15
+ if line.startswith("project(Lbug VERSION"):
16
+ version = line.split(" ")[2].strip()
17
+ # Make version semver-compatible
18
+ components = version.split(".")
19
+ if len(components) >= 4:
20
+ version = ".".join(components[0:3]) + "-pre." + ".".join(components[3:])
21
+ return version
22
+
23
+
24
+ if __name__ == "__main__":
25
+ version = get_lbug_version()
26
+ version_changed = False
27
+ with open(LBUG_RS_ROOT / "Cargo.toml", encoding="utf-8") as file:
28
+ data = file.readlines()
29
+ section = None
30
+ for index, line in enumerate(data):
31
+ if line.startswith("["):
32
+ section = line.strip().strip("[]")
33
+ if line.startswith("version = ") and section == "package":
34
+ toml_version = re.match('version = "(.*)"', line).group(1)
35
+ if toml_version != version:
36
+ version_changed = True
37
+ print(
38
+ f"Updating version in Cargo.toml from {toml_version} to {version}"
39
+ )
40
+ data[index] = re.sub(
41
+ 'version = ".*"', f'version = "{version}"', line
42
+ )
43
+ break
44
+
45
+ if version_changed:
46
+ with open(LBUG_RS_ROOT / "Cargo.toml", "w", encoding="utf-8") as file:
47
+ file.writelines(data)
@@ -0,0 +1,218 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ import shutil
5
+ import subprocess
6
+
7
+ import pexpect
8
+ import pytest
9
+
10
+ from test_helper import LBUG_EXEC_PATH, LBUG_ROOT, deleteIfExists
11
+
12
+
13
+ def pytest_addoption(parser) -> None:
14
+ parser.addoption(
15
+ "--start-offset",
16
+ action="store",
17
+ type=int,
18
+ help="Skip the first 'n' tests",
19
+ )
20
+
21
+
22
+ def pytest_collection_modifyitems(config, items) -> None:
23
+ start_offset = config.getoption("--start-offset")
24
+ if not start_offset:
25
+ # --skiplist not given in cli, therefore move on
26
+ return
27
+
28
+ skipped = pytest.mark.skip(reason="included in --skiplist")
29
+ skipped_items = items[:start_offset]
30
+ for item in skipped_items:
31
+ item.add_marker(skipped)
32
+
33
+
34
+ class TestResult:
35
+ def __init__(self, stdout, stderr, status_code) -> None:
36
+ self.stdout: str | bytes = stdout
37
+ self.stderr: str | bytes = stderr
38
+ self.status_code: int = status_code
39
+
40
+ def check_stdout(self, expected: str | list[str] | bytes) -> None:
41
+ if isinstance(expected, list):
42
+ expected = "\n".join(expected)
43
+ assert self.status_code == 0
44
+ assert expected in self.stdout
45
+
46
+ def check_not_stdout(self, expected: str | list[str] | bytes) -> None:
47
+ if isinstance(expected, list):
48
+ expected = "\n".join(expected)
49
+ assert self.status_code == 0
50
+ assert expected not in self.stdout
51
+
52
+ def check_stderr(self, expected: str) -> None:
53
+ assert expected in self.stderr
54
+
55
+
56
+ class ShellTest:
57
+ def __init__(self) -> None:
58
+ self.shell = LBUG_EXEC_PATH
59
+ self.arguments = [self.shell]
60
+ self.statements: list[str] = []
61
+ self.input = None
62
+ self.output = None
63
+ self.environment = {}
64
+ self.shell_process = None
65
+
66
+ def add_argument(self, *args):
67
+ self.arguments.extend(args)
68
+ return self
69
+
70
+ def statement(self, stmt):
71
+ self.statements.append(stmt)
72
+ return self
73
+
74
+ def query(self, *stmts):
75
+ self.statements.extend(stmts)
76
+ return self
77
+
78
+ def input_file(self, file_path):
79
+ self.input = file_path
80
+ return self
81
+
82
+ def output_file(self, file_path):
83
+ self.output = file_path
84
+ return self
85
+
86
+ # Test Running methods
87
+
88
+ def get_command(self, cmd: str) -> list[str]:
89
+ command = self.arguments
90
+ if self.input:
91
+ command += [cmd]
92
+ return command
93
+
94
+ def get_input_data(self, cmd: str):
95
+ if self.input:
96
+ with open(self.input, "rb") as f:
97
+ input_data = f.read()
98
+ else:
99
+ input_data = bytearray(cmd, "utf8")
100
+ return input_data
101
+
102
+ def get_output_pipe(self):
103
+ output_pipe = subprocess.PIPE
104
+ if self.output:
105
+ output_pipe = open(self.output, "w+")
106
+ return output_pipe
107
+
108
+ def get_statements(self):
109
+ statements = []
110
+ for statement in self.statements:
111
+ statements.append(statement)
112
+ return "\n".join(statements)
113
+
114
+ def get_output_data(self, res):
115
+ if self.output:
116
+ stdout = open(self.output).read()
117
+ else:
118
+ stdout = res.stdout.decode("utf8").strip()
119
+ stderr = res.stderr.decode("utf8").strip()
120
+ return stdout, stderr
121
+
122
+ def run(self):
123
+ statements = self.get_statements()
124
+ command = self.get_command(statements)
125
+ input_data = self.get_input_data(statements)
126
+ output_pipe = self.get_output_pipe()
127
+
128
+ my_env = os.environ.copy()
129
+ for key, val in self.environment.items():
130
+ my_env[key] = val
131
+
132
+ res = subprocess.run(
133
+ command,
134
+ input=input_data,
135
+ stdout=output_pipe,
136
+ stderr=subprocess.PIPE,
137
+ env=my_env,
138
+ check=False,
139
+ )
140
+
141
+ stdout, stderr = self.get_output_data(res)
142
+ return TestResult(stdout, stderr, res.returncode)
143
+
144
+ def start(self) -> None:
145
+ command = " ".join(self.arguments)
146
+
147
+ my_env = os.environ.copy()
148
+ for key, val in self.environment.items():
149
+ my_env[key] = val
150
+
151
+ self.shell_process = pexpect.spawn(command, encoding="utf_8", env=my_env)
152
+
153
+ def send_finished_statement(self, stmt: str) -> None:
154
+ if self.shell_process:
155
+ assert self.shell_process.expect_exact(["lbug", pexpect.EOF]) == 0
156
+ self.shell_process.send(stmt)
157
+ assert self.shell_process.expect_exact(["lbug", pexpect.EOF]) == 0
158
+
159
+ def send_statement(self, stmt: str) -> None:
160
+ if self.shell_process:
161
+ assert self.shell_process.expect_exact(["lbug", pexpect.EOF]) == 0
162
+ self.shell_process.send(stmt)
163
+
164
+ def send_control_statement(self, stmt: str) -> None:
165
+ if self.shell_process:
166
+ assert self.shell_process.expect_exact(["lbug", pexpect.EOF]) == 0
167
+ self.shell_process.sendcontrol(stmt)
168
+
169
+
170
+ @pytest.fixture()
171
+ def temp_db(tmp_path):
172
+ shutil.rmtree(tmp_path, ignore_errors=True)
173
+ return str(tmp_path)
174
+
175
+
176
+ @pytest.fixture()
177
+ def get_tmp_path(tmp_path):
178
+ return str(tmp_path)
179
+
180
+
181
+ @pytest.fixture()
182
+ def history_path():
183
+ path = os.path.join(LBUG_ROOT, "tools", "shell", "test", "files")
184
+ deleteIfExists(os.path.join(path, "history.txt"))
185
+ return path
186
+
187
+
188
+ @pytest.fixture()
189
+ def csv_path():
190
+ return os.path.join(LBUG_ROOT, "tools", "shell", "test", "files", "vPerson.csv")
191
+
192
+
193
+ @pytest.fixture()
194
+ def init_path():
195
+ return os.path.join(LBUG_ROOT, "tools", "shell", "test", "files", "start.cypher")
196
+
197
+
198
+ @pytest.fixture
199
+ def test(request):
200
+ return request.getfixturevalue(request.param)
201
+
202
+
203
+ @pytest.fixture
204
+ def multiline():
205
+ test = ShellTest()
206
+ test.start()
207
+ return test
208
+
209
+
210
+ @pytest.fixture
211
+ def singleline():
212
+ test = ShellTest()
213
+ test.start()
214
+ test.send_finished_statement(":singleline\r")
215
+ assert (
216
+ test.shell_process.expect_exact(["Single line mode enabled", pexpect.EOF]) == 0
217
+ )
218
+ return test