Skip to content

Commit

Permalink
Update api doc for java, nodejs, python and add pytest for in-mem mode (
Browse files Browse the repository at this point in the history
#4095)

* update api doc for java, nodejs and python; add pytest for in-mem mode

* Add test for Java and Node.js

* Fix linter error

---------

Co-authored-by: Chang Liu <liuc223@gmail.com>
  • Loading branch information
ray6080 and mewim committed Aug 16, 2024
1 parent 6a3386a commit 34386c2
Show file tree
Hide file tree
Showing 17 changed files with 144 additions and 30 deletions.
10 changes: 9 additions & 1 deletion tools/java_api/src/main/java/com/kuzudb/KuzuDatabase.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,13 @@ public class KuzuDatabase {
boolean destroyed = false;
boolean readOnly = false;

/**
* Creates a database object. The database will be created in memory with default settings.
*/
public KuzuDatabase() {
this("");
}

/**
* Creates a database object.
* @param databasePath: Database path. If the database does not already exist, it will be created.
Expand All @@ -25,7 +32,8 @@ public KuzuDatabase(String databasePath) {

/**
* Creates a database object.
* @param databasePath: Database path. If the database does not already exist, it will be created.
* @param databasePath: Database path. If the path is empty, or equal to `:memory:`, the database will be created in
* memory.
* @param bufferPoolSize: Max size of the buffer pool in bytes.
* @param enableCompression: Enable compression in storage.
* @param readOnly: Open the database in READ_ONLY mode.
Expand Down
10 changes: 10 additions & 0 deletions tools/java_api/src/test/java/com/kuzudb/test/DatabaseTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,14 @@ void DBCreationAndDestroyWithPathOnly() {
fail("DBCreationAndDestroyWithPathOnly failed");
}
}

@Test
void DBCreationAndDestroyWithNoParam(){
try {
KuzuDatabase database = new KuzuDatabase();
database.destroy();
} catch (Exception e) {
fail("DBCreationAndDestroyWithNoParam failed");
}
}
}
10 changes: 7 additions & 3 deletions tools/nodejs_api/src_js/database.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ class Database {
* executed. To initialize the database immediately, call the `init()`
* function on the returned object.
*
* @param {String} databasePath path to the database file.
* @param {String} databasePath path to the database file. If the path is not specified, or empty, or equal to
`:memory:`, the database will be created in memory.
* @param {Number} bufferManagerSize size of the buffer manager in bytes.
* @param {Boolean} enableCompression whether to enable compression.
* @param {Boolean} readOnly if true, database will be opened in read-only mode.
Expand All @@ -19,13 +20,16 @@ class Database {
* address space limit some environment.
*/
constructor(
databasePath = "",
databasePath,
bufferManagerSize = 0,
enableCompression = true,
readOnly = false,
maxDBSize = 0
) {
if (typeof databasePath !== "string") {
if (!databasePath) {
databasePath = ":memory:";
}
else if (typeof databasePath !== "string") {
throw new Error("Database path must be a string.");
}
if (typeof bufferManagerSize !== "number" || bufferManagerSize < 0) {
Expand Down
42 changes: 42 additions & 0 deletions tools/nodejs_api/test/test_database.js
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,48 @@ describe("Database constructor", function () {
assert.equal(e.message, "Max DB size must be a positive integer.");
}
});

it("should create an in-memory database when no path is provided", async function () {
const testDb = new kuzu.Database();
const conn = new kuzu.Connection(testDb);
let res = await conn.query("CREATE NODE TABLE person(name STRING, age INT64, PRIMARY KEY(name));");
res.close();
res = await conn.query("CREATE (:person {name: 'Alice', age: 30});");
res.close();
res = await conn.query("CREATE (:person {name: 'Bob', age: 40});");
res.close();
res = await conn.query("MATCH (p:person) RETURN p.*;");
const result = await res.getAll();
assert.equal(result.length, 2);
assert.deepEqual(result, [
{ 'p.name': 'Alice', 'p.age': 30 },
{ 'p.name': 'Bob', 'p.age': 40 }
]);
res.close();
conn.close();
testDb.close();
});

it("should create an in-memory database when empty path is provided", async function () {
const testDb = new kuzu.Database("", 1 << 28 /* 256MB */);
const conn = new kuzu.Connection(testDb);
let res = await conn.query("CREATE NODE TABLE person(name STRING, age INT64, PRIMARY KEY(name));");
res.close();
res = await conn.query("CREATE (:person {name: 'Alice', age: 30});");
res.close();
res = await conn.query("CREATE (:person {name: 'Bob', age: 40});");
res.close();
res = await conn.query("MATCH (p:person) RETURN p.*;");
const result = await res.getAll();
assert.equal(result.length, 2);
assert.deepEqual(result, [
{ 'p.name': 'Alice', 'p.age': 30 },
{ 'p.name': 'Bob', 'p.age': 40 }
]);
res.close();
conn.close();
testDb.close();
});
});

describe("Database close", function () {
Expand Down
2 changes: 1 addition & 1 deletion tools/python_api/src_py/connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,7 @@ def create_function(
parsed_params_type = [x if type(x) is str else x.value for x in params_type]
if type(return_type) is not str:
return_type = return_type.value

self._connection.create_function(
name=name,
udf=udf,
Expand Down
7 changes: 5 additions & 2 deletions tools/python_api/src_py/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ class Database:

def __init__(
self,
database_path: str | Path = "",
database_path: str | Path | None = None,
*,
buffer_pool_size: int = 0,
max_num_threads: int = 0,
Expand All @@ -40,7 +40,8 @@ def __init__(
Parameters
----------
database_path : str, Path
The path to database files
The path to database files. If the path is not specified, or empty, or equal to `:memory:`, the database
will be created in memory.
buffer_pool_size : int
The maximum size of buffer pool in bytes. Defaults to ~80% of system memory.
Expand Down Expand Up @@ -73,6 +74,8 @@ def __init__(
environment and 1GB under 32-bit one.
"""
if database_path is None:
database_path = ":memory:"
if isinstance(database_path, Path):
database_path = str(database_path)

Expand Down
2 changes: 2 additions & 0 deletions tools/python_api/test/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,8 +132,10 @@ def init_rdf(conn: kuzu.Connection) -> None:
if line := line.strip():
conn.execute(line)


_POOL_SIZE_: int = 256 * 1024 * 1024


def init_db(path: Path) -> Path:
if Path(path).exists():
shutil.rmtree(path)
Expand Down
48 changes: 47 additions & 1 deletion tools/python_api/test/test_database.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ def open_database_on_subprocess(tmp_path: Path, build_dir: Path) -> None:
print(r"{tmp_path!s}")
"""
)
result = subprocess.run([sys.executable, "-c", code], capture_output=True, text=True)
result = subprocess.run(
[sys.executable, "-c", code], capture_output=True, text=True
)
if result.returncode != 0:
raise RuntimeError(result.stderr)

Expand Down Expand Up @@ -77,3 +79,47 @@ def test_database_context_manager(tmp_path: Path, build_dir: Path) -> None:
# TODO: Enable this test on Windows when the read-only mode is implemented.
if sys.platform != "win32":
open_database_on_subprocess(db_path, build_dir)


def test_in_mem_database_memory_db_path() -> None:
db = kuzu.Database(database_path=":memory:")
assert not db.is_closed
assert db._database is not None

# Open the database on a subprocess. It should raise an exception.
conn = kuzu.Connection(db)
conn.execute("CREATE NODE TABLE person(name STRING, age INT64, PRIMARY KEY(name));")
conn.execute("CREATE (:person {name: 'Alice', age: 30});")
conn.execute("CREATE (:person {name: 'Bob', age: 40});")
result = conn.execute("MATCH (p:person) RETURN p.*")
assert result.get_num_tuples() == 2


def test_in_mem_database_empty_db_path() -> None:
db = kuzu.Database()
assert not db.is_closed
assert db._database is not None

# Open the database on a subprocess. It should raise an exception.
conn = kuzu.Connection(db)
conn.execute("CREATE NODE TABLE person(name STRING, age INT64, PRIMARY KEY(name));")
conn.execute("CREATE (:person {name: 'Alice', age: 30});")
conn.execute("CREATE (:person {name: 'Bob', age: 40});")
result = conn.execute("MATCH (p:person) RETURN p.*")
assert result.get_num_tuples() == 2


def test_in_mem_database_no_db_path() -> None:
with kuzu.Database(database_path="") as db:
assert not db.is_closed
assert db._database is not None

# Open the database on a subprocess. It should raise an exception.
conn = kuzu.Connection(db)
conn.execute(
"CREATE NODE TABLE person(name STRING, age INT64, PRIMARY KEY(name));"
)
conn.execute("CREATE (:person {name: 'Alice', age: 30});")
conn.execute("CREATE (:person {name: 'Bob', age: 40});")
with conn.execute("MATCH (p:person) RETURN p.*") as result:
assert result.get_num_tuples() == 2
3 changes: 2 additions & 1 deletion tools/python_api/test/test_datatype.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
import numpy as np
import pandas as pd
import pytz

from type_aliases import ConnDB


Expand Down Expand Up @@ -100,6 +99,7 @@ def test_double(conn_db_readonly: ConnDB) -> None:
assert not result.has_next()
result.close()


def test_decimal(conn_db_readonly: ConnDB) -> None:
conn, _ = conn_db_readonly
res = conn.execute("UNWIND [1, 2, 3] AS A UNWIND [5.7, 8.3, 2.9] AS B WITH cast(CAST(A AS DECIMAL) * CAST(B AS DECIMAL) AS DECIMAL(18, 1)) AS PROD RETURN COLLECT(PROD) AS RES")
Expand Down Expand Up @@ -127,6 +127,7 @@ def test_decimal(conn_db_readonly: ConnDB) -> None:
Decimal('8.7'),
])


def test_string(conn_db_readonly: ConnDB) -> None:
conn, db = conn_db_readonly
result = conn.execute("MATCH (a:person) WHERE a.ID = 0 RETURN a.fName;")
Expand Down
1 change: 1 addition & 0 deletions tools/python_api/test/test_df.py
Original file line number Diff line number Diff line change
Expand Up @@ -545,6 +545,7 @@ def test_get_df_unicode(conn_db_readonly: ConnDB) -> None:
"Roma",
]


def test_get_df_decimal(conn_db_readonly: ConnDB) -> None:
conn, _ = conn_db_readonly
res = conn.execute("UNWIND [1, 2, 3] AS A UNWIND [5.7, 8.3, 2.9] AS B RETURN CAST(CAST(A AS DECIMAL) * CAST(B AS DECIMAL) AS DECIMAL(18, 1)) AS PROD").get_as_df()
Expand Down
1 change: 0 additions & 1 deletion tools/python_api/test/test_exception.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

import kuzu
import pytest

from type_aliases import ConnDB


Expand Down
5 changes: 4 additions & 1 deletion tools/python_api/test/test_parameter.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from pathlib import Path
import kuzu


def test_struct_param_access(conn_db_readwrite: ConnDB) -> None:
conn, _ = conn_db_readwrite
batch = \
Expand Down Expand Up @@ -45,6 +46,7 @@ def test_struct_param_access(conn_db_readwrite: ConnDB) -> None:
parameters={"batch": batch}
)


def test_array_binding(conn_db_readwrite: ConnDB) -> None:
conn, _ = conn_db_readwrite
conn.execute("CREATE NODE TABLE node(id STRING, embedding DOUBLE[3], PRIMARY KEY(id))")
Expand All @@ -65,7 +67,8 @@ def test_array_binding(conn_db_readwrite: ConnDB) -> None:
# """, {"emb": [4.3, 5.2, 6.7], "emb1": [2.2, 3.3, 5.5]}
# )
# assert str(err.value) == "Binder exception: Left and right type are both ANY, which is not currently supported."



def test_bool_param(conn_db_readonly: ConnDB) -> None:
conn, db = conn_db_readonly
result = conn.execute(
Expand Down
12 changes: 7 additions & 5 deletions tools/python_api/test/test_scan_pandas.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import datetime
import re
from pathlib import Path
from uuid import UUID

import numpy as np
import pandas as pd
import pytest
from uuid import UUID

try:
from zoneinfo import ZoneInfo
Expand Down Expand Up @@ -399,24 +399,26 @@ def test_copy_from_pandas_object(tmp_path: Path) -> None:
assert result.get_next() == ["Karissa", '40']
assert result.has_next() is False


def test_copy_from_pandas_date(tmp_path: Path) -> None:
db = kuzu.Database(tmp_path)
conn = kuzu.Connection(db)
df = pd.DataFrame({"id": [1, 2], "date": [pd.Timestamp('2024-01-03'), pd.Timestamp('2023-10-10')]})
conn.execute("CREATE NODE TABLE Person(id INT16, d TIMESTAMP, PRIMARY KEY (id));")
conn.execute("COPY Person FROM df;")
result = conn.execute("match (p:Person) return p.*")
assert result.get_next() == [1, datetime.datetime(2024,1,3)]
assert result.get_next() == [2, datetime.datetime(2023,10,10)]
assert result.get_next() == [1, datetime.datetime(2024, 1, 3)]
assert result.get_next() == [2, datetime.datetime(2023, 10, 10)]
assert result.has_next() is False


def test_scan_string_to_nested(tmp_path: Path) -> None:
db = kuzu.Database(tmp_path)
conn = kuzu.Connection(db)
df = pd.DataFrame({"id": ["1"], "lstcol": ["[1,2,3]"], "mapcol": ["{'a'=1,'b'=2}"], "structcol": ["{a:1,b:2}"], "lstlstcol": ["[[],[1,2,3],[4,5,6]]"]})
conn.execute("CREATE NODE TABLE tab(id INT64, lstcol INT64[], mapcol MAP(STRING, INT64), structcol STRUCT(a INT64, b INT64), lstlstcol INT64[][], PRIMARY KEY(id))")
conn.execute("COPY tab from df")
result = conn.execute("match (t:tab) return t.*")
assert result.get_next() == [1, [1,2,3], {"'a'": 1, "'b'": 2}, {"a": 1, "b": 2}, [[],[1,2,3],[4,5,6]]]
assert result.get_next() == [1, [1, 2, 3], {"'a'": 1, "'b'": 2}, {"a": 1, "b": 2}, [[], [1, 2, 3], [4, 5, 6]]]
assert not result.has_next()

3 changes: 2 additions & 1 deletion tools/python_api/test/test_scan_pandas_pyarrow.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import math
from decimal import Decimal
import random
import struct
from datetime import datetime, timedelta
from decimal import Decimal
from pathlib import Path

import kuzu
Expand Down Expand Up @@ -634,6 +634,7 @@ def test_pyarrow_map_offset(conn_db_readonly: ConnDB) -> None:

assert idx == 48


def test_pyarrow_decimal(conn_db_readwrite: ConnDB) -> None:
conn, db = conn_db_readwrite
datalength = 4
Expand Down
8 changes: 4 additions & 4 deletions tools/python_api/test/test_scan_pyarrow.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
from datetime import datetime

import pyarrow as pa
import pytest
from type_aliases import ConnDB

def test_pyarrow_basic(conn_db_readonly : ConnDB) -> None:

def test_pyarrow_basic(conn_db_readonly: ConnDB) -> None:
conn, db = conn_db_readonly
tab = pa.Table.from_arrays(
[
Expand All @@ -19,7 +18,8 @@ def test_pyarrow_basic(conn_db_readonly : ConnDB) -> None:
assert (result.get_next() == [2, 'b', False])
assert (result.get_next() == [3, 'c', None])

def test_pyarrow_copy_from(conn_db_readwrite : ConnDB) -> None:

def test_pyarrow_copy_from(conn_db_readwrite: ConnDB) -> None:
conn, db = conn_db_readwrite
tab = pa.Table.from_arrays(
[
Expand Down
7 changes: 0 additions & 7 deletions tools/python_api/test/test_torch_geometric_remote_backend.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,5 @@
from __future__ import annotations

import random

import kuzu
import torch
from test_helper import KUZU_ROOT
from type_aliases import ConnDB

TINY_SNB_KNOWS_GROUND_TRUTH = {
0: [2, 3, 5],
2: [0, 3, 5],
Expand Down
Loading

0 comments on commit 34386c2

Please sign in to comment.