Skip to content

Commit 872972b

Browse files
fix: add the ability to cache entities in duckdb to hopefully improve performance on orphan checks
1 parent db63b1c commit 872972b

1 file changed

Lines changed: 57 additions & 4 deletions

File tree

  • src/dve/core_engine/backends/implementations/duckdb

‎src/dve/core_engine/backends/implementations/duckdb/rules.py‎

Lines changed: 57 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"""Business rule definitions for duckdb backend"""
22

33
from collections.abc import Callable, Iterator
4-
from typing import get_type_hints
4+
from typing import get_type_hints, Optional
55
from uuid import uuid4
66

77
from duckdb import (
@@ -56,11 +56,12 @@
5656
SemiJoin,
5757
TableUnion,
5858
)
59+
from dve.core_engine.configuration.v1.hierarchy import EntityHierarchy
5960
from dve.core_engine.constants import ORPHANED_RECORD_ENTITY_NAME, RECORD_INDEX_COLUMN_NAME
6061
from dve.core_engine.functions import implementations as functions
6162
from dve.core_engine.message import FeedbackMessage
6263
from dve.core_engine.templating import template_object
63-
from dve.core_engine.type_hints import Messages
64+
from dve.core_engine.type_hints import URI, EntityName, Messages
6465

6566

6667
@duckdb_record_index
@@ -73,6 +74,7 @@ class DuckDBStepImplementations(BaseStepImplementations[DuckDBPyRelation]):
7374
def __init__(self, connection: DuckDBPyConnection, **kwargs):
7475
self._connection = connection
7576
self.registered_functions = get_all_registered_udfs(self._connection)
77+
self._temp_tables: dict[EntityName, str] = {}
7678
super().__init__(**kwargs)
7779

7880
@property
@@ -113,6 +115,29 @@ def register_udfs( # type: ignore
113115
connection.sql(_sql)
114116
return cls(connection=connection, **kwargs)
115117

118+
def _materialise_temp_tables_from_entity(
119+
self,
120+
entities: DuckDBEntities,
121+
entity_names: list[EntityName],
122+
refresh: bool = False
123+
):
124+
"""Materialise an entity into a temporary duckdb table."""
125+
for entity_name in entity_names:
126+
temp_name = f"temp_{entity_name}"
127+
if entity_name in self._temp_tables:
128+
if not refresh:
129+
continue
130+
self.connection.unregister(temp_name)
131+
del self._temp_tables[entity_name]
132+
133+
self.connection.register(temp_name, entities[entity_name])
134+
self._temp_tables[entity_name] = temp_name
135+
136+
def _drop_temp_tables(self) -> None:
137+
for temp_name in self._temp_tables.values():
138+
self.connection.unregister(temp_name)
139+
self._temp_tables.clear()
140+
116141
def add(self, entities: DuckDBEntities, *, config: ColumnAddition) -> Messages:
117142
"""A transformation step which adds a column to an entity."""
118143
entity: DuckDBPyRelation = entities[config.entity_name]
@@ -392,9 +417,14 @@ def identify_orphans(
392417
logical OR of its current value and the value it would have been set to otherwise.
393418
394419
"""
395-
source_rel: DuckDBPyRelation = entities[config.entity_name]
420+
self._materialise_temp_tables_from_entity(
421+
entities,
422+
[config.entity_name, config.target_name]
423+
)
424+
425+
source_rel: DuckDBPyRelation = self.connection.table(self._temp_tables[config.entity_name])
396426
source_rel = source_rel.set_alias(config.entity_name)
397-
target_rel: DuckDBPyRelation = entities[config.target_name]
427+
target_rel: DuckDBPyRelation = self.connection.table(self._temp_tables[config.target_name])
398428
target_rel = target_rel.set_alias(config.target_name)
399429

400430
if relation_is_empty(source_rel):
@@ -465,8 +495,31 @@ def remove_orphans(self, entities: DuckDBEntities, *, config: OrphanRemoval) ->
465495

466496
entities[config.entity_name] = filtered_rel
467497

498+
self._materialise_temp_tables_from_entity(entities, [config.entity_name], refresh=True)
499+
468500
return duckdb_rel_to_dictionaries(message_rel)
469501

502+
def identify_and_remove_orphans(
503+
self,
504+
working_directory: URI,
505+
entities: DuckDBEntities,
506+
entity_hierarchy: EntityHierarchy,
507+
key_fields: Optional[dict[str, list[str]]] = None,
508+
) -> tuple[Messages, dict[EntityName, bool]]:
509+
"""
510+
Identifies and removes orphan records by traversing the EntityHierarchy object.
511+
An orphan is a child record whose parent FK does not exist in the parent entity.
512+
Processes recursively: removes orphans at each level, then processes children.
513+
"""
514+
_msgs, entity_issues_found = super().identify_and_remove_orphans(
515+
working_directory,
516+
entities,
517+
entity_hierarchy,
518+
key_fields,
519+
)
520+
self._drop_temp_tables()
521+
return _msgs, entity_issues_found
522+
470523
def check_mandatory_group(
471524
self, entities: DuckDBEntities, *, config: GroupIdentification
472525
) -> Iterator:

0 commit comments

Comments
 (0)