Skip to content

Commit

Permalink
Simplify relation tree structure.
Browse files Browse the repository at this point in the history
This merges DimensionJoin, Selection, and OrderedSlice into a single
leaf-or-join Select relation, while restricting the relation types that
can appear upstream or downstream of it.  This means almost all trees
will be structured as (with [] meaning optional):

                                          ╭─ [DatasetSearch]
[OrderedSlice] <─ [FindFirst] <─ Select <─┼─ [DataCoordinateUpload]
                                          ╰─ [Materialization] <─ ...

where "..." is another tree of the same structure.  While this *can*
do arbitrary nesting, we never descend past a Materialization when
figuring out where put a new operation when pushing a new filter or
join onto the tree, so all the logic for that can just work with the
first Select it sees rather than worry about what's upstream.  It's
hard to overstate how much of a simplification this is going to be
compared to the more general daf_relation trees.

The downside of this restricted-tree approach is that it obfuscates
what happens in SQL vs. Python postprocessing, and while that might
seem like a good thing (since it *mostly*) is, the performance
difference involved in moving certain operations down to Python can be
so huge that we can't pretend it's a pure implementation, and this
simplified tree structure may make it a bit more difficult to warn or
fail if the user tries to do something that *seems* reasonable but
actually isn't when those implementation/performance implications are
in play.
  • Loading branch information
TallJimbo committed Dec 8, 2023
1 parent 777ce75 commit 5fe882d
Show file tree
Hide file tree
Showing 10 changed files with 84 additions and 137 deletions.
4 changes: 2 additions & 2 deletions python/lsst/daf/butler/queries/_query.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
from ..dimensions import DataCoordinate, DataId, DimensionGroup
from .data_coordinate_results import DataCoordinateResultSpec, RelationDataCoordinateQueryResults
from .driver import QueryDriver
from .relation_tree import Relation
from .relation_tree import RootRelation

Check warning on line 39 in python/lsst/daf/butler/queries/_query.py

View check run for this annotation

Codecov / codecov/patch

python/lsst/daf/butler/queries/_query.py#L35-L39

Added lines #L35 - L39 were not covered by tests

if TYPE_CHECKING:
from .._query_results import DataCoordinateQueryResults, DatasetQueryResults, DimensionRecordQueryResults
Expand Down Expand Up @@ -65,7 +65,7 @@ class RelationQuery(Query):
if this is the only implementation.
"""

def __init__(self, driver: QueryDriver, tree: Relation, include_dimension_records: bool):
def __init__(self, driver: QueryDriver, tree: RootRelation, include_dimension_records: bool):
self._driver = driver
self._tree = tree
self._include_dimension_records = include_dimension_records

Check warning on line 71 in python/lsst/daf/butler/queries/_query.py

View check run for this annotation

Codecov / codecov/patch

python/lsst/daf/butler/queries/_query.py#L68-L71

Added lines #L68 - L71 were not covered by tests
Expand Down
4 changes: 2 additions & 2 deletions python/lsst/daf/butler/queries/data_coordinate_results.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
from .._query_results import DataCoordinateQueryResults, DatasetQueryResults
from ..dimensions import DataCoordinate, DimensionGroup
from .driver import QueryDriver
from .relation_tree import Relation
from .relation_tree import RootRelation

Check warning on line 47 in python/lsst/daf/butler/queries/data_coordinate_results.py

View check run for this annotation

Codecov / codecov/patch

python/lsst/daf/butler/queries/data_coordinate_results.py#L42-L47

Added lines #L42 - L47 were not covered by tests

if TYPE_CHECKING:
from .driver import PageKey
Expand Down Expand Up @@ -82,7 +82,7 @@ class RelationDataCoordinateQueryResults(DataCoordinateQueryResults):
we won't need an ABC if this is the only implementation.
"""

def __init__(self, tree: Relation, driver: QueryDriver, spec: DataCoordinateResultSpec):
def __init__(self, tree: RootRelation, driver: QueryDriver, spec: DataCoordinateResultSpec):
self._tree = tree
self._driver = driver
self._spec = spec

Check warning on line 88 in python/lsst/daf/butler/queries/data_coordinate_results.py

View check run for this annotation

Codecov / codecov/patch

python/lsst/daf/butler/queries/data_coordinate_results.py#L85-L88

Added lines #L85 - L88 were not covered by tests
Expand Down
20 changes: 11 additions & 9 deletions python/lsst/daf/butler/queries/driver.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
from .dataset_results import DatasetRefResultPage, DatasetRefResultSpec
from .dimension_record_results import DimensionRecordResultPage, DimensionRecordResultSpec
from .general_results import GeneralResultPage, GeneralResultSpec
from .relation_tree import Relation
from .relation_tree import RootRelation

