Delete Strategies

This notebook demonstrates the three deletion strategies supported by deleteNode():

  • ``”cascade”`` (default): ON DELETE CASCADE — delete the node and all connected edges.

  • ``”restrict”``: ON DELETE RESTRICT — refuse to delete if the node has connected edges.

  • ``”set_null”``: ON DELETE SET NULL — delete the node and its edges, but keep neighbor nodes.

It also covers cascade delete through WeakNode hierarchies via the propagation flag.

[ ]:
import importlib.util

# Comprovació de presència del paquet, amb l'extra [notebook] del visualitzador de grafs (anywidget)
if (importlib.util.find_spec("cvcdocdb") is None
        or importlib.util.find_spec("cvcdocdb.viewer") is None
        or importlib.util.find_spec("anywidget") is None):
    print("⚠️ cvcdocdb[notebook] no està instal·lat. Iniciant instal·lació...")
    %pip install -q --upgrade "cvcdocdb[notebook]"
    print("✅ Instal·lació completada. L'estat del kernel PODRIA requerir un reinici.")
else:
    print("✅ cvcdocdb i anywidget ja són presents al sistema. Saltant instal·lació.")
[ ]:
import os
import tempfile

from cvcdocdb import NetworkXGraph, Node, WeakNode, Relation

# A fresh directory per run, so re-running the notebook starts from an empty graph
workdir = tempfile.mkdtemp(prefix="cvcdocdb_delete_strategies_")
graph = NetworkXGraph(persistence_path=os.path.join(workdir, "graph.pkl"))

# Create a small graph:
#   Alice --WROTE--> Paper1 --CITES--> Paper2
#   Bob    --WROTE--> Paper1
#
#   Document1
#     |--CONTAINS--> Section1 (WeakNode)
#                        |--CONTAINS--> Page1 (WeakNode of Section1)

doc = Node(pk={"doc": "DOC-001"}, main_label="Document")
section = WeakNode(parent=doc, pk={"section": 1}, main_label="Section")
page = WeakNode(parent=section, pk={"page": 1}, main_label="Page")

alice = Node(pk={"name": "Alice"}, main_label="Author")
bob = Node(pk={"name": "Bob"}, main_label="Author")
paper1 = Node(pk={"title": "P1"}, main_label="Paper")
paper2 = Node(pk={"title": "P2"}, main_label="Paper")

# Insert all nodes
graph.insertNode(doc)
graph.insertNode(section, insert_parent=True)
graph.insertNode(page, insert_parent=True)
graph.insertNode(alice)
graph.insertNode(bob)
graph.insertNode(paper1)
graph.insertNode(paper2)

# Create relations
graph.insertRelation(Relation(alice, paper1, "WROTE"))
graph.insertRelation(Relation(bob, paper1, "WROTE"))
graph.insertRelation(Relation(paper1, paper2, "CITES"))

def show_state(label):
    print(f"\n=== {label} ===")
    print(f"Nodes: {graph.get_node_ids()}")
    print(f"Edges: {graph.get_edges()}")

show_state("Initial state")

Watch the graph

The viewer below shows the whole graph (load_all=True). Each step calls viewer.refresh() after deleting, so the viewer always shows the current state: scroll back up to it after each step (in JupyterLab, right-click the viewer → Create New View for Cell Output keeps it next to the steps).

[ ]:
from cvcdocdb.viewer import show

viewer = show(graph, load_all=True, height=520)
viewer

Strategy 1: CASCADE (default)

on_delete="cascade" with detach=True deletes the node and all connected edges. Neighbor nodes remain as standalone entities. Without detach=True, deleting a node that still has relations raises a RuntimeError.

[ ]:
print("Before deleting paper1:")
show_state("Before CASCADE delete of paper1")

# Delete paper1 with cascade (default); detach=True removes its relations
result = graph.deleteNode(paper1, detach=True, on_delete="cascade")
print(f"deleteNode returned: {result}")

print("\nAfter deleting paper1 (CASCADE):")
show_state("After CASCADE delete of paper1")
print("Note: Alice, Bob, and paper2 still exist, but edges are gone.")

viewer.refresh()

Strategy 2: RESTRICT

on_delete="restrict" refuses to delete if the node has any connected edges. This is useful for enforcing referential integrity.

[ ]:
print("Before trying to delete alice (who has edges):")
show_state("Before RESTRICT delete attempt on alice")

try:
    graph.deleteNode(alice, on_delete="restrict")
    print("ERROR: Should have raised RuntimeError!")
except RuntimeError as e:
    print(f"Caught expected error: {e}")

print("\nGraph unchanged (alice was not deleted):")
show_state("After RESTRICT attempt (unchanged)")

viewer.refresh()

Strategy 3: SET NULL

on_delete="set_null" deletes the node and its edges, but keeps neighbor nodes as standalone entities. Functionally similar to cascade for the graph structure, but with a different semantic meaning.

[ ]:
print("Before deleting bob:")
show_state("Before SET NULL delete of bob")

result = graph.deleteNode(bob, on_delete="set_null")
print(f"deleteNode returned: {result}")

print("\nAfter deleting bob (SET NULL):")
show_state("After SET NULL delete of bob")
print("Note: alice and paper2 still exist; bob is gone with any edges he had left.")

viewer.refresh()

Cascade delete through WeakNode hierarchies

When propagation=True, deleting a parent node also deletes all child WeakNodes connected via edges with _propagate=True. This is the default behavior for WeakNode relations.

[ ]:
# Reset: create a fresh graph with a WeakNode hierarchy
graph2 = NetworkXGraph(persistence_path=os.path.join(workdir, "graph2.pkl"))

doc2 = Node(pk={"doc": "DOC-002"}, main_label="Document")
section2 = WeakNode(parent=doc2, pk={"section": 1}, main_label="Section")
page2 = WeakNode(parent=section2, pk={"page": 1}, main_label="Page")

graph2.insertNode(doc2)
graph2.insertNode(section2, insert_parent=True)
graph2.insertNode(page2, insert_parent=True)

print("Before deleting the document:")
show_state("Before cascade delete of Document")
print("Hierarchy: Document -> Section -> Page")

# Delete the parent document with propagation
# This cascades to Section and Page
result = graph2.deleteNode(doc2, propagation=True, detach=True)
print(f"\ndeleteNode returned: {result}")

print("\nAfter deleting Document with propagation=True:")
show_state("After cascade delete of Document")
print("All nodes (Document, Section, Page) were deleted.")

Summary table

Strategy

Deletes node

Deletes edges

Keeps neighbors

Raises on edges

"cascade" (default)

✅

✅

✅ (standalone)

❌

"restrict"

❌

❌

—

✅ (RuntimeError)

"set_null"

✅

✅

✅ (standalone)

❌

With propagation=True, cascade/delete also removes child WeakNodes.

Cleanup

[ ]:
graph2.close()
# Closing `graph` empties it, and the viewer with it: run this once you are done exploring.
# viewer.close()
# graph.close()