diff --git a/tools/java_api/src/main/java/com/kuzudb/KuzuDatabase.java b/tools/java_api/src/main/java/com/kuzudb/KuzuDatabase.java index b5141452480..b9677ead013 100644 --- a/tools/java_api/src/main/java/com/kuzudb/KuzuDatabase.java +++ b/tools/java_api/src/main/java/com/kuzudb/KuzuDatabase.java @@ -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. @@ -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. diff --git a/tools/java_api/src/test/java/com/kuzudb/test/DatabaseTest.java b/tools/java_api/src/test/java/com/kuzudb/test/DatabaseTest.java index 1173592dcea..a5f527aa7e6 100644 --- a/tools/java_api/src/test/java/com/kuzudb/test/DatabaseTest.java +++ b/tools/java_api/src/test/java/com/kuzudb/test/DatabaseTest.java @@ -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"); + } + } } diff --git a/tools/nodejs_api/src_js/database.js b/tools/nodejs_api/src_js/database.js index 135679af78f..53b9240bd08 100644 --- a/tools/nodejs_api/src_js/database.js +++ b/tools/nodejs_api/src_js/database.js @@ -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. @@ -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) { diff --git a/tools/nodejs_api/test/test_database.js b/tools/nodejs_api/test/test_database.js index 6af38b9e767..8ea5b8ec29a 100644 --- a/tools/nodejs_api/test/test_database.js +++ b/tools/nodejs_api/test/test_database.js @@ -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 () { diff --git a/tools/python_api/src_py/connection.py b/tools/python_api/src_py/connection.py index e91642b4041..da2965bc370 100644 --- a/tools/python_api/src_py/connection.py +++ b/tools/python_api/src_py/connection.py @@ -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, diff --git a/tools/python_api/src_py/database.py b/tools/python_api/src_py/database.py index f9e05e7cccf..33cc3633aea 100644 --- a/tools/python_api/src_py/database.py +++ b/tools/python_api/src_py/database.py @@ -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, @@ -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. @@ -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) diff --git a/tools/python_api/test/conftest.py b/tools/python_api/test/conftest.py index 0d75c781ed8..685b0d5ccc8 100644 --- a/tools/python_api/test/conftest.py +++ b/tools/python_api/test/conftest.py @@ -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) diff --git a/tools/python_api/test/test_database.py b/tools/python_api/test/test_database.py index 066a5ef80b1..fe0659adb33 100644 --- a/tools/python_api/test/test_database.py +++ b/tools/python_api/test/test_database.py @@ -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) @@ -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 diff --git a/tools/python_api/test/test_datatype.py b/tools/python_api/test/test_datatype.py index 2eb68320d53..3a6b0393301 100644 --- a/tools/python_api/test/test_datatype.py +++ b/tools/python_api/test/test_datatype.py @@ -7,7 +7,6 @@ import numpy as np import pandas as pd import pytz - from type_aliases import ConnDB @@ -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") @@ -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;") diff --git a/tools/python_api/test/test_df.py b/tools/python_api/test/test_df.py index fcdde5a9f94..3cf7db63015 100644 --- a/tools/python_api/test/test_df.py +++ b/tools/python_api/test/test_df.py @@ -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() diff --git a/tools/python_api/test/test_exception.py b/tools/python_api/test/test_exception.py index 1e5b5524695..18c5452418b 100644 --- a/tools/python_api/test/test_exception.py +++ b/tools/python_api/test/test_exception.py @@ -4,7 +4,6 @@ import kuzu import pytest - from type_aliases import ConnDB diff --git a/tools/python_api/test/test_parameter.py b/tools/python_api/test/test_parameter.py index 50639920683..cf5a7515126 100644 --- a/tools/python_api/test/test_parameter.py +++ b/tools/python_api/test/test_parameter.py @@ -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 = \ @@ -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))") @@ -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( diff --git a/tools/python_api/test/test_scan_pandas.py b/tools/python_api/test/test_scan_pandas.py index 342c3e0a7c6..3eb96b66f57 100644 --- a/tools/python_api/test/test_scan_pandas.py +++ b/tools/python_api/test/test_scan_pandas.py @@ -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 @@ -399,6 +399,7 @@ 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) @@ -406,10 +407,11 @@ def test_copy_from_pandas_date(tmp_path: Path) -> None: 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) @@ -417,6 +419,6 @@ def test_scan_string_to_nested(tmp_path: Path) -> None: 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() - + diff --git a/tools/python_api/test/test_scan_pandas_pyarrow.py b/tools/python_api/test/test_scan_pandas_pyarrow.py index ac7203d18db..a929d341162 100644 --- a/tools/python_api/test/test_scan_pandas_pyarrow.py +++ b/tools/python_api/test/test_scan_pandas_pyarrow.py @@ -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 @@ -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 diff --git a/tools/python_api/test/test_scan_pyarrow.py b/tools/python_api/test/test_scan_pyarrow.py index ec41929c609..ec907b1def9 100644 --- a/tools/python_api/test/test_scan_pyarrow.py +++ b/tools/python_api/test/test_scan_pyarrow.py @@ -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( [ @@ -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( [ diff --git a/tools/python_api/test/test_torch_geometric_remote_backend.py b/tools/python_api/test/test_torch_geometric_remote_backend.py index 5f512c6691a..545fa32f9fd 100644 --- a/tools/python_api/test/test_torch_geometric_remote_backend.py +++ b/tools/python_api/test/test_torch_geometric_remote_backend.py @@ -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], diff --git a/tools/python_api/test/test_udf.py b/tools/python_api/test/test_udf.py index afa43467fc5..83f032857ab 100644 --- a/tools/python_api/test/test_udf.py +++ b/tools/python_api/test/test_udf.py @@ -6,7 +6,6 @@ import pyarrow as pa import pytest from kuzu import Type - from type_aliases import ConnDB @@ -89,7 +88,7 @@ def distancePrimer(pointA, pointB): 'x': (pointA['x'] - pointB['x']) ** 2, 'y': (pointA['y'] - pointB['y']) ** 2 } - + distanceArgs = ["UDFDist", distancePrimer, ["STRUCT(x INT32, y INT32)", "STRUCT(x INT32, y INT32)"], "STRUCT(x INT32, y INT32)"] conn.create_function(*add5IntArgs)