Check warning on line 45 in python/lsst/daf/butler/queries/driver.py

View check run for this annotation

Codecov / codecov/patch

python/lsst/daf/butler/queries/driver.py#L40-L45

Added lines #L40 - L45 were not covered by tests

PageKey: TypeAlias = uuid.UUID

Check warning on line 47 in python/lsst/daf/butler/queries/driver.py

View check run for this annotation

Codecov / codecov/patch

python/lsst/daf/butler/queries/driver.py#L47

Added line #L47 was not covered by tests

Expand Down Expand Up @@ -89,23 +89,25 @@ def universe(self) -> DimensionUniverse:
raise NotImplementedError()

@overload

Check warning on line 91 in python/lsst/daf/butler/queries/driver.py

View check run for this annotation

Codecov / codecov/patch

python/lsst/daf/butler/queries/driver.py#L91

Added line #L91 was not covered by tests
def execute(self, tree: Relation, result_spec: DataCoordinateResultSpec) -> DataCoordinateResultPage:
def execute(self, tree: RootRelation, result_spec: DataCoordinateResultSpec) -> DataCoordinateResultPage:
...

Check warning on line 93 in python/lsst/daf/butler/queries/driver.py

View check run for this annotation

Codecov / codecov/patch

python/lsst/daf/butler/queries/driver.py#L93

Added line #L93 was not covered by tests

@overload

Check warning on line 95 in python/lsst/daf/butler/queries/driver.py

View check run for this annotation

Codecov / codecov/patch

python/lsst/daf/butler/queries/driver.py#L95

Added line #L95 was not covered by tests
def execute(self, tree: Relation, result_spec: DimensionRecordResultSpec) -> DimensionRecordResultPage:
def execute(
self, tree: RootRelation, result_spec: DimensionRecordResultSpec
) -> DimensionRecordResultPage:
...

Check warning on line 99 in python/lsst/daf/butler/queries/driver.py

View check run for this annotation

Codecov / codecov/patch

python/lsst/daf/butler/queries/driver.py#L99

Added line #L99 was not covered by tests

@overload

Check warning on line 101 in python/lsst/daf/butler/queries/driver.py

View check run for this annotation

Codecov / codecov/patch

python/lsst/daf/butler/queries/driver.py#L101

Added line #L101 was not covered by tests
def execute(self, tree: Relation, result_spec: DatasetRefResultSpec) -> DatasetRefResultPage:
def execute(self, tree: RootRelation, result_spec: DatasetRefResultSpec) -> DatasetRefResultPage:
...

Check warning on line 103 in python/lsst/daf/butler/queries/driver.py

View check run for this annotation

Codecov / codecov/patch

python/lsst/daf/butler/queries/driver.py#L103

Added line #L103 was not covered by tests

@overload

Check warning on line 105 in python/lsst/daf/butler/queries/driver.py

View check run for this annotation

Codecov / codecov/patch

python/lsst/daf/butler/queries/driver.py#L105

Added line #L105 was not covered by tests
def execute(self, tree: Relation, result_spec: GeneralResultSpec) -> GeneralResultPage:
def execute(self, tree: RootRelation, result_spec: GeneralResultSpec) -> GeneralResultPage:
...

Check warning on line 107 in python/lsst/daf/butler/queries/driver.py

View check run for this annotation

Codecov / codecov/patch

python/lsst/daf/butler/queries/driver.py#L107

Added line #L107 was not covered by tests

@abstractmethod

Check warning on line 109 in python/lsst/daf/butler/queries/driver.py

View check run for this annotation

Codecov / codecov/patch

python/lsst/daf/butler/queries/driver.py#L109

Added line #L109 was not covered by tests
def execute(self, tree: Relation, result_spec: ResultSpec) -> ResultPage:
def execute(self, tree: RootRelation, result_spec: ResultSpec) -> ResultPage:
"""Execute a query and return the first result page.
Parameters
Expand Down Expand Up @@ -184,7 +186,7 @@ def fetch_next_page(self, result_spec: ResultSpec, key: PageKey) -> ResultPage:
raise NotImplementedError()

@abstractmethod

Check warning on line 188 in python/lsst/daf/butler/queries/driver.py

View check run for this annotation

Codecov / codecov/patch

python/lsst/daf/butler/queries/driver.py#L188

Added line #L188 was not covered by tests
def count(self, tree: Relation, *, exact: bool, discard: bool) -> int:
def count(self, tree: RootRelation, *, exact: bool, discard: bool) -> int:
"""Return the number of rows a query would return.
Parameters
Expand All @@ -206,7 +208,7 @@ def count(self, tree: Relation, *, exact: bool, discard: bool) -> int:
raise NotImplementedError()

