Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 4 additions & 6 deletions src/dve/core_engine/backends/base/rules.py
Original file line number Diff line number Diff line change
Expand Up @@ -448,8 +448,7 @@ def process_node(node: HierarchyNode):
record=record, # type: ignore
error_location=location,
error_message=template_object(
node.missing_parent_id_error_message,
record
node.missing_parent_id_error_message, record
),
failure_type="record",
error_type="record",
Expand Down Expand Up @@ -522,10 +521,7 @@ def process_node(node: HierarchyNode) -> bool:
entity=node.parent_entity,
record=record, # type: ignore
error_location=location,
error_message=template_object(
node.no_valid_records_error_message,
record
),
error_message=template_object(node.no_valid_records_error_message, record),
failure_type="record",
error_type="record",
error_code=node.no_valid_records_error_code,
Expand Down Expand Up @@ -624,6 +620,7 @@ def apply_sync_filters(
excluded_columns=filter_column_names,
reporting=rule.reporting,
parent=rule.parent,
error_on_null=True,
),
)
if not success:
Expand Down Expand Up @@ -655,6 +652,7 @@ def apply_sync_filters(
expression=f"NOT ({rule.expression})",
reporting=rule.reporting,
parent=rule.parent,
error_on_null=True,
),
)
if not success:
Expand Down
7 changes: 6 additions & 1 deletion src/dve/core_engine/backends/implementations/duckdb/rules.py
Original file line number Diff line number Diff line change
Expand Up @@ -568,7 +568,12 @@ def notify(self, entities: DuckDBEntities, *, config: Notification) -> Messages:
"""
messages: Messages = []
entity = entities[config.entity_name]

if config.error_if_expression_null:
if entity.filter(f"({config.expression}) IS NULL").shape[0] > 0:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

worth using duckdb_get_entity_count for maintainability?

raise ValueError(
f"The filter evaluated for error code {config.reporting.code}"
+ f" in entity {config.entity_name} produced some NULL results. Please investigate." # pylint: disable=C0301
)
matched = entity.filter(config.expression)
if config.excluded_columns:
matched = matched.select(StarExpression(exclude=config.excluded_columns))
Expand Down
7 changes: 7 additions & 0 deletions src/dve/core_engine/backends/implementations/spark/rules.py
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,13 @@ def notify(self, entities: SparkEntities, *, config: Notification) -> Messages:
messages: Messages = []
entity = entities[config.entity_name]

if config.error_if_expression_null:
if entity.filter(f"({config.expression}) IS NULL").count() > 0:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same as previous comment but for spark?

raise ValueError(
f"The filter evaluated for error code {config.reporting.code}"
+ f" in entity {config.entity_name} produced some NULL results. Please investigate." # pylint: disable=C0301
)

matched = entity.filter(config.expression)
if config.excluded_columns:
matched = matched.drop(*config.excluded_columns)
Expand Down
2 changes: 2 additions & 0 deletions src/dve/core_engine/backends/metadata/rules.py
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,8 @@ class Notification(AbstractStep):
"""Columns to be excluded from the record in the report."""
reporting: ReportingConfig
"""The reporting information for the filter."""
error_if_expression_null: bool = False
"""Raise error if the results of evaluating the expression passed leads to some NULL results"""

def get_required_entities(self) -> set[EntityName]:
return {self.entity_name}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -820,6 +820,25 @@ def test_planets_notify(planets_rel: DuckDBPyRelation):
assert len(messages[0]) == 4


def test_notify_null_errors(planets_rel: DuckDBPyRelation):

config = Notification(
entity_name="planets",
expression="CASE WHEN planet=='Mercury' THEN NULL ELSE False END",
excluded_columns=["mass", "diameter"],
reporting=ReportingConfig(
code="TESTNULLERROR", message="this is a test", location="planet, has_ring_system"
),
error_if_expression_null=True
)
entities = EntityManager({"planets": planets_rel})
messages, success = DUCKDB_STEP_BACKEND.evaluate(entities, config=config)

assert not success
assert len(messages) == 1
assert messages[0].is_critical


def test_read_and_write_simple_parquet(simple_typecast_parquet):
parquet_uri, data = simple_typecast_parquet
entity: DuckDBPyRelation = DUCKDB_STEP_BACKEND.read_parquet(path=parquet_uri)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
from dve.core_engine.backends.base.core import EntityManager
from dve.core_engine.backends.exceptions import MissingEntity
from dve.core_engine.backends.implementations.spark.rules import SparkStepImplementations
from dve.core_engine.backends.metadata.reporting import ReportingConfig
from dve.core_engine.backends.metadata.rules import (
Aggregation,
AntiJoin,
Expand All @@ -32,6 +33,7 @@
HeaderJoin,
InnerJoin,
LeftJoin,
Notification,
OneToOneJoin,
OrphanIdentification,
RenameEntity,
Expand Down Expand Up @@ -447,6 +449,24 @@ def test_join_can_take_all_cols(
expected_rows = sorted(expected_df.collect(), key=lambda row: row.planet)

assert actual_rows == expected_rows

def test_notify_null_errors(planets_df: DataFrame):

config = Notification(
entity_name="planets",
expression="CASE WHEN planet=='Mercury' THEN NULL ELSE False END",
excluded_columns=["mass", "diameter"],
reporting=ReportingConfig(
code="TESTNULLERROR", message="this is a test", location="planet, has_ring_system"
),
error_if_expression_null=True
)
entities = EntityManager({"planets": planets_df})
messages, success = SPARK_STEP_BACKEND.evaluate(entities, config=config)

assert not success
assert len(messages) == 1
assert messages[0].is_critical


def test_one_to_one_join_multi_matches_raises(planets_df: DataFrame, satellites_df: DataFrame):
Expand Down
Loading