Coverage for python / lsst / daf / butler / queries / tree / _column_reference.py: 51%
85 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-14 23:37 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-14 23:37 +0000
1# This file is part of daf_butler.
2#
3# Developed for the LSST Data Management System.
4# This product includes software developed by the LSST Project
5# (http://www.lsst.org).
6# See the COPYRIGHT file at the top-level directory of this distribution
7# for details of code ownership.
8#
9# This software is dual licensed under the GNU General Public License and also
10# under a 3-clause BSD license. Recipients may choose which of these licenses
11# to use; please see the files gpl-3.0.txt and/or bsd_license.txt,
12# respectively. If you choose the GPL option then the following text applies
13# (but note that there is still no warranty even if you opt for BSD instead):
14#
15# This program is free software: you can redistribute it and/or modify
16# it under the terms of the GNU General Public License as published by
17# the Free Software Foundation, either version 3 of the License, or
18# (at your option) any later version.
19#
20# This program is distributed in the hope that it will be useful,
21# but WITHOUT ANY WARRANTY; without even the implied warranty of
22# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23# GNU General Public License for more details.
24#
25# You should have received a copy of the GNU General Public License
26# along with this program. If not, see <http://www.gnu.org/licenses/>.
28from __future__ import annotations
30__all__ = ("ColumnReference", "DatasetFieldReference", "DimensionFieldReference", "DimensionKeyReference")
32from typing import TYPE_CHECKING, ClassVar, Literal, TypeAlias, TypeVar, Union, final
34import pydantic
36from ..._exceptions import InvalidQueryError
37from ...column_spec import ColumnType
38from ...dimensions import Dimension, DimensionElement
39from ._base import ANY_DATASET, AnyDatasetType, ColumnExpressionBase, DatasetFieldName
41if TYPE_CHECKING:
42 from ..visitors import ColumnExpressionVisitor
43 from ._column_set import ColumnSet
46_T = TypeVar("_T")
49@final
50class DimensionKeyReference(ColumnExpressionBase):
51 """A column expression that references a dimension primary key column."""
53 expression_type: Literal["dimension_key"] = "dimension_key"
55 is_column_reference: ClassVar[bool] = True
57 dimension: Dimension
58 """Definition and name of this dimension."""
60 def gather_required_columns(self, columns: ColumnSet) -> None:
61 # Docstring inherited.
62 columns.update_dimensions(self.dimension.minimal_group)
64 def gather_governors(self, governors: set[str]) -> None:
65 if self.dimension.governor is not None and self.dimension.governor is not self.dimension:
66 governors.add(self.dimension.governor.name)
68 @property
69 def column_type(self) -> ColumnType:
70 # Docstring inherited.
71 return self.dimension.primary_key.type
73 def __str__(self) -> str:
74 return self.dimension.name
76 def visit(self, visitor: ColumnExpressionVisitor[_T]) -> _T:
77 # Docstring inherited.
78 return visitor.visit_dimension_key_reference(self)
81@final
82class DimensionFieldReference(ColumnExpressionBase):
83 """A column expression that references a dimension record column that is
84 not a primary key.
85 """
87 expression_type: Literal["dimension_field"] = "dimension_field"
89 is_column_reference: ClassVar[bool] = True
91 element: DimensionElement
92 """Definition and name of the dimension element."""
94 field: str
95 """Name of the field (i.e. column) in the element's logical table."""
97 def gather_required_columns(self, columns: ColumnSet) -> None:
98 # Docstring inherited.
99 columns.update_dimensions(self.element.minimal_group)
100 columns.dimension_fields[self.element.name].add(self.field)
102 def gather_governors(self, governors: set[str]) -> None:
103 # Docstring inherited.
104 # We assume metadata fields (and certainly region and timespan fields)
105 # don't need to be qualified with (e.g.) an instrument or skymap name
106 # to make sense, but keys like visit IDs or detector names do need to
107 # e qualified.
108 if (
109 isinstance(self.element, Dimension)
110 and self.field in self.element.alternate_keys.names
111 and self.element.governor is not None
112 ):
113 governors.add(self.element.governor.name)
115 @property
116 def column_type(self) -> ColumnType:
117 # Docstring inherited.
118 return self.element.schema.remainder[self.field].type
120 def __str__(self) -> str:
121 return f"{self.element}.{self.field}"
123 def visit(self, visitor: ColumnExpressionVisitor[_T]) -> _T:
124 # Docstring inherited.
125 return visitor.visit_dimension_field_reference(self)
127 @pydantic.model_validator(mode="after")
128 def _validate_field(self) -> DimensionFieldReference:
129 if self.field not in self.element.schema.remainder.names:
130 raise InvalidQueryError(f"Dimension field {self.element.name}.{self.field} does not exist.")
131 return self
134@final
135class DatasetFieldReference(ColumnExpressionBase):
136 """A column expression that references a column associated with a dataset
137 type.
138 """
140 expression_type: Literal["dataset_field"] = "dataset_field"
142 is_column_reference: ClassVar[bool] = True
144 dataset_type: AnyDatasetType | str
145 """Name of the dataset type, or ``ANY_DATSET`` to match any dataset type.
146 """
148 field: DatasetFieldName
149 """Name of the field (i.e. column) in the dataset's logical table."""
151 def gather_required_columns(self, columns: ColumnSet) -> None:
152 # Docstring inherited.
153 columns.dataset_fields[self.dataset_type].add(self.field)
155 def gather_governors(self, governors: set[str]) -> None:
156 # Docstring inherited.
157 pass
159 @property
160 def column_type(self) -> ColumnType:
161 # Docstring inherited.
162 match self.field:
163 case "dataset_id":
164 return "uuid"
165 case "ingest_date":
166 # See comment at definition of `ColumnType` type alias.
167 return "ingest_date"
168 case "run":
169 return "string"
170 case "collection":
171 return "string"
172 case "timespan":
173 return "timespan"
174 raise AssertionError(f"Invalid field {self.field!r} for dataset.")
176 def __str__(self) -> str:
177 if self.dataset_type is ANY_DATASET:
178 return self.field
179 else:
180 return f"{self.dataset_type}.{self.field}"
182 def visit(self, visitor: ColumnExpressionVisitor[_T]) -> _T:
183 # Docstring inherited.
184 return visitor.visit_dataset_field_reference(self)
187ColumnReference: TypeAlias = Union[
188 DimensionKeyReference,
189 DimensionFieldReference,
190 DatasetFieldReference,
191]