@abstractmethod

Check warning on line 210 in python/lsst/daf/butler/queries/driver.py

View check run for this annotation

Codecov / codecov/patch

python/lsst/daf/butler/queries/driver.py#L210

Added line #L210 was not covered by tests
def any(self, tree: Relation, *, execute: bool, exact: bool) -> bool:
def any(self, tree: RootRelation, *, execute: bool, exact: bool) -> bool:
"""Test whether the query would return any rows.
Parameters
Expand All @@ -232,7 +234,7 @@ def any(self, tree: Relation, *, execute: bool, exact: bool) -> bool:
raise NotImplementedError()

@abstractmethod

Check warning on line 236 in python/lsst/daf/butler/queries/driver.py

View check run for this annotation

Codecov / codecov/patch

python/lsst/daf/butler/queries/driver.py#L236

Added line #L236 was not covered by tests
def explain_no_results(self, tree: Relation, execute: bool) -> Iterable[str]:
def explain_no_results(self, tree: RootRelation, execute: bool) -> Iterable[str]:
"""Return human-readable messages that may help explain why the query
yields no results.
Expand Down
4 changes: 2 additions & 2 deletions python/lsst/daf/butler/queries/relation_tree/_find_first.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
from ._base import RelationBase, StringOrWildcard

Check warning on line 38 in python/lsst/daf/butler/queries/relation_tree/_find_first.py

View check run for this annotation

Codecov / codecov/patch

python/lsst/daf/butler/queries/relation_tree/_find_first.py#L37-L38

Added lines #L37 - L38 were not covered by tests

if TYPE_CHECKING:
from ._relation import Relation
from ._select import Select


class FindFirst(RelationBase):

Check warning on line 44 in python/lsst/daf/butler/queries/relation_tree/_find_first.py

View check run for this annotation

Codecov / codecov/patch

python/lsst/daf/butler/queries/relation_tree/_find_first.py#L44

Added line #L44 was not covered by tests
Expand All @@ -51,7 +51,7 @@ class FindFirst(RelationBase):

relation_type: Literal["find_first"] = "find_first"

Check warning on line 52 in python/lsst/daf/butler/queries/relation_tree/_find_first.py

View check run for this annotation

Codecov / codecov/patch

python/lsst/daf/butler/queries/relation_tree/_find_first.py#L52

Added line #L52 was not covered by tests

