diff --git a/src/schema/schema_neo4j_queries.py b/src/schema/schema_neo4j_queries.py index b519dcfd..443f58eb 100644 --- a/src/schema/schema_neo4j_queries.py +++ b/src/schema/schema_neo4j_queries.py @@ -749,6 +749,70 @@ def link_entity_to_direct_ancestors(neo4j_driver, entity_uuid, direct_ancestor_u tx.rollback() raise TransactionError(msg) + + +""" +Create linkages from new direct ancestors to an EXISTING activity node in neo4j. + + +Parameters +---------- +neo4j_driver : neo4j.Driver object + The neo4j database connection pool +entity_uuid : str + The uuid of target child entity +new_ancestor_uuid : str + The uuid of new direct ancestor to be linked +activity_uuid : str + The uuid of the existing activity node to link to +""" +def add_new_ancestors_to_existing_activity(neo4j_driver, new_ancestor_uuids, activity_uuid): + try: + with neo4j_driver.session() as session: + tx = session.begin_transaction() + + create_outgoing_activity_relationships_tx(tx=tx + , source_node_uuids=new_ancestor_uuids + , activity_node_uuid=activity_uuid) + + tx.commit() + except TransactionError as te: + msg = "TransactionError from calling add_new_ancestors_to_existing_activity(): " + logger.exception(msg) + + if tx.closed() == False: + logger.error("Failed to commit add_new_ancestors_to_existing_activity() transaction, rollback") + tx.rollback() + + raise TransactionError(msg) + +""" +Parameters +---------- +neo4j_driver : neo4j.Driver object + The neo4j database connection pool +entity_uuid : str + The uuid of the target entity nodeget_paren + +Returns +------- +str + The uuid of the direct ancestor Activity node +""" +def get_parent_activity_uuid_from_entity(neo4j_driver, entity_uuid): + query = """ + MATCH (activity:Activity)-[:ACTIVITY_OUTPUT]->(entity:Entity {uuid: $entity_uuid}) + RETURN activity.uuid AS activity_uuid + """ + + with neo4j_driver.session() as session: + result = session.run(query, entity_uuid=entity_uuid) + + record = result.single() + if record: + return record["activity_uuid"] + else: + return None """ @@ -1883,6 +1947,52 @@ def _delete_activity_node_and_linkages_tx(tx, uuid): result = tx.run(query) +""" +Delete only the ACTIVITY_INPUT linkages between a target entity and a specific set of its direct ancestors. +The Activity node and the entity nodes remain intact. + +Parameters +---------- +neo4j_driver : neo4j.Driver object + The neo4j database connection pool +entity_uuid : str + The uuid of the target child entity +ancestor_uuids : list + A list of uuids of ancestors whose relationships should be deleted +""" +def delete_ancestor_linkages_tx(neo4j_driver, entity_uuid, ancestor_uuids): + query = ( + "MATCH (a:Entity)-[r:ACTIVITY_INPUT]->(activity:Activity)-[:ACTIVITY_OUTPUT]->(t:Entity {uuid: $entity_uuid}) " + "WHERE a.uuid IN $ancestor_uuids " + "DELETE r" + ) + + logger.info("======delete_ancestor_linkages_tx() query======") + logger.debug(query) + + try: + with neo4j_driver.session() as session: + tx = session.begin_transaction() + + result = tx.run( + query, + entity_uuid=entity_uuid, + ancestor_uuids=ancestor_uuids + ) + + + tx.commit() + + except TransactionError as te: + msg = "TransactionError from calling delete_ancestor_linkages_tx(): " + logger.exception(msg) + + if tx.closed() == False: + logger.error("Failed to commit delete_ancestor_linkages_tx() transaction, rollback") + tx.rollback() + + raise TransactionError(msg) + """ Delete linkages between a publication and its associated collection diff --git a/src/schema/schema_triggers.py b/src/schema/schema_triggers.py index 4445718e..08817c9a 100644 --- a/src/schema/schema_triggers.py +++ b/src/schema/schema_triggers.py @@ -896,21 +896,24 @@ def link_dataset_to_direct_ancestors(property_key, normalized_type, request, use dataset_uuid = existing_data_dict['uuid'] direct_ancestor_uuids = new_data_dict['direct_ancestor_uuids'] - # Generate property values for Activity node - activity_data_dict = schema_manager.generate_activity_data(normalized_type, request, user_token, existing_data_dict) - - try: - # Create a linkage (via one Activity node) between the dataset node and its direct ancestors in neo4j - schema_neo4j_queries.link_entity_to_direct_ancestors(schema_manager.get_neo4j_driver_instance(), dataset_uuid, direct_ancestor_uuids, activity_data_dict) + existing_dataset_ancestor_uuids = schema_neo4j_queries.get_dataset_direct_ancestors(schema_manager.get_neo4j_driver_instance(), dataset_uuid, "uuid") + new_ancestors = set(direct_ancestor_uuids)-set(existing_dataset_ancestor_uuids) + ancestors_to_unlink = set(existing_dataset_ancestor_uuids)-set(direct_ancestor_uuids) + activity_uuid = schema_neo4j_queries.get_parent_activity_uuid_from_entity(schema_manager.get_neo4j_driver_instance(), dataset_uuid) + if new_ancestors: + + try: + schema_neo4j_queries.add_new_ancestors_to_existing_activity(schema_manager.get_neo4j_driver_instance(), list(new_ancestors), activity_uuid) + except TransactionError: + raise - # Delete the cache of this dataset if any cache exists - # Because the `Dataset.direct_ancestors` field - schema_manager.delete_memcached_cache([dataset_uuid]) - except TransactionError: - # No need to log - raise - + if ancestors_to_unlink: + try: + schema_neo4j_queries.delete_ancestor_linkages_tx(schema_manager.get_neo4j_driver_instance(), dataset_uuid, list(ancestors_to_unlink)) + except TransactionError: + raise + """ TriggerTypeEnum.AFTER_CREATE and TriggerTypeEnum.AFTER_UPDATE @@ -1914,21 +1917,22 @@ def link_sample_to_direct_ancestor(property_key, normalized_type, request, user_ # Build a list of direct ancestor uuids # Only one uuid in the list in this case direct_ancestor_uuids = [new_data_dict['direct_ancestor_uuid']] + existing_sample_ancestor_uuids = schema_neo4j_queries.get_sample_direct_ancestor(schema_manager.get_neo4j_driver_instance(), sample_uuid, "uuid") + new_ancestors = set(direct_ancestor_uuids)-set(existing_sample_ancestor_uuids) + ancestors_to_unlink = set(existing_sample_ancestor_uuids)-set(direct_ancestor_uuids) + activity_uuid = schema_neo4j_queries.get_parent_activity_uuid_from_entity(schema_manager.get_neo4j_driver_instance(), sample_uuid) + if new_ancestors: - # Generate property values for Activity node - activity_data_dict = schema_manager.generate_activity_data(normalized_type, request, user_token, existing_data_dict) - - try: - # Create a linkage (via Activity node) - # between the Sample node and the source entity node in neo4j - schema_neo4j_queries.link_entity_to_direct_ancestors(schema_manager.get_neo4j_driver_instance(), sample_uuid, direct_ancestor_uuids, activity_data_dict) + try: + schema_neo4j_queries.add_new_ancestors_to_existing_activity(schema_manager.get_neo4j_driver_instance(), list(new_ancestors), activity_uuid) + except TransactionError: + raise - # Delete the cache of sample if any cache exists - # Because the `Sample.direct_ancestor` field can be updated - schema_manager.delete_memcached_cache([sample_uuid]) - except TransactionError: - # No need to log - raise + if ancestors_to_unlink: + try: + schema_neo4j_queries.delete_ancestor_linkages_tx(schema_manager.get_neo4j_driver_instance(), sample_uuid, list(ancestors_to_unlink)) + except TransactionError: + raise """ TriggerTypeEnum.BEFORE_CREATE and TriggerTypeEnum.BEFORE_UPDATE