Coverage for oc_ocdm / support / query_utils.py: 77%
73 statements
« prev ^ index » next coverage.py v7.13.4, created at 2026-07-06 20:05 +0000
« prev ^ index » next coverage.py v7.13.4, created at 2026-07-06 20:05 +0000
1#!/usr/bin/python
3# SPDX-FileCopyrightText: 2020-2022 Simone Persiani <iosonopersia@gmail.com>
4# SPDX-FileCopyrightText: 2023-2026 Arcangelo Massari <arcangelo.massari@unibo.it>
5#
6# SPDX-License-Identifier: ISC
8# -*- coding: utf-8 -*-
9from __future__ import annotations
11from typing import TYPE_CHECKING, AbstractSet, List, Set
13from rdflib.term import Node
14from triplelite import RDFTerm, Triple
16if TYPE_CHECKING:
17 from typing import Tuple
19 from oc_ocdm.abstract_entity import AbstractEntity
21MAX_TRIPLES_PER_QUERY = 500
24def _term_to_nt(term: str | RDFTerm | Node) -> str:
25 if isinstance(term, RDFTerm):
26 if term.type == "literal":
27 escaped = term.value.replace("\\", "\\\\").replace('"', '\\"').replace("\n", "\\n").replace("\r", "\\r")
28 if term.lang:
29 return f'"{escaped}"@{term.lang}'
30 return f'"{escaped}"^^<{term.datatype}>'
31 return f"<{term.value}>"
32 if isinstance(term, str):
33 return f"<{term}>"
34 return term.n3()
37def _serialize_triples_to_nt(triples: AbstractSet[Triple]) -> str:
38 return "".join(f"{_term_to_nt(s)} {_term_to_nt(p)} {_term_to_nt(o)} ." for s, p, o in triples)
41def _chunk_set(data: AbstractSet[Triple], chunk_size: int) -> List[Set[Triple]]:
42 data_list = list(data)
43 return [set(data_list[i : i + chunk_size]) for i in range(0, len(data_list), chunk_size)]
46def get_delete_query(graph_iri: str, data: AbstractSet[Triple]) -> Tuple[List[str], int]:
47 num_of_statements: int = len(data)
48 if num_of_statements <= 0:
49 return [], 0
51 if num_of_statements <= MAX_TRIPLES_PER_QUERY:
52 statements: str = _serialize_triples_to_nt(data)
53 return [f"DELETE DATA {{ GRAPH <{graph_iri}> {{ {statements} }} }}"], num_of_statements
55 chunks = _chunk_set(data, MAX_TRIPLES_PER_QUERY)
56 queries: List[str] = []
57 for chunk in chunks:
58 statements = _serialize_triples_to_nt(chunk)
59 queries.append(f"DELETE DATA {{ GRAPH <{graph_iri}> {{ {statements} }} }}")
60 return queries, num_of_statements
63def get_insert_query(graph_iri: str, data: AbstractSet[Triple]) -> Tuple[List[str], int]:
64 num_of_statements: int = len(data)
65 if num_of_statements <= 0:
66 return [], 0
68 if num_of_statements <= MAX_TRIPLES_PER_QUERY:
69 statements: str = _serialize_triples_to_nt(data)
70 return [f"INSERT DATA {{ GRAPH <{graph_iri}> {{ {statements} }} }}"], num_of_statements
72 chunks = _chunk_set(data, MAX_TRIPLES_PER_QUERY)
73 queries: List[str] = []
74 for chunk in chunks:
75 statements = _serialize_triples_to_nt(chunk)
76 queries.append(f"INSERT DATA {{ GRAPH <{graph_iri}> {{ {statements} }} }}")
77 return queries, num_of_statements
80def _compute_graph_changes(entity: AbstractEntity, entity_type: str) -> Tuple[Set[Triple], Set[Triple], int, int]:
81 """
82 Computes the triples to insert and delete for an entity.
84 Args:
85 entity: The entity to analyze
86 entity_type: Type of entity ("graph", "prov", or "metadata")
88 Returns:
89 Tuple of (triples_to_insert, triples_to_delete, added_count, removed_count)
90 """
91 if entity_type == "prov":
92 triples = set(entity.g)
93 return triples, set(), len(triples), 0
95 # Deferred import to break circular dependency:
96 # graph_entity → abstract_entity → support.support → (support/__init__) → query_utils → graph_entity
97 from oc_ocdm.graph.graph_entity import GraphEntity # noqa: E402
99 assert isinstance(entity, GraphEntity)
100 to_be_deleted: bool = entity.to_be_deleted
101 preexisting_triples = entity.preexisting_triples
103 if to_be_deleted:
104 return set(), set(preexisting_triples), 0, len(preexisting_triples)
106 current_triples = set(entity.g)
108 if len(preexisting_triples) == len(current_triples) and set(preexisting_triples) == current_triples:
109 return set(), set(), 0, 0
111 removed_triples = set(preexisting_triples) - current_triples
112 added_triples = current_triples - set(preexisting_triples)
114 return added_triples, removed_triples, len(added_triples), len(removed_triples)
117def get_update_query(entity: AbstractEntity, entity_type: str = "graph") -> Tuple[List[str], int, int]:
118 to_insert, to_delete, n_added, n_removed = _compute_graph_changes(entity, entity_type)
120 if n_added == 0 and n_removed == 0:
121 return [], 0, 0
123 graph_iri = entity.g.identifier
124 if graph_iri is None:
125 raise ValueError("Entity graph has no identifier")
127 delete_queries, _ = get_delete_query(graph_iri, to_delete)
128 insert_queries, _ = get_insert_query(graph_iri, to_insert)
130 return delete_queries + insert_queries, n_added, n_removed