operand: Relation
operand: Select
"""The upstream relation to operate on.

Check warning on line 55 in python/lsst/daf/butler/queries/relation_tree/_find_first.py

View check run for this annotation

Codecov / codecov/patch

python/lsst/daf/butler/queries/relation_tree/_find_first.py#L54-L55

Added lines #L54 - L55 were not covered by tests
This may have more than one `DatasetSearch` joined into it (at any level),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
from ._base import RelationBase, StringOrWildcard

Check warning on line 37 in python/lsst/daf/butler/queries/relation_tree/_materialization.py

View check run for this annotation

Codecov / codecov/patch

python/lsst/daf/butler/queries/relation_tree/_materialization.py#L36-L37

Added lines #L36 - L37 were not covered by tests

if TYPE_CHECKING:
from ._relation import Relation
from ._relation import RootRelation


class Materialization(RelationBase):

Check warning on line 43 in python/lsst/daf/butler/queries/relation_tree/_materialization.py

View check run for this annotation

Codecov / codecov/patch

python/lsst/daf/butler/queries/relation_tree/_materialization.py#L43

Added line #L43 was not covered by tests
Expand All @@ -47,7 +47,7 @@ class Materialization(RelationBase):

relation_type: Literal["materialization"] = "materialization"

Check warning on line 48 in python/lsst/daf/butler/queries/relation_tree/_materialization.py

View check run for this annotation

Codecov / codecov/patch

python/lsst/daf/butler/queries/relation_tree/_materialization.py#L48

Added line #L48 was not covered by tests

operand: Relation
operand: RootRelation
"""The upstream relation to evaluate."""

Check warning on line 51 in python/lsst/daf/butler/queries/relation_tree/_materialization.py

View check run for this annotation

Codecov / codecov/patch

python/lsst/daf/butler/queries/relation_tree/_materialization.py#L50-L51

Added lines #L50 - L51 were not covered by tests

dataset_types: frozenset[StringOrWildcard]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
)

if TYPE_CHECKING:
from ._relation import Relation
from ._relation import OrderedSliceOperand


class OrderedSlice(RelationBase):

Check warning on line 50 in python/lsst/daf/butler/queries/relation_tree/_ordered_slice.py

View check run for this annotation

Codecov / codecov/patch

python/lsst/daf/butler/queries/relation_tree/_ordered_slice.py#L50

Added line #L50 was not covered by tests
Expand All @@ -54,7 +54,7 @@ class OrderedSlice(RelationBase):

relation_type: Literal["ordered_slice"] = "ordered_slice"

Check warning on line 55 in python/lsst/daf/butler/queries/relation_tree/_ordered_slice.py

View check run for this annotation

Codecov / codecov/patch

python/lsst/daf/butler/queries/relation_tree/_ordered_slice.py#L55

Added line #L55 was not covered by tests

operand: Relation
operand: OrderedSliceOperand
"""The upstream relation to operate on."""

Check warning on line 58 in python/lsst/daf/butler/queries/relation_tree/_ordered_slice.py

View check run for this annotation

Codecov / codecov/patch

python/lsst/daf/butler/queries/relation_tree/_ordered_slice.py#L57-L58

Added lines #L57 - L58 were not covered by tests

order_by: tuple[OrderExpression, ...] = ()
Expand Down
4 changes: 2 additions & 2 deletions python/lsst/daf/butler/queries/relation_tree/_predicate.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
from ._column_expression import ColumnExpression

Check warning on line 40 in python/lsst/daf/butler/queries/relation_tree/_predicate.py

View check run for this annotation

Codecov / codecov/patch

python/lsst/daf/butler/queries/relation_tree/_predicate.py#L38-L40

Added lines #L38 - L40 were not covered by tests

if TYPE_CHECKING:
from ._relation import Relation
from ._relation import RootRelation


class LogicalAnd(PredicateBase):

Check warning on line 46 in python/lsst/daf/butler/queries/relation_tree/_predicate.py

View check run for this annotation

Codecov / codecov/patch

python/lsst/daf/butler/queries/relation_tree/_predicate.py#L46

Added line #L46 was not covered by tests
Expand Down Expand Up @@ -148,7 +148,7 @@ class InRelation(PredicateBase):
predicate_type: Literal["in_relation"] = "in_relation"
member: ColumnExpression
column: ColumnExpression
relation: Relation
relation: RootRelation

Check warning on line 151 in python/lsst/daf/butler/queries/relation_tree/_predicate.py

View check run for this annotation

Codecov / codecov/patch

python/lsst/daf/butler/queries/relation_tree/_predicate.py#L148-L151

Added lines #L148 - L151 were not covered by tests

def gather_required_columns(self) -> set[ColumnReference]:

Check warning on line 153 in python/lsst/daf/butler/queries/relation_tree/_predicate.py

View check run for this annotation

Codecov / codecov/patch

python/lsst/daf/butler/queries/relation_tree/_predicate.py#L153

Added line #L153 was not covered by tests
# We're only gathering columns from the relation this predicate is
Expand Down
27 changes: 16 additions & 11 deletions python/lsst/daf/butler/queries/relation_tree/_relation.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@

from __future__ import annotations

Check warning on line 28 in python/lsst/daf/butler/queries/relation_tree/_relation.py

View check run for this annotation

Codecov / codecov/patch

python/lsst/daf/butler/queries/relation_tree/_relation.py#L28

Added line #L28 was not covered by tests

__all__ = ("Relation", "DeferredValidationRelation")
__all__ = ("RootRelation", "DeferredValidationRootRelation", "JoinOperand", "OrderedSliceOperand")

Check warning on line 30 in python/lsst/daf/butler/queries/relation_tree/_relation.py

View check run for this annotation

Codecov / codecov/patch

python/lsst/daf/butler/queries/relation_tree/_relation.py#L30

Added line #L30 was not covered by tests

from typing import Annotated, TypeAlias, Union

Check warning on line 32 in python/lsst/daf/butler/queries/relation_tree/_relation.py

View check run for this annotation

Codecov / codecov/patch

python/lsst/daf/butler/queries/relation_tree/_relation.py#L32

Added line #L32 was not covered by tests

Expand All @@ -36,32 +36,37 @@
from ...pydantic_utils import DeferredValidation
from ._data_coordinate_upload import DataCoordinateUpload
from ._dataset_search import DatasetSearch
from ._dimension_join import DimensionJoin
from ._find_first import FindFirst
from ._materialization import Materialization
from ._ordered_slice import OrderedSlice
from ._selection import Selection
from ._select import Select

Check warning on line 42 in python/lsst/daf/butler/queries/relation_tree/_relation.py

View check run for this annotation

Codecov / codecov/patch

python/lsst/daf/butler/queries/relation_tree/_relation.py#L36-L42

Added lines #L36 - L42 were not covered by tests

Relation: TypeAlias = Annotated[
JoinOperand: TypeAlias = Annotated[

Check warning on line 44 in python/lsst/daf/butler/queries/relation_tree/_relation.py

View check run for this annotation

Codecov / codecov/patch

python/lsst/daf/butler/queries/relation_tree/_relation.py#L44

Added line #L44 was not covered by tests
Union[
DataCoordinateUpload,
DatasetSearch,
DimensionJoin,
FindFirst,
Materialization,
OrderedSlice,
Selection,
],
pydantic.Field(discriminator="relation_type"),
]

OrderedSliceOperand: TypeAlias = Annotated[

Check warning on line 53 in python/lsst/daf/butler/queries/relation_tree/_relation.py

View check run for this annotation

Codecov / codecov/patch

python/lsst/daf/butler/queries/relation_tree/_relation.py#L53

Added line #L53 was not covered by tests
Union[Select, FindFirst],
pydantic.Field(discriminator="relation_type"),
]


RootRelation: TypeAlias = Annotated[

Check warning on line 59 in python/lsst/daf/butler/queries/relation_tree/_relation.py

View check run for this annotation

Codecov / codecov/patch

python/lsst/daf/butler/queries/relation_tree/_relation.py#L59

Added line #L59 was not covered by tests
Union[Select, FindFirst, OrderedSlice],
pydantic.Field(discriminator="relation_type"),
]


DimensionJoin.model_rebuild()
Select.model_rebuild()
FindFirst.model_rebuild()
Materialization.model_rebuild()
OrderedSlice.model_rebuild()

Check warning on line 68 in python/lsst/daf/butler/queries/relation_tree/_relation.py

View check run for this annotation

Codecov / codecov/patch

python/lsst/daf/butler/queries/relation_tree/_relation.py#L65-L68

Added lines #L65 - L68 were not covered by tests
Selection.model_rebuild()


class DeferredValidationRelation(DeferredValidation[Relation]):
class DeferredValidationRootRelation(DeferredValidation[RootRelation]):
pass

Check warning on line 72 in python/lsst/daf/butler/queries/relation_tree/_relation.py

View check run for this annotation

Codecov / codecov/patch

python/lsst/daf/butler/queries/relation_tree/_relation.py#L71-L72

Added lines #L71 - L72 were not covered by tests
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@

from __future__ import annotations

Check warning on line 28 in python/lsst/daf/butler/queries/relation_tree/_select.py

View check run for this annotation

Codecov / codecov/patch

python/lsst/daf/butler/queries/relation_tree/_select.py#L28

Added line #L28 was not covered by tests

__all__ = ("DimensionJoin", "make_unit_relation")
__all__ = ("Select", "make_unit_relation")

Check warning on line 30 in python/lsst/daf/butler/queries/relation_tree/_select.py

View check run for this annotation

Codecov / codecov/patch

python/lsst/daf/butler/queries/relation_tree/_select.py#L30

Added line #L30 was not covered by tests

import itertools
from functools import cached_property
Expand All @@ -37,42 +37,44 @@

from ...dimensions import DimensionGroup, DimensionUniverse
from ._base import RelationBase, StringOrWildcard
from ._column_reference import DatasetFieldReference, DimensionFieldReference, DimensionKeyReference
from ._predicate import Predicate
from .joins import JoinTuple

Check warning on line 42 in python/lsst/daf/butler/queries/relation_tree/_select.py

View check run for this annotation

Codecov / codecov/patch

python/lsst/daf/butler/queries/relation_tree/_select.py#L38-L42

Added lines #L38 - L42 were not covered by tests

if TYPE_CHECKING:
from ._relation import Relation
from ._relation import JoinOperand


def make_unit_relation(universe: DimensionUniverse) -> DimensionJoin:
def make_unit_relation(universe: DimensionUniverse) -> Select:

Check warning on line 48 in python/lsst/daf/butler/queries/relation_tree/_select.py

View check run for this annotation

Codecov / codecov/patch

python/lsst/daf/butler/queries/relation_tree/_select.py#L48

Added line #L48 was not covered by tests
"""Make an initial relation with empty dimensions and a single logical row.
This method should be used by `Butler._query` to construct the initial
relation tree. This relation is a useful initial state because it is the
identity relation for joins, in that joining any other relation to this
relation yields that relation.
"""
return DimensionJoin.model_construct(dimensions=universe.empty.as_group())
return Select.model_construct(dimensions=universe.empty.as_group())

Check warning on line 56 in python/lsst/daf/butler/queries/relation_tree/_select.py

View check run for this annotation

Codecov / codecov/patch

python/lsst/daf/butler/queries/relation_tree/_select.py#L56

Added line #L56 was not covered by tests


class DimensionJoin(RelationBase):
"""An abstract relation that represents a join between dimension-element
tables and (optionally) other relations.
class Select(RelationBase):

