Coverage for python/lsst/daf/butler/registry/dimensions/query.py: 96%
42 statements
« prev ^ index » next coverage.py v7.3.2, created at 2023-12-05 11:05 +0000
« prev ^ index » next coverage.py v7.3.2, created at 2023-12-05 11:05 +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/>.
27from __future__ import annotations
29__all__ = ["QueryDimensionRecordStorage"]
31from collections.abc import Mapping
32from typing import TYPE_CHECKING, Any
34import sqlalchemy
35from lsst.daf.relation import Relation
37from ..._column_tags import DimensionKeyColumnTag
38from ..._named import NamedKeyMapping
39from ...dimensions import (
40 DatabaseDimension,
41 DatabaseDimensionElement,
42 DataCoordinate,
43 DimensionRecord,
44 GovernorDimension,
45)
46from ..interfaces import (
47 Database,
48 DatabaseDimensionRecordStorage,
49 GovernorDimensionRecordStorage,
50 StaticTablesContext,
51)
53if TYPE_CHECKING:
54 from .. import queries
57class QueryDimensionRecordStorage(DatabaseDimensionRecordStorage):
58 """A read-only record storage implementation backed by SELECT query.
60 At present, the only query this class supports is a SELECT DISTINCT over
61 the table for some other dimension that has this dimension as an implied
62 dependency. For example, we can use this class to provide access to the
63 set of ``band`` names referenced by any ``physical_filter``.
65 Parameters
66 ----------
67 db : `Database`
68 Interface to the database engine and namespace that will hold these
69 dimension records.
70 element : `DatabaseDimensionElement`
71 The element whose records this storage will manage.
72 view_target : `DatabaseDimensionRecordStorage`
73 Storage object for the element this target's storage is a view of.
74 """
76 def __init__(
77 self, db: Database, element: DatabaseDimensionElement, view_target: DatabaseDimensionRecordStorage
78 ):
79 assert isinstance(
80 element, DatabaseDimension
81 ), "An element cannot be a dependency unless it is a dimension."
82 self._db = db
83 self._element = element
84 self._target = view_target
85 if element.name not in self._target.element.minimal_group:
86 raise NotImplementedError("Query-backed dimension must be a dependency of its target.")
87 if element.metadata:
88 raise NotImplementedError("Cannot use query to back dimension with metadata.")
89 if element.implied:
90 raise NotImplementedError("Cannot use query to back dimension with implied dependencies.")
91 if element.alternateKeys:
92 raise NotImplementedError("Cannot use query to back dimension with alternate unique keys.")
93 if element.spatial is not None:
94 raise NotImplementedError("Cannot use query to back spatial dimension.")
95 if element.temporal is not None:
96 raise NotImplementedError("Cannot use query to back temporal dimension.")
98 @classmethod
99 def initialize(
100 cls,
101 db: Database,
102 element: DatabaseDimensionElement,
103 *,
104 context: StaticTablesContext | None = None,
105 config: Mapping[str, Any],
106 governors: NamedKeyMapping[GovernorDimension, GovernorDimensionRecordStorage],
107 view_target: DatabaseDimensionRecordStorage | None = None,
108 ) -> DatabaseDimensionRecordStorage:
109 # Docstring inherited from DatabaseDimensionRecordStorage.
110 assert view_target is not None, f"Storage for '{element}' is a view."
111 return cls(db, element, view_target)
113 @property
114 def element(self) -> DatabaseDimension:
115 # Docstring inherited from DimensionRecordStorage.element.
116 return self._element
118 def clearCaches(self) -> None:
119 # Docstring inherited from DimensionRecordStorage.clearCaches.
120 pass
122 def make_relation(self, context: queries.SqlQueryContext) -> Relation:
123 # Docstring inherited from DimensionRecordStorage.
124 columns = DimensionKeyColumnTag.generate([self.element.name])
125 return (
126 self._target.make_relation(context)
127 .with_only_columns(
128 frozenset(columns),
129 preferred_engine=context.preferred_engine,
130 require_preferred_engine=True,
131 )
132 .without_duplicates()
133 )
135 def insert(self, *records: DimensionRecord, replace: bool = False, skip_existing: bool = False) -> None:
136 # Docstring inherited from DimensionRecordStorage.insert.
137 raise TypeError(
138 f"Cannot insert {self.element.name} records, define as part of {self._target.element} instead."
139 )
141 def sync(self, record: DimensionRecord, update: bool = False) -> bool:
142 # Docstring inherited from DimensionRecordStorage.sync.
143 raise TypeError(
144 f"Cannot sync {self.element.name} records, define as part of {self._target.element} instead."
145 )
147 def fetch_one(self, data_id: DataCoordinate, context: queries.SqlQueryContext) -> DimensionRecord | None:
148 # Docstring inherited from DimensionRecordStorage.
149 # Given the restrictions imposed at construction, we know there's
150 # nothing to actually fetch: everything we need is in the data ID.
151 return self.element.RecordClass(**data_id.required)
153 def digestTables(self) -> list[sqlalchemy.schema.Table]:
154 # Docstring inherited from DimensionRecordStorage.digestTables.
155 return []