Skip to content

Commit

Permalink
Adds a fail_if_exist option to index creation functions (#180)
Browse files Browse the repository at this point in the history
* Updated changelog

* Added a fail_if_exists option to create_fulltext_index and create_vector_index

* Updated test_indexes.py unit tests

* Added tests to test_indexes.py to test fail_if_exists parameters

* Replace double quotes with single quotes in f strings

* Updated changelog
  • Loading branch information
alexthomas93 authored Oct 15, 2024
1 parent 59c6207 commit 102d799
Show file tree
Hide file tree
Showing 3 changed files with 55 additions and 8 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@

## Next

### Added
- Introduced a fail_if_exist option to index creation functions to control behavior when an index already exists.

### Changed
- Comprehensive rewrite of the README to improve clarity and provide detailed usage examples.

## 1.0.0

### Fixed
Expand Down
12 changes: 8 additions & 4 deletions src/neo4j_graphrag/indexes.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ def create_vector_index(
embedding_property: str,
dimensions: int,
similarity_fn: Literal["euclidean", "cosine"],
fail_if_exists: bool = False,
neo4j_database: Optional[str] = None,
) -> None:
"""
Expand All @@ -46,7 +47,6 @@ def create_vector_index(
See Cypher manual on `creating vector indexes <https://neo4j.com/docs/cypher-manual/current/indexes/semantic-indexes/vector-indexes/#create-vector-index>`_.
Important: This operation will fail if an index with the same name already exists.
Ensure that the index name provided is unique within the database context.
Example:
Expand All @@ -72,6 +72,7 @@ def create_vector_index(
embedding_property="vectorProperty",
dimensions=1536,
similarity_fn="euclidean",
fail_if_exists=False,
)
Expand All @@ -83,6 +84,7 @@ def create_vector_index(
dimensions (int): Vector embedding dimension
similarity_fn (str): case-insensitive values for the vector similarity function:
``euclidean`` or ``cosine``.
fail_if_exists (bool): If True raise an error if the index already exists. Defaults to False.
neo4j_database (Optional[str]): The name of the Neo4j database. If not provided, this defaults to "neo4j" in the database (`see reference to documentation <https://neo4j.com/docs/operations-manual/current/database-administration/#manage-databases-default>`_).
Raises:
Expand All @@ -105,7 +107,7 @@ def create_vector_index(

try:
query = (
f"CREATE VECTOR INDEX $name FOR (n:{label}) ON n.{embedding_property} OPTIONS "
f"CREATE VECTOR INDEX $name {'' if fail_if_exists else 'IF NOT EXISTS'} FOR (n:{label}) ON n.{embedding_property} OPTIONS "
"{ indexConfig: { `vector.dimensions`: toInteger($dimensions), `vector.similarity_function`: $similarity_fn } }"
)
logger.info(f"Creating vector index named '{name}'")
Expand All @@ -123,6 +125,7 @@ def create_fulltext_index(
name: str,
label: str,
node_properties: list[str],
fail_if_exists: bool = False,
neo4j_database: Optional[str] = None,
) -> None:
"""
Expand All @@ -131,7 +134,6 @@ def create_fulltext_index(
See Cypher manual on `creating fulltext indexes <https://neo4j.com/docs/cypher-manual/current/indexes/semantic-indexes/full-text-indexes/#create-full-text-indexes>`_.
Important: This operation will fail if an index with the same name already exists.
Ensure that the index name provided is unique within the database context.
Example:
Expand All @@ -155,6 +157,7 @@ def create_fulltext_index(
INDEX_NAME,
label="Document",
node_properties=["vectorProperty"],
fail_if_exists=False,
)
Expand All @@ -163,6 +166,7 @@ def create_fulltext_index(
name (str): The unique name of the index.
label (str): The node label to be indexed.
node_properties (list[str]): The node properties to create the fulltext index on.
fail_if_exists (bool): If True raise an error if the index already exists. Defaults to False.
neo4j_database (Optional[str]): The name of the Neo4j database. If not provided, this defaults to "neo4j" in the database (`see reference to documentation <https://neo4j.com/docs/operations-manual/current/database-administration/#manage-databases-default>`_).
Raises:
Expand All @@ -180,7 +184,7 @@ def create_fulltext_index(

try:
query = (
"CREATE FULLTEXT INDEX $name "
f"CREATE FULLTEXT INDEX $name {'' if fail_if_exists else 'IF NOT EXISTS'} "
f"FOR (n:`{label}`) ON EACH "
f"[{', '.join(['n.`' + prop + '`' for prop in node_properties])}]"
)
Expand Down
45 changes: 41 additions & 4 deletions tests/unit/test_indexes.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@

def test_create_vector_index_happy_path(driver: MagicMock) -> None:
create_query = (
"CREATE VECTOR INDEX $name FOR (n:People) ON n.name OPTIONS "
"CREATE VECTOR INDEX $name IF NOT EXISTS FOR (n:People) ON n.name OPTIONS "
"{ indexConfig: { `vector.dimensions`: toInteger($dimensions), `vector.similarity_function`: $similarity_fn } }"
)

Expand All @@ -43,9 +43,26 @@ def test_create_vector_index_happy_path(driver: MagicMock) -> None:
)


def test_create_vector_index_fail_if_exists(driver: MagicMock) -> None:
create_query = (
"CREATE VECTOR INDEX $name FOR (n:People) ON n.name OPTIONS "
"{ indexConfig: { `vector.dimensions`: toInteger($dimensions), `vector.similarity_function`: $similarity_fn } }"
)

create_vector_index(
driver, "my-index", "People", "name", 2048, "cosine", fail_if_exists=True
)

driver.execute_query.assert_called_once_with(
create_query,
{"name": "my-index", "dimensions": 2048, "similarity_fn": "cosine"},
database_=None,
)


def test_create_vector_index_ensure_escaping(driver: MagicMock) -> None:
create_query = (
"CREATE VECTOR INDEX $name FOR (n:People) ON n.name OPTIONS "
"CREATE VECTOR INDEX $name IF NOT EXISTS FOR (n:People) ON n.name OPTIONS "
"{ indexConfig: { `vector.dimensions`: toInteger($dimensions), `vector.similarity_function`: $similarity_fn } }"
)

Expand Down Expand Up @@ -120,7 +137,7 @@ def test_create_fulltext_index_happy_path(driver: MagicMock) -> None:
label = "node-label"
text_node_properties = ["property-1", "property-2"]
create_query = (
"CREATE FULLTEXT INDEX $name "
"CREATE FULLTEXT INDEX $name IF NOT EXISTS "
f"FOR (n:`{label}`) ON EACH "
f"[{', '.join(['n.`' + property + '`' for property in text_node_properties])}]"
)
Expand All @@ -134,6 +151,26 @@ def test_create_fulltext_index_happy_path(driver: MagicMock) -> None:
)


def test_create_fulltext_index_fail_if_exists(driver: MagicMock) -> None:
label = "node-label"
text_node_properties = ["property-1", "property-2"]
create_query = (
"CREATE FULLTEXT INDEX $name "
f"FOR (n:`{label}`) ON EACH "
f"[{', '.join(['n.`' + property + '`' for property in text_node_properties])}]"
)

create_fulltext_index(
driver, "my-index", label, text_node_properties, fail_if_exists=True
)

driver.execute_query.assert_called_once_with(
create_query,
{"name": "my-index"},
database_=None,
)


def test_create_fulltext_index_raises_error_with_neo4j_client_error(
driver: MagicMock,
) -> None:
Expand All @@ -159,7 +196,7 @@ def test_create_fulltext_index_ensure_escaping(driver: MagicMock) -> None:
label = "node-label"
text_node_properties = ["property-1", "property-2"]
create_query = (
"CREATE FULLTEXT INDEX $name "
"CREATE FULLTEXT INDEX $name IF NOT EXISTS "
f"FOR (n:`{label}`) ON EACH "
f"[{', '.join(['n.`' + property + '`' for property in text_node_properties])}]"
)
Expand Down

0 comments on commit 102d799

Please sign in to comment.