Check warning on line 59 in python/lsst/daf/butler/queries/relation_tree/_select.py

View check run for this annotation

Codecov / codecov/patch

python/lsst/daf/butler/queries/relation_tree/_select.py#L59

Added line #L59 was not covered by tests
"""An abstract relation that combines joins and row-selection within a
fixed set of dimensions.
Notes
-----
Joins on dataset IDs are expected to be expressed as
`abstract_expressions.InRelation` predicates in `Selection` operations.
`abstract_expressions.InRelation` predicates used in the `where` attribute.
That is slightly more powerful (since it can do set differences via
`abstract_expressions.LogicalNot`) and it keeps the abstract relation tree
simpler if the only join constraints in play are on dimension columns.
simpler if the only join constraints in play here are on dimension columns.
"""

relation_type: Literal["dimension_join"] = "dimension_join"
relation_type: Literal["select"] = "select"

Check warning on line 72 in python/lsst/daf/butler/queries/relation_tree/_select.py

View check run for this annotation

Codecov / codecov/patch

python/lsst/daf/butler/queries/relation_tree/_select.py#L72

Added line #L72 was not covered by tests

dimensions: DimensionGroup
"""The dimensions of the relation."""

Check warning on line 75 in python/lsst/daf/butler/queries/relation_tree/_select.py

