From 8677ab25e89726c0255cebed53d0ba4788d26baa Mon Sep 17 00:00:00 2001 From: Anthony Mahanna Date: Tue, 16 Jan 2024 13:51:33 -0500 Subject: [PATCH 1/5] DE-743 | initial commit --- arango/collection.py | 100 +++++++++++++++++++++++++++++++++++++++++++ tests/test_index.py | 65 ++++++++++++++++++++++++++++ 2 files changed, 165 insertions(+) diff --git a/arango/collection.py b/arango/collection.py index 930ef771..ae036ac4 100644 --- a/arango/collection.py +++ b/arango/collection.py @@ -1617,6 +1617,106 @@ def add_inverted_index( return self._add_index(data) + def add_zkd_index( + self, + fields: Sequence[str], + field_value_types: str, + unique: Optional[bool] = None, + deduplicate: Optional[bool] = None, + name: Optional[str] = None, + in_background: Optional[bool] = None, + ) -> Result[Json]: + """Create a new ZKD Index. + + :param fields: Document fields to index. Unlike for other indexes the + order of the fields does not matter. + :type fields: Sequence[str] + :param field_value_types: The type of the field values. The only allowed + value is "double" at the moment. + :type field_value_types: str + :param unique: Whether the index is unique. + :type unique: bool | None + :param deduplicate: If set to True, inserting duplicate index values + from the same document triggers unique constraint errors. + :type deduplicate: bool | None + :param name: Optional name for the index. + :type name: str | None + :param in_background: Do not hold the collection lock. + :type in_background: bool | None + :return: New index details. + :rtype: dict + :raise arango.exceptions.IndexCreateError: If create fails. + """ + data: Json = { + "type": "zkd", + "fields": fields, + "fieldValueTypes": field_value_types, + } + + if unique is not None: + data["unique"] = unique + if deduplicate is not None: + data["deduplicate"] = deduplicate + if name is not None: + data["name"] = name + if in_background is not None: + data["inBackground"] = in_background + + return self._add_index(data) + + def add_mdi_index( + self, + fields: Sequence[str], + field_value_types: str, + unique: Optional[bool] = None, + deduplicate: Optional[bool] = None, + name: Optional[str] = None, + in_background: Optional[bool] = None, + stored_values: Optional[Sequence[str]] = None, + ) -> Result[Json]: + """Create a new MDI index, previously known as ZKD index. This method + is only usable with ArangoDB 3.12 and later. + + :param fields: Document fields to index. Unlike for other indexes the + order of the fields does not matter. + :type fields: Sequence[str] + :param field_value_types: The type of the field values. The only allowed + value is "double" at the moment. + :type field_value_types: str + :param unique: Whether the index is unique. + :type unique: bool | None + :param deduplicate: If set to True, inserting duplicate index values + from the same document triggers unique constraint errors. + :type deduplicate: bool | None + :param name: Optional name for the index. + :type name: str | None + :param in_background: Do not hold the collection lock. + :type in_background: bool | None + :param stored_values: TODO + :type stored_values: TODO + :return: New index details. + :rtype: dict + :raise arango.exceptions.IndexCreateError: If create fails. + """ + data: Json = { + "type": "mdi", + "fields": fields, + "fieldValueTypes": field_value_types, + } + + if unique is not None: + data["unique"] = unique + if deduplicate is not None: + data["deduplicate"] = deduplicate + if name is not None: + data["name"] = name + if in_background is not None: + data["inBackground"] = in_background + if stored_values is not None: + data["storedValues"] = stored_values + + return self._add_index(data) + def delete_index(self, index_id: str, ignore_missing: bool = False) -> Result[bool]: """Delete an index. diff --git a/tests/test_index.py b/tests/test_index.py index dbf235fa..8560a83d 100644 --- a/tests/test_index.py +++ b/tests/test_index.py @@ -248,6 +248,71 @@ def test_add_inverted_index(icol, enterprise, db_version): icol.delete_index(result["id"]) +def test_add_zkd_index(icol, db_version): + result = icol.add_zkd_index( + name="zkd_index", + fields=["x", "y", "z"], + field_value_types="double", + unique=True, + deduplicate=True, + in_background=False, + ) + + expected_index = { + "name": "zkd_index", + "type": "zkd", + "fields": ["x", "y", "z"], + "unique": True, + "new": True, + "sparse": False, + } + + for key, value in expected_index.items(): + assert result[key] == value + + assert result["id"] in extract("id", icol.indexes()) + + with assert_raises(IndexCreateError) as err: + icol.add_zkd_index(field_value_types="integer", fields=["x", "y", "z"]) + assert err.value.error_code == 10 + + icol.delete_index(result["id"]) + + +def test_add_mdi_index(icol, db_version): + if db_version < version.parse("3.12.0"): + pytest.skip("MDI indexes are usable with 3.12+ only") + + result = icol.add_mdi_index( + name="mdi_index", + fields=["x", "y", "z"], + field_value_types="double", + unique=True, + deduplicate=True, + in_background=False, + ) + + expected_index = { + "name": "mdi_index", + "type": "zkd", + "fields": ["x", "y", "z"], + "unique": True, + "new": True, + "sparse": False, + } + + for key, value in expected_index.items(): + assert result[key] == value + + assert result["id"] in extract("id", icol.indexes()) + + with assert_raises(IndexCreateError) as err: + icol.add_mdi_index(field_value_types="integer", fields=["x", "y", "z"]) + assert err.value.error_code == 10 + + icol.delete_index(result["id"]) + + def test_delete_index(icol, bad_col): old_indexes = set(extract("id", icol.indexes())) icol.add_hash_index(["attr3", "attr4"], unique=True) From ac8273b6f4322c40f0f84c97ae67ecda29100c93 Mon Sep 17 00:00:00 2001 From: Anthony Mahanna Date: Tue, 16 Jan 2024 14:01:22 -0500 Subject: [PATCH 2/5] fix: `format_index` --- arango/formatter.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/arango/formatter.py b/arango/formatter.py index 2058b1d6..6a730b87 100644 --- a/arango/formatter.py +++ b/arango/formatter.py @@ -101,6 +101,8 @@ def format_index(body: Json) -> Json: result["writebuffer_active"] = body["writebufferActive"] if "writebufferSizeMax" in body: result["writebuffer_max_size"] = body["writebufferSizeMax"] + if "fieldValueTypes" in body: + result["field_value_types"] = body["fieldValueTypes"] return verify_format(body, result) From e779125af47169203d4c8c5a62f33f03c4e1e5f6 Mon Sep 17 00:00:00 2001 From: Anthony Mahanna Date: Tue, 16 Jan 2024 14:08:25 -0500 Subject: [PATCH 3/5] remove `sparse` assertion (zkd & mdi indexes don't support the `sparse` property` --- tests/test_index.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/test_index.py b/tests/test_index.py index 8560a83d..2471884b 100644 --- a/tests/test_index.py +++ b/tests/test_index.py @@ -264,7 +264,6 @@ def test_add_zkd_index(icol, db_version): "fields": ["x", "y", "z"], "unique": True, "new": True, - "sparse": False, } for key, value in expected_index.items(): @@ -298,7 +297,6 @@ def test_add_mdi_index(icol, db_version): "fields": ["x", "y", "z"], "unique": True, "new": True, - "sparse": False, } for key, value in expected_index.items(): From d0e3f07624f16e0b1ca0e49acbdefecd4e0a2f82 Mon Sep 17 00:00:00 2001 From: Anthony Mahanna Date: Thu, 25 Jan 2024 09:29:25 -0500 Subject: [PATCH 4/5] cleanup --- arango/collection.py | 37 ++++++++++--------------------------- tests/test_index.py | 12 +++++------- 2 files changed, 15 insertions(+), 34 deletions(-) diff --git a/arango/collection.py b/arango/collection.py index ae036ac4..d483a5b4 100644 --- a/arango/collection.py +++ b/arango/collection.py @@ -1620,10 +1620,9 @@ def add_inverted_index( def add_zkd_index( self, fields: Sequence[str], - field_value_types: str, - unique: Optional[bool] = None, - deduplicate: Optional[bool] = None, + field_value_types: str = "double", name: Optional[str] = None, + unique: Optional[bool] = None, in_background: Optional[bool] = None, ) -> Result[Json]: """Create a new ZKD Index. @@ -1632,15 +1631,12 @@ def add_zkd_index( order of the fields does not matter. :type fields: Sequence[str] :param field_value_types: The type of the field values. The only allowed - value is "double" at the moment. + value is "double" at the moment. Defaults to "double". :type field_value_types: str - :param unique: Whether the index is unique. - :type unique: bool | None - :param deduplicate: If set to True, inserting duplicate index values - from the same document triggers unique constraint errors. - :type deduplicate: bool | None :param name: Optional name for the index. :type name: str | None + :param unique: Whether the index is unique. + :type unique: bool | None :param in_background: Do not hold the collection lock. :type in_background: bool | None :return: New index details. @@ -1655,8 +1651,6 @@ def add_zkd_index( if unique is not None: data["unique"] = unique - if deduplicate is not None: - data["deduplicate"] = deduplicate if name is not None: data["name"] = name if in_background is not None: @@ -1667,12 +1661,10 @@ def add_zkd_index( def add_mdi_index( self, fields: Sequence[str], - field_value_types: str, - unique: Optional[bool] = None, - deduplicate: Optional[bool] = None, + field_value_types: str = "double", name: Optional[str] = None, + unique: Optional[bool] = None, in_background: Optional[bool] = None, - stored_values: Optional[Sequence[str]] = None, ) -> Result[Json]: """Create a new MDI index, previously known as ZKD index. This method is only usable with ArangoDB 3.12 and later. @@ -1681,19 +1673,14 @@ def add_mdi_index( order of the fields does not matter. :type fields: Sequence[str] :param field_value_types: The type of the field values. The only allowed - value is "double" at the moment. + value is "double" at the moment. Defaults to "double". :type field_value_types: str - :param unique: Whether the index is unique. - :type unique: bool | None - :param deduplicate: If set to True, inserting duplicate index values - from the same document triggers unique constraint errors. - :type deduplicate: bool | None :param name: Optional name for the index. :type name: str | None + :param unique: Whether the index is unique. + :type unique: bool | None :param in_background: Do not hold the collection lock. :type in_background: bool | None - :param stored_values: TODO - :type stored_values: TODO :return: New index details. :rtype: dict :raise arango.exceptions.IndexCreateError: If create fails. @@ -1706,14 +1693,10 @@ def add_mdi_index( if unique is not None: data["unique"] = unique - if deduplicate is not None: - data["deduplicate"] = deduplicate if name is not None: data["name"] = name if in_background is not None: data["inBackground"] = in_background - if stored_values is not None: - data["storedValues"] = stored_values return self._add_index(data) diff --git a/tests/test_index.py b/tests/test_index.py index 2471884b..840d744a 100644 --- a/tests/test_index.py +++ b/tests/test_index.py @@ -253,17 +253,16 @@ def test_add_zkd_index(icol, db_version): name="zkd_index", fields=["x", "y", "z"], field_value_types="double", - unique=True, - deduplicate=True, in_background=False, + unique=False, ) expected_index = { "name": "zkd_index", "type": "zkd", "fields": ["x", "y", "z"], - "unique": True, "new": True, + "unique": False, } for key, value in expected_index.items(): @@ -286,17 +285,16 @@ def test_add_mdi_index(icol, db_version): name="mdi_index", fields=["x", "y", "z"], field_value_types="double", - unique=True, - deduplicate=True, in_background=False, + unique=True, ) expected_index = { "name": "mdi_index", - "type": "zkd", + "type": "mdi", "fields": ["x", "y", "z"], - "unique": True, "new": True, + "unique": True, } for key, value in expected_index.items(): From b93971ad694b80247558dd9df11b81b846f93472 Mon Sep 17 00:00:00 2001 From: Anthony Mahanna Date: Thu, 25 Jan 2024 09:49:20 -0500 Subject: [PATCH 5/5] use `split` on `db_version` allows us to test with a nightly build, e.g `3.12.0-NIGHTLY.20240116` --- tests/conftest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/conftest.py b/tests/conftest.py index da95e2ef..184269d7 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -134,7 +134,7 @@ def pytest_configure(config): global_data.username = username global_data.password = password global_data.db_name = tst_db_name - global_data.db_version = version.parse(db_version) + global_data.db_version = version.parse(db_version.split("-")[0]) global_data.sys_db = sys_db global_data.tst_db = tst_db global_data.bad_db = bad_db