Initial commit
This commit is contained in:
@@ -0,0 +1,504 @@
|
||||
from dotenv import load_dotenv, find_dotenv
|
||||
load_dotenv(find_dotenv())
|
||||
import os
|
||||
import modules.logger_tool as logger
|
||||
log_name = 'api_modules_database_tools_neo4j_session_tools'
|
||||
log_dir = os.getenv("LOG_PATH", "/logs") # Default path as fallback
|
||||
logging = logger.get_logger(
|
||||
name=log_name,
|
||||
log_level=os.getenv("LOG_LEVEL", "DEBUG"),
|
||||
log_path=log_dir,
|
||||
log_file=log_name,
|
||||
runtime=True,
|
||||
log_format='default'
|
||||
)
|
||||
import modules.database.tools.queries as query
|
||||
from contextlib import suppress
|
||||
|
||||
def get_node_by_unique_id_and_adjacent_nodes(session, unique_id):
|
||||
return session.read_transaction(_get_node_by_unique_id_and_adjacent_nodes, unique_id)
|
||||
|
||||
def _get_node_by_unique_id_and_adjacent_nodes(tx, unique_id):
|
||||
query = """
|
||||
MATCH (n {unique_id: $unique_id})
|
||||
OPTIONAL MATCH (n)-[r]-(adjacent)
|
||||
RETURN n AS node, COLLECT(DISTINCT {node: adjacent, relationship: r}) AS connected_nodes
|
||||
"""
|
||||
result = tx.run(query, unique_id=unique_id)
|
||||
record = result.single()
|
||||
if record:
|
||||
node = record["node"]
|
||||
connected_nodes = record["connected_nodes"]
|
||||
return {"node": node, "connected_nodes": connected_nodes}
|
||||
return None
|
||||
|
||||
def delete_all_nodes_and_relationships(session):
|
||||
total_deleted = 0
|
||||
while True:
|
||||
deleted_count = session.write_transaction(_delete_batch)
|
||||
total_deleted += deleted_count
|
||||
if deleted_count == 0:
|
||||
break
|
||||
|
||||
def _delete_batch(tx):
|
||||
result_data = tx.run(query.delete_batch, batch_size=10000).single()
|
||||
return 0 if result_data is None else result_data[0]
|
||||
|
||||
def delete_all_constraints(session):
|
||||
if show_constraints_result := session.run(query.show_constraints).data():
|
||||
for constraint in show_constraints_result:
|
||||
constraint_name = constraint['name']
|
||||
session.run(query.drop_constraint(constraint_name))
|
||||
|
||||
def reset_all_indexes(session):
|
||||
indexes = session.run(query.show_indexes).data()
|
||||
for index in indexes:
|
||||
index_name = index['name']
|
||||
session.run(query.drop_index(index_name))
|
||||
|
||||
def reset_databases(session):
|
||||
delete_all_nodes_and_relationships(session)
|
||||
delete_all_constraints(session)
|
||||
reset_all_indexes(session)
|
||||
|
||||
def close_session(session):
|
||||
if session:
|
||||
with suppress(Exception):
|
||||
session.close()
|
||||
|
||||
def create_node(session, label, properties, returns=False):
|
||||
"""
|
||||
Function to create a node in Neo4j database.
|
||||
|
||||
Args:
|
||||
driver (neo4j.Driver): The Neo4j driver.
|
||||
session (str): The Neo4j session.
|
||||
label (str): The label of the node.
|
||||
properties (dict): A dictionary of properties for the node.
|
||||
|
||||
Example usage:
|
||||
create_node(neo4j_driver, "Topic", {"TopicID": "AP.PAG10", "Title": "Topic 10"})
|
||||
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
transaction = session.write_transaction(_create_node, label, properties)
|
||||
if returns:
|
||||
transaction_id = transaction.id
|
||||
# logging.database(f"Created {label} node with transaction ID {transaction_id} and properties {properties}")
|
||||
print(f"Created {label} node with transaction ID {transaction_id} and properties {properties}")
|
||||
return find_node_by_transaction_id(session, transaction_id)
|
||||
else:
|
||||
# logging.warning(f"Failed to create {label} node with properties {properties}")
|
||||
print(f"Failed to create {label} node with properties {properties}")
|
||||
return None
|
||||
|
||||
def _create_node(tx, label, properties):
|
||||
query = f"""
|
||||
CREATE (n:{label} $properties)
|
||||
RETURN n
|
||||
"""
|
||||
# logging.query(f"Running query: {query}")
|
||||
print(f"Running query: {query}")
|
||||
result = tx.run(query, properties=properties)
|
||||
return result.single()[0] if result.single() is not None else None # Handle no record found
|
||||
|
||||
# Function to find a node by its element ID
|
||||
def find_node_by_transaction_id(session, transaction_id):
|
||||
"""
|
||||
Function to find a node in Neo4j database by its element ID.
|
||||
|
||||
Args:
|
||||
driver (neo4j.Driver): The Neo4j driver.
|
||||
element_id (str): The element ID of the node to find.
|
||||
|
||||
Returns:
|
||||
The matched node.
|
||||
"""
|
||||
return session.read_transaction(_find_node_by_element_id, transaction_id)
|
||||
|
||||
def _find_node_by_element_id(tx, transaction_id):
|
||||
query = """
|
||||
MATCH (n)
|
||||
WHERE id(n) = $transaction_id
|
||||
RETURN n
|
||||
"""
|
||||
# logging.query(f"Running query: {query}")
|
||||
result = tx.run(query, transaction_id=transaction_id)
|
||||
record = result.single() # Get the single result record, if any
|
||||
return record[0] if record is not None else None # Handle no record found
|
||||
|
||||
# Function to create a relationship between two nodes in Neo4j
|
||||
def create_relationship(session, start_node, end_node, label, properties=None, returns=False):
|
||||
"""
|
||||
Function to create a relationship between two nodes in Neo4j database.
|
||||
|
||||
Args:
|
||||
driver (neo4j.Driver): The Neo4j driver.
|
||||
session (str): The Neo4j session.
|
||||
start_node (str): The ID of the start node.
|
||||
end_node (str): The ID of the end node.
|
||||
rel_type (str): The type of the relationship.
|
||||
properties (dict): A dictionary of properties for the relationship.
|
||||
|
||||
Example usage:
|
||||
create_relationship(neo4j_driver, "AP.PAG10", "AP.PAG11", "HAS_NEXT")
|
||||
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
relationship = session.write_transaction(_create_relationship, start_node, end_node, label, properties)
|
||||
if returns:
|
||||
relationship_id = relationship.id
|
||||
return find_relationship_by_relationship_id(session, relationship_id)
|
||||
else:
|
||||
return None
|
||||
|
||||
def _create_relationship(tx, start_node, end_node, label, properties):
|
||||
query = f"""
|
||||
MATCH (a), (b)
|
||||
WHERE ID(a) = $start_node_id AND ID(b) = $end_node_id
|
||||
CREATE (a)-[r:{label}]->(b)
|
||||
RETURN r
|
||||
"""
|
||||
# logging.query(f"Running query: {query}")
|
||||
result = tx.run(query, start_node_id=start_node.id, end_node_id=end_node.id, properties=properties)
|
||||
single_result = result.single()
|
||||
return single_result[0] if single_result is not None else None
|
||||
|
||||
def order_list_of_nodes_by_property(session, label, property_name, order="ASC"):
|
||||
"""
|
||||
Function to order a list of nodes in Neo4j database by a property.
|
||||
|
||||
Args:
|
||||
driver (neo4j.Driver): The Neo4j driver.
|
||||
label (str): The label of the nodes to find.
|
||||
property_name (str): The name of the property to order by.
|
||||
order (str): The order of the sorting (ASC or DESC).
|
||||
|
||||
Returns:
|
||||
List of matched nodes.
|
||||
"""
|
||||
return session.read_transaction(_order_list_of_nodes_by_property, label, property_name, order)
|
||||
|
||||
def _order_list_of_nodes_by_property(tx, label, property_name, order):
|
||||
query = f"""
|
||||
MATCH (n:{label})
|
||||
RETURN n
|
||||
ORDER BY n.{property_name} {order}
|
||||
"""
|
||||
# logging.query(f"Running query: {query}")
|
||||
result = tx.run(query)
|
||||
return [record["n"] for record in result]
|
||||
|
||||
def find_relationship_by_relationship_id(session, relationship_id):
|
||||
"""
|
||||
Function to find a relationship in Neo4j database by its relationship ID.
|
||||
|
||||
Args:
|
||||
driver (neo4j.Driver): The Neo4j driver.
|
||||
relationship_id (str): The relationship ID of the relationship to find.
|
||||
|
||||
Returns:
|
||||
The matched relationship.
|
||||
"""
|
||||
return session.read_transaction(_find_relationship_by_relationship_id, relationship_id)
|
||||
|
||||
def _find_relationship_by_relationship_id(tx, relationship_id):
|
||||
query = """
|
||||
MATCH ()-[r]->()
|
||||
WHERE id(r) = $relationship_id
|
||||
"""
|
||||
# logging.query(f"Running query: {query}")
|
||||
print(f"Running query: {query}")
|
||||
result = tx.run(query, relationship_id=relationship_id)
|
||||
record = result.single() # Get the single result record, if any
|
||||
return record[0] if record is not None else None # Handle no record found
|
||||
|
||||
# Function to find nodes in Neo4j database by label
|
||||
def find_nodes_by_label(session, label):
|
||||
"""
|
||||
Function to find nodes in Neo4j database by label.
|
||||
|
||||
Args:
|
||||
driver (neo4j.Driver): The Neo4j driver.
|
||||
label (str): The label of the nodes to find.
|
||||
|
||||
Example usage:
|
||||
find_nodes_by_label(neo4j_driver, "Topic")
|
||||
|
||||
Returns:
|
||||
List of matched nodes.
|
||||
"""
|
||||
return session.read_transaction(_find_nodes_by_label, label)
|
||||
|
||||
def _find_nodes_by_label(tx, label):
|
||||
query = f"""
|
||||
MATCH (n:{label})
|
||||
RETURN n
|
||||
"""
|
||||
# logging.query(f"Running query: {query}")
|
||||
print(f"Running query: {query}")
|
||||
result = tx.run(query)
|
||||
return [record["n"] for record in result]
|
||||
|
||||
def get_node_by_unique_id(session, unique_id):
|
||||
return session.read_transaction(_get_node_by_unique_id, unique_id)
|
||||
|
||||
def _get_node_by_unique_id(tx, unique_id):
|
||||
query = f"""
|
||||
MATCH (n)
|
||||
WHERE n.unique_id = $unique_id
|
||||
RETURN n
|
||||
"""
|
||||
logging.debug(f"Executing query with unique_id: {unique_id}")
|
||||
result = tx.run(query, unique_id=unique_id)
|
||||
record = result.single()
|
||||
if record is None:
|
||||
logging.warning(f"No node found with unique_id: {unique_id}")
|
||||
return None
|
||||
return record[0]
|
||||
|
||||
# Function to find nodes in Neo4j database by label and properties
|
||||
def find_nodes_by_label_and_properties(session, label, properties):
|
||||
"""
|
||||
Function to find nodes in Neo4j database by label and properties.
|
||||
|
||||
Args:
|
||||
session (neo4j.Session): The Neo4j session.
|
||||
label (str): The label of the nodes to find.
|
||||
properties (dict): A dictionary of properties to match.
|
||||
|
||||
Returns:
|
||||
List of matched nodes.
|
||||
"""
|
||||
logging.debug(f"Finding nodes with label: {label} and properties: {properties}")
|
||||
with session:
|
||||
response = session.read_transaction(_find_nodes_by_label_and_properties, label, properties)
|
||||
logging.debug(f"Response: {response}")
|
||||
return response
|
||||
|
||||
def _find_nodes_by_label_and_properties(tx, label, properties):
|
||||
query = f"""
|
||||
MATCH (n:{label})
|
||||
WHERE {' AND '.join([f'n.{key} = ${key}' for key in properties.keys()])}
|
||||
RETURN n
|
||||
"""
|
||||
logging.debug(f"Running query: {query}")
|
||||
result = tx.run(query, **properties)
|
||||
logging.debug(f"Result: {result}")
|
||||
return [record["n"] for record in result]
|
||||
|
||||
# Function to find relationships in Neo4j database by type
|
||||
def find_relationships_by_type(session, rel_type):
|
||||
"""
|
||||
Function to find relationships in Neo4j database by type.
|
||||
|
||||
Args:
|
||||
driver (neo4j.Driver): The Neo4j driver.
|
||||
rel_type (str): The type of the relationships to find.
|
||||
|
||||
Returns:
|
||||
List of matched relationships.
|
||||
"""
|
||||
return session.read_transaction(_find_relationships_by_type, rel_type)
|
||||
|
||||
def _find_relationships_by_type(tx, rel_type):
|
||||
query = f"""
|
||||
MATCH ()-[r:{rel_type}]->()
|
||||
RETURN r
|
||||
"""
|
||||
# logging.query(f"Running query: {query}")
|
||||
print(f"Running query: {query}")
|
||||
result = tx.run(query)
|
||||
return [record["r"] for record in result]
|
||||
|
||||
# Function to find relationships in Neo4j database by type and properties
|
||||
def find_relationships_by_type_and_properties(session, label, properties):
|
||||
"""
|
||||
Function to find relationships in Neo4j database by type and properties.
|
||||
|
||||
Args:
|
||||
driver (neo4j.Driver): The Neo4j driver.
|
||||
rel_type (str): The type of the relationships to find.
|
||||
properties (dict): A dictionary of properties to match.
|
||||
|
||||
Returns:
|
||||
List of matched relationships.
|
||||
"""
|
||||
return session.read_transaction(_find_relationships_by_type_and_properties, label, properties)
|
||||
|
||||
|
||||
def _find_relationships_by_type_and_properties(tx, label, properties):
|
||||
query = f"""
|
||||
MATCH (a)-[r:{label}]->(b)
|
||||
WHERE {' AND '.join([f'r.{key} = ${key}' for key in properties.keys()])}
|
||||
RETURN r
|
||||
"""
|
||||
# logging.query(f"Running query: {query}")
|
||||
print(f"Running query: {query}")
|
||||
result = tx.run(query, **properties)
|
||||
return [record["r"] for record in result]
|
||||
|
||||
# Function to find nodes and relationships in Neo4j database by label and properties
|
||||
def find_nodes_and_relationships_by_label_and_properties(session, label, properties):
|
||||
"""
|
||||
Function to find nodes and relationships in Neo4j database by label and properties.
|
||||
|
||||
Args:
|
||||
driver (neo4j.Driver): The Neo4j driver.
|
||||
label (str): The label of the nodes to find.
|
||||
properties (dict): A dictionary of properties to match.
|
||||
|
||||
Returns:
|
||||
List of matched nodes and relationships.
|
||||
"""
|
||||
return session.read_transaction(_find_nodes_and_relationships_by_label_and_properties, label, properties)
|
||||
|
||||
def _find_nodes_and_relationships_by_label_and_properties(tx, label, properties):
|
||||
query = f"""
|
||||
MATCH (n:{label})
|
||||
WHERE {' AND '.join([f'n.{key} = ${key}' for key in properties.keys()])}
|
||||
RETURN n
|
||||
"""
|
||||
# logging.query(f"Running query: {query}")
|
||||
result = tx.run(query, **properties)
|
||||
return [record["n"] for record in result]
|
||||
|
||||
# Function to delete nodes in Neo4j based on given criteria
|
||||
def delete_nodes(session, criteria, delete_related=False):
|
||||
"""
|
||||
Function to delete nodes in Neo4j based on given criteria.
|
||||
|
||||
Args:
|
||||
driver (neo4j.Driver): The Neo4j driver.
|
||||
criteria (dict): A dictionary containing the properties to match for deletion.
|
||||
delete_related (bool): If True, deletes related nodes and relationships; otherwise, deletes only the matched nodes.
|
||||
|
||||
Example usage:
|
||||
# Delete only the nodes matching the criteria
|
||||
delete_nodes(neo4j_driver, {'TopicID': 'AP.PAG10'})
|
||||
|
||||
# Delete the nodes and their related relationships
|
||||
delete_nodes(neo4j_driver, {'TopicID': 'AP.PAG10'}, delete_related=True)
|
||||
"""
|
||||
session.write_transaction(_delete_nodes, criteria, delete_related)
|
||||
|
||||
def _delete_nodes(tx, criteria, delete_related=False):
|
||||
"""
|
||||
Internal function to execute a Cypher query to delete nodes based on criteria.
|
||||
|
||||
Args:
|
||||
tx (neo4j.Transaction): The Neo4j transaction.
|
||||
criteria (dict): A dictionary containing the properties to match for deletion.
|
||||
delete_related (bool): Specifies whether to delete related nodes and relationships.
|
||||
"""
|
||||
condition_str = " AND ".join([f"n.{key} = ${key}" for key in criteria])
|
||||
if delete_related:
|
||||
query = f"""
|
||||
MATCH (n)-[r]-()
|
||||
WHERE {condition_str}
|
||||
DELETE n, r
|
||||
"""
|
||||
else:
|
||||
query = f"""
|
||||
MATCH (n)
|
||||
WHERE {condition_str}
|
||||
DELETE n
|
||||
"""
|
||||
# logging.query(f"Running query: {query}")
|
||||
tx.run(query, **criteria)
|
||||
|
||||
# Function to delete all nodes and relationships in the Neo4j database in batches
|
||||
def delete_lots_of_nodes_and_relationships(session):
|
||||
"""
|
||||
Function to delete all nodes and relationships in the Neo4j database in batches.
|
||||
|
||||
Args:
|
||||
driver (neo4j.Driver): The Neo4j driver.
|
||||
session (str): The Neo4j session.
|
||||
"""
|
||||
total_deleted = 0
|
||||
while True:
|
||||
deleted_count = session.write_transaction(_delete_batch)
|
||||
total_deleted += deleted_count
|
||||
if deleted_count == 0:
|
||||
break # Exit the loop if no more nodes are deleted
|
||||
# logging.prod(f"All nodes and relationships have been deleted. Total deleted: {total_deleted}")
|
||||
print(f"Neo4j: All nodes and relationships have been deleted. Total deleted: {total_deleted}")
|
||||
|
||||
def _delete_batch(tx):
|
||||
"""
|
||||
Function to execute a Cypher query to delete a batch of nodes and relationships.
|
||||
|
||||
Args:
|
||||
tx (neo4j.Transaction): The Neo4j transaction.
|
||||
"""
|
||||
batch_size = 10000 # Adjust the batch size according to your needs
|
||||
query = """
|
||||
MATCH (n)
|
||||
WITH n LIMIT $batch_size
|
||||
DETACH DELETE n
|
||||
RETURN count(*)
|
||||
"""
|
||||
# logging.query(f"Running query: {query}")
|
||||
result = tx.run(query, batch_size=batch_size)
|
||||
result_data = result.single()
|
||||
|
||||
if result_data is None:
|
||||
return 0
|
||||
deleted_count = result_data[0]
|
||||
if deleted_count is None: # This check might be redundant, but kept for clarity
|
||||
return 0
|
||||
if deleted_count > 0:
|
||||
# logging.database(f"Deleted {deleted_count} nodes.")
|
||||
print(f"Neo4j: Deleted {deleted_count} nodes.")
|
||||
return deleted_count
|
||||
|
||||
def delete_all_constraints(session):
|
||||
# Correct command to fetch all constraints for Neo4j 4.x and later
|
||||
constraints_query = "SHOW CONSTRAINTS"
|
||||
# logging.query(f"Running query: {constraints_query}")
|
||||
if constraints_query_result := session.run(constraints_query).data():
|
||||
for constraint in constraints_query_result:
|
||||
# Ensure correct key is used to extract constraint name
|
||||
constraint_name = constraint['name'] # Adjust this if necessary
|
||||
drop_query = f"DROP CONSTRAINT {constraint_name}"
|
||||
# logging.query(f"Running query: {drop_query}")
|
||||
session.run(drop_query)
|
||||
# logging.database(f"Dropped constraint: {constraint_name}")
|
||||
print(f"Neo4j: Dropped constraint: {constraint_name}")
|
||||
else:
|
||||
# logging.warning("No constraints found to delete.")
|
||||
print("Neo4j: No constraints found to delete.")
|
||||
|
||||
def reset_all_indexes(session):
|
||||
indexes = session.run("SHOW INDEXES").data()
|
||||
for index in indexes:
|
||||
index_name = index['name']
|
||||
session.run(f"DROP INDEX {index_name}")
|
||||
# logging.info(f"Deleted index: {index_name}")
|
||||
print(f"Neo4j: Deleted index: {index_name}")
|
||||
|
||||
def reset_database_in_session(session):
|
||||
logging.debug("Neo4j: Resetting database")
|
||||
delete_lots_of_nodes_and_relationships(session)
|
||||
delete_all_constraints(session)
|
||||
reset_all_indexes(session)
|
||||
logging.info("Neo4j: Database reset")
|
||||
|
||||
def create_database(session, db_name):
|
||||
"""
|
||||
Creates a new database in Neo4j if it does not already exist.
|
||||
|
||||
Args:
|
||||
session (neo4j.Session): The Neo4j session.
|
||||
db_name (str): The name of the database to create.
|
||||
"""
|
||||
logging.debug(f"Neo4j: Creating database {db_name}")
|
||||
query = f"CREATE DATABASE `{db_name}` IF NOT EXISTS"
|
||||
try:
|
||||
session.run(query)
|
||||
logging.info(f"Neo4j: Database {db_name} created successfully.")
|
||||
except Exception as e:
|
||||
logging.error(f"Neo4j: Failed to create database {db_name}: {str(e)}")
|
||||
Reference in New Issue
Block a user