View check run for this annotation

Codecov / codecov/patch

python/lsst/daf/butler/queries/relation_tree/_select.py#L74-L75

Added lines #L74 - L75 were not covered by tests

operands: tuple[Relation, ...] = ()
join_operands: tuple[JoinOperand, ...] = ()
"""Relations to include in the join other than dimension-element tables.

Check warning on line 78 in python/lsst/daf/butler/queries/relation_tree/_select.py

View check run for this annotation

Codecov / codecov/patch

python/lsst/daf/butler/queries/relation_tree/_select.py#L77-L78

Added lines #L77 - L78 were not covered by tests
Because dimension-element tables are expected to contain the full set of
Expand All @@ -85,29 +87,32 @@ class DimensionJoin(RelationBase):
``detector`` table, but the ``physical_filter`` table will be joined in.
"""

spatial: frozenset[JoinTuple] = frozenset()
spatial_joins: frozenset[JoinTuple] = frozenset()
"""Pairs of dimension element names that should whose regions on the sky

Check warning on line 91 in python/lsst/daf/butler/queries/relation_tree/_select.py

View check run for this annotation

Codecov / codecov/patch

python/lsst/daf/butler/queries/relation_tree/_select.py#L90-L91

Added lines #L90 - L91 were not covered by tests
must overlap.
"""

temporal: frozenset[JoinTuple] = frozenset()
temporal_joins: frozenset[JoinTuple] = frozenset()
"""Pairs of dimension element names and calibration dataset type names

Check warning on line 96 in python/lsst/daf/butler/queries/relation_tree/_select.py

View check run for this annotation

Codecov / codecov/patch

python/lsst/daf/butler/queries/relation_tree/_select.py#L95-L96

Added lines #L95 - L96 were not covered by tests
whose timespans must overlap.
"""

where: tuple[Predicate, ...] = ()
"""Boolean expression trees whose logical AND defines a row filter."""

Check warning on line 101 in python/lsst/daf/butler/queries/relation_tree/_select.py

View check run for this annotation

Codecov / codecov/patch

python/lsst/daf/butler/queries/relation_tree/_select.py#L100-L101

Added lines #L100 - L101 were not covered by tests

@cached_property

Check warning on line 103 in python/lsst/daf/butler/queries/relation_tree/_select.py

View check run for this annotation

Codecov / codecov/patch

python/lsst/daf/butler/queries/relation_tree/_select.py#L103

Added line #L103 was not covered by tests
def available_dataset_types(self) -> frozenset[StringOrWildcard]:
"""The dataset types whose ID columns (at least) are available from
this relation.
"""
result: set[StringOrWildcard] = set()

Check warning on line 108 in python/lsst/daf/butler/queries/relation_tree/_select.py

View check run for this annotation

Codecov / codecov/patch

python/lsst/daf/butler/queries/relation_tree/_select.py#L108

Added line #L108 was not covered by tests
for operand in self.operands:
for operand in self.join_operands:
result.update(operand.available_dataset_types)
return frozenset(result)

