real-ladybug 0.0.1.dev1__cp312-cp312-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.
- real_ladybug/__init__.py +83 -0
- real_ladybug/_lbug.cp312-win_amd64.pyd +0 -0
- real_ladybug/_lbug.exp +0 -0
- real_ladybug/_lbug.lib +0 -0
- real_ladybug/async_connection.py +226 -0
- real_ladybug/connection.py +323 -0
- real_ladybug/constants.py +7 -0
- real_ladybug/database.py +307 -0
- real_ladybug/prepared_statement.py +51 -0
- real_ladybug/py.typed +0 -0
- real_ladybug/query_result.py +511 -0
- real_ladybug/torch_geometric_feature_store.py +185 -0
- real_ladybug/torch_geometric_graph_store.py +131 -0
- real_ladybug/torch_geometric_result_converter.py +282 -0
- real_ladybug/types.py +39 -0
- real_ladybug-0.0.1.dev1.dist-info/METADATA +88 -0
- real_ladybug-0.0.1.dev1.dist-info/RECORD +114 -0
- real_ladybug-0.0.1.dev1.dist-info/WHEEL +5 -0
- real_ladybug-0.0.1.dev1.dist-info/licenses/LICENSE +21 -0
- real_ladybug-0.0.1.dev1.dist-info/top_level.txt +3 -0
- real_ladybug-0.0.1.dev1.dist-info/zip-safe +1 -0
- real_ladybug-source/scripts/antlr4/hash.py +2 -0
- real_ladybug-source/scripts/antlr4/keywordhandler.py +47 -0
- real_ladybug-source/scripts/collect-extensions.py +68 -0
- real_ladybug-source/scripts/collect-single-file-header.py +126 -0
- real_ladybug-source/scripts/export-dbs.py +101 -0
- real_ladybug-source/scripts/export-import-test.py +345 -0
- real_ladybug-source/scripts/extension/purge-beta.py +34 -0
- real_ladybug-source/scripts/generate-cpp-docs/collect_files.py +122 -0
- real_ladybug-source/scripts/generate-tinysnb.py +34 -0
- real_ladybug-source/scripts/get-clangd-diagnostics.py +233 -0
- real_ladybug-source/scripts/migrate-lbug-db.py +308 -0
- real_ladybug-source/scripts/multiplatform-test-helper/collect-results.py +71 -0
- real_ladybug-source/scripts/multiplatform-test-helper/notify-discord.py +68 -0
- real_ladybug-source/scripts/pip-package/package_tar.py +90 -0
- real_ladybug-source/scripts/pip-package/setup.py +130 -0
- real_ladybug-source/scripts/run-clang-format.py +408 -0
- real_ladybug-source/scripts/setup-extension-repo.py +67 -0
- real_ladybug-source/scripts/test-simsimd-dispatch.py +45 -0
- real_ladybug-source/scripts/update-nightly-build-version.py +81 -0
- real_ladybug-source/third_party/brotli/scripts/dictionary/step-01-download-rfc.py +16 -0
- real_ladybug-source/third_party/brotli/scripts/dictionary/step-02-rfc-to-bin.py +34 -0
- real_ladybug-source/third_party/brotli/scripts/dictionary/step-03-validate-bin.py +35 -0
- real_ladybug-source/third_party/brotli/scripts/dictionary/step-04-generate-java-literals.py +85 -0
- real_ladybug-source/third_party/pybind11/tools/codespell_ignore_lines_from_errors.py +35 -0
- real_ladybug-source/third_party/pybind11/tools/libsize.py +36 -0
- real_ladybug-source/third_party/pybind11/tools/make_changelog.py +63 -0
- real_ladybug-source/tools/python_api/build/real_ladybug/__init__.py +83 -0
- real_ladybug-source/tools/python_api/build/real_ladybug/async_connection.py +226 -0
- real_ladybug-source/tools/python_api/build/real_ladybug/connection.py +323 -0
- real_ladybug-source/tools/python_api/build/real_ladybug/constants.py +7 -0
- real_ladybug-source/tools/python_api/build/real_ladybug/database.py +307 -0
- real_ladybug-source/tools/python_api/build/real_ladybug/prepared_statement.py +51 -0
- real_ladybug-source/tools/python_api/build/real_ladybug/py.typed +0 -0
- real_ladybug-source/tools/python_api/build/real_ladybug/query_result.py +511 -0
- real_ladybug-source/tools/python_api/build/real_ladybug/torch_geometric_feature_store.py +185 -0
- real_ladybug-source/tools/python_api/build/real_ladybug/torch_geometric_graph_store.py +131 -0
- real_ladybug-source/tools/python_api/build/real_ladybug/torch_geometric_result_converter.py +282 -0
- real_ladybug-source/tools/python_api/build/real_ladybug/types.py +39 -0
- real_ladybug-source/tools/python_api/src_py/__init__.py +83 -0
- real_ladybug-source/tools/python_api/src_py/async_connection.py +226 -0
- real_ladybug-source/tools/python_api/src_py/connection.py +323 -0
- real_ladybug-source/tools/python_api/src_py/constants.py +7 -0
- real_ladybug-source/tools/python_api/src_py/database.py +307 -0
- real_ladybug-source/tools/python_api/src_py/prepared_statement.py +51 -0
- real_ladybug-source/tools/python_api/src_py/py.typed +0 -0
- real_ladybug-source/tools/python_api/src_py/query_result.py +511 -0
- real_ladybug-source/tools/python_api/src_py/torch_geometric_feature_store.py +185 -0
- real_ladybug-source/tools/python_api/src_py/torch_geometric_graph_store.py +131 -0
- real_ladybug-source/tools/python_api/src_py/torch_geometric_result_converter.py +282 -0
- real_ladybug-source/tools/python_api/src_py/types.py +39 -0
- real_ladybug-source/tools/python_api/test/conftest.py +230 -0
- real_ladybug-source/tools/python_api/test/disabled_test_extension.py +73 -0
- real_ladybug-source/tools/python_api/test/ground_truth.py +430 -0
- real_ladybug-source/tools/python_api/test/test_arrow.py +694 -0
- real_ladybug-source/tools/python_api/test/test_async_connection.py +159 -0
- real_ladybug-source/tools/python_api/test/test_blob_parameter.py +145 -0
- real_ladybug-source/tools/python_api/test/test_connection.py +49 -0
- real_ladybug-source/tools/python_api/test/test_database.py +234 -0
- real_ladybug-source/tools/python_api/test/test_datatype.py +372 -0
- real_ladybug-source/tools/python_api/test/test_df.py +564 -0
- real_ladybug-source/tools/python_api/test/test_dict.py +112 -0
- real_ladybug-source/tools/python_api/test/test_exception.py +54 -0
- real_ladybug-source/tools/python_api/test/test_fsm.py +227 -0
- real_ladybug-source/tools/python_api/test/test_get_header.py +49 -0
- real_ladybug-source/tools/python_api/test/test_helper.py +8 -0
- real_ladybug-source/tools/python_api/test/test_issue.py +147 -0
- real_ladybug-source/tools/python_api/test/test_iteration.py +96 -0
- real_ladybug-source/tools/python_api/test/test_networkx.py +437 -0
- real_ladybug-source/tools/python_api/test/test_parameter.py +340 -0
- real_ladybug-source/tools/python_api/test/test_prepared_statement.py +117 -0
- real_ladybug-source/tools/python_api/test/test_query_result.py +54 -0
- real_ladybug-source/tools/python_api/test/test_query_result_close.py +44 -0
- real_ladybug-source/tools/python_api/test/test_scan_pandas.py +676 -0
- real_ladybug-source/tools/python_api/test/test_scan_pandas_pyarrow.py +714 -0
- real_ladybug-source/tools/python_api/test/test_scan_polars.py +165 -0
- real_ladybug-source/tools/python_api/test/test_scan_pyarrow.py +167 -0
- real_ladybug-source/tools/python_api/test/test_timeout.py +11 -0
- real_ladybug-source/tools/python_api/test/test_torch_geometric.py +640 -0
- real_ladybug-source/tools/python_api/test/test_torch_geometric_remote_backend.py +111 -0
- real_ladybug-source/tools/python_api/test/test_udf.py +207 -0
- real_ladybug-source/tools/python_api/test/test_version.py +6 -0
- real_ladybug-source/tools/python_api/test/test_wal.py +80 -0
- real_ladybug-source/tools/python_api/test/type_aliases.py +10 -0
- real_ladybug-source/tools/rust_api/update_version.py +47 -0
- real_ladybug-source/tools/shell/test/conftest.py +218 -0
- real_ladybug-source/tools/shell/test/test_helper.py +60 -0
- real_ladybug-source/tools/shell/test/test_shell_basics.py +325 -0
- real_ladybug-source/tools/shell/test/test_shell_commands.py +656 -0
- real_ladybug-source/tools/shell/test/test_shell_control_edit.py +438 -0
- real_ladybug-source/tools/shell/test/test_shell_control_search.py +468 -0
- real_ladybug-source/tools/shell/test/test_shell_esc_edit.py +232 -0
- real_ladybug-source/tools/shell/test/test_shell_esc_search.py +162 -0
- 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,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,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
|