Check warning on line 111 in python/lsst/daf/butler/queries/relation_tree/_select.py

View check run for this annotation

Codecov / codecov/patch

python/lsst/daf/butler/queries/relation_tree/_select.py#L110-L111

Added lines #L110 - L111 were not covered by tests

@pydantic.model_validator(mode="after")

Check warning on line 113 in python/lsst/daf/butler/queries/relation_tree/_select.py

View check run for this annotation

Codecov / codecov/patch

python/lsst/daf/butler/queries/relation_tree/_select.py#L113

Added line #L113 was not covered by tests
def _validate_operands(self) -> DimensionJoin:
for operand in self.operands:
def _validate_join_operands(self) -> Select:
for operand in self.join_operands:
if not operand.dimensions.issubset(self.dimensions):
raise ValueError(

Check warning on line 117 in python/lsst/daf/butler/queries/relation_tree/_select.py

View check run for this annotation

Codecov / codecov/patch

python/lsst/daf/butler/queries/relation_tree/_select.py#L117

Added line #L117 was not covered by tests
f"Dimensions {operand.dimensions} of join operand {operand} are not a "
Expand All @@ -116,7 +121,7 @@ def _validate_operands(self) -> DimensionJoin:
return self

Check warning on line 121 in python/lsst/daf/butler/queries/relation_tree/_select.py

View check run for this annotation

Codecov / codecov/patch

python/lsst/daf/butler/queries/relation_tree/_select.py#L121

Added line #L121 was not covered by tests

@pydantic.model_validator(mode="after")

Check warning on line 123 in python/lsst/daf/butler/queries/relation_tree/_select.py

View check run for this annotation

Codecov / codecov/patch

python/lsst/daf/butler/queries/relation_tree/_select.py#L123

Added line #L123 was not covered by tests
def _validate_spatial(self) -> DimensionJoin:
def _validate_spatial_joins(self) -> Select:
def check_operand(operand: str) -> str:

Check warning on line 125 in python/lsst/daf/butler/queries/relation_tree/_select.py

View check run for this annotation

Codecov / codecov/patch

python/lsst/daf/butler/queries/relation_tree/_select.py#L125

Added line #L125 was not covered by tests
if operand not in self.dimensions.elements:
raise ValueError(f"Spatial join operand {operand!r} is not in this join's dimensions.")
Expand All @@ -125,13 +130,13 @@ def check_operand(operand: str) -> str:
raise ValueError(f"Spatial join operand {operand!r} is not associated with a region.")
return family.name

Check warning on line 131 in python/lsst/daf/butler/queries/relation_tree/_select.py

View check run for this annotation

Codecov / codecov/patch

python/lsst/daf/butler/queries/relation_tree/_select.py#L130-L131

Added lines #L130 - L131 were not covered by tests

for a, b in self.spatial:
for a, b in self.spatial_joins:
if check_operand(a) == check_operand(b):
raise ValueError(f"Spatial join between {a!r} and {b!r} is unnecessary.")
return self

Check warning on line 136 in python/lsst/daf/butler/queries/relation_tree/_select.py

View check run for this annotation

Codecov / codecov/patch

python/lsst/daf/butler/queries/relation_tree/_select.py#L135-L136

Added lines #L135 - L136 were not covered by tests

@pydantic.model_validator(mode="after")

Check warning on line 138 in python/lsst/daf/butler/queries/relation_tree/_select.py

View check run for this annotation

Codecov / codecov/patch

python/lsst/daf/butler/queries/relation_tree/_select.py#L138

Added line #L138 was not covered by tests
def _validate_temporal(self) -> DimensionJoin:
def _validate_temporal_joins(self) -> Select:
def check_operand(operand: str) -> str:

Check warning on line 140 in python/lsst/daf/butler/queries/relation_tree/_select.py

View check run for this annotation

Codecov / codecov/patch

python/lsst/daf/butler/queries/relation_tree/_select.py#L140

Added line #L140 was not covered by tests
if operand in self.dimensions.elements:
family = self.dimensions.universe[operand].temporal

Check warning on line 142 in python/lsst/daf/butler/queries/relation_tree/_select.py

View check run for this annotation

Codecov / codecov/patch

python/lsst/daf/butler/queries/relation_tree/_select.py#L142

Added line #L142 was not covered by tests
Expand All @@ -145,18 +150,36 @@ def check_operand(operand: str) -> str:
f"Temporal join operand {operand!r} is not in this join's dimensions or dataset types."
)

for a, b in self.spatial:
for a, b in self.spatial_joins:
if check_operand(a) == check_operand(b):
raise ValueError(f"Temporal join between {a!r} and {b!r} is unnecessary or impossible.")
return self

Check warning on line 156 in python/lsst/daf/butler/queries/relation_tree/_select.py

View check run for this annotation

Codecov / codecov/patch

python/lsst/daf/butler/queries/relation_tree/_select.py#L155-L156

Added lines #L155 - L156 were not covered by tests

@pydantic.model_validator(mode="after")

Check warning on line 158 in python/lsst/daf/butler/queries/relation_tree/_select.py

View check run for this annotation

Codecov / codecov/patch

python/lsst/daf/butler/queries/relation_tree/_select.py#L158

Added line #L158 was not covered by tests
def _validate_upstream_datasets(self) -> DimensionJoin:
for a, b in itertools.combinations(self.operands, 2):
def _validate_upstream_datasets(self) -> Select:
for a, b in itertools.combinations(self.join_operands, 2):
if not a.available_dataset_types.isdisjoint(b.available_dataset_types):
common = a.available_dataset_types & b.available_dataset_types

Check warning on line 162 in python/lsst/daf/butler/queries/relation_tree/_select.py

View check run for this annotation

Codecov / codecov/patch

python/lsst/daf/butler/queries/relation_tree/_select.py#L162

Added line #L162 was not covered by tests
if None in common:
raise ValueError(f"Upstream relations {a} and {b} both have a dataset wildcard.")

Check warning on line 164 in python/lsst/daf/butler/queries/relation_tree/_select.py

View check run for this annotation

Codecov / codecov/patch

python/lsst/daf/butler/queries/relation_tree/_select.py#L164

Added line #L164 was not covered by tests
else:
raise ValueError(f"Upstream relations {a} and {b} both have dataset types {common}.")
return self

Check warning on line 167 in python/lsst/daf/butler/queries/relation_tree/_select.py

View check run for this annotation

Codecov / codecov/patch

python/lsst/daf/butler/queries/relation_tree/_select.py#L166-L167

Added lines #L166 - L167 were not covered by tests

@pydantic.model_validator(mode="after")

Check warning on line 169 in python/lsst/daf/butler/queries/relation_tree/_select.py

View check run for this annotation

Codecov / codecov/patch

python/lsst/daf/butler/queries/relation_tree/_select.py#L169

Added line #L169 was not covered by tests
def _validate_required_columns(self) -> Select:
required_columns = set()

Check warning on line 171 in python/lsst/daf/butler/queries/relation_tree/_select.py

View check run for this annotation

Codecov / codecov/patch

python/lsst/daf/butler/queries/relation_tree/_select.py#L171

Added line #L171 was not covered by tests
for where_term in self.where:
required_columns.update(where_term.gather_required_columns())

Check warning on line 173 in python/lsst/daf/butler/queries/relation_tree/_select.py

View check run for this annotation

Codecov / codecov/patch

python/lsst/daf/butler/queries/relation_tree/_select.py#L173

Added line #L173 was not covered by tests
for column in required_columns:
match column:

Check warning on line 175 in python/lsst/daf/butler/queries/relation_tree/_select.py

View check run for this annotation

Codecov / codecov/patch

python/lsst/daf/butler/queries/relation_tree/_select.py#L175

Added line #L175 was not covered by tests
case DimensionKeyReference(dimension=dimension):
if dimension not in self.dimensions:
raise ValueError(f"Column {column} is not in dimensions {self.dimensions}.")

Check warning on line 178 in python/lsst/daf/butler/queries/relation_tree/_select.py

View check run for this annotation

Codecov / codecov/patch

python/lsst/daf/butler/queries/relation_tree/_select.py#L178

Added line #L178 was not covered by tests
case DimensionFieldReference(element=element):
if element not in self.dimensions.elements:
raise ValueError(f"Column {column} is not in dimensions {self.dimensions}.")

Check warning on line 181 in python/lsst/daf/butler/queries/relation_tree/_select.py

View check run for this annotation

Codecov / codecov/patch

python/lsst/daf/butler/queries/relation_tree/_select.py#L181

Added line #L181 was not covered by tests
case DatasetFieldReference(dataset_type=dataset_type):
if dataset_type not in self.available_dataset_types:
raise ValueError(f"Dataset search for column {column} is not present.")
return self

Check warning on line 185 in python/lsst/daf/butler/queries/relation_tree/_select.py

View check run for this annotation

Codecov / codecov/patch

python/lsst/daf/butler/queries/relation_tree/_select.py#L184-L185

Added lines #L184 - L185 were not covered by tests
Loading

0 comments on commit 5fe882d

Please sign in to comment.