Coverage for python/lsst/daf/butler/registry/dimensions/overlaps.py: 95%
58 statements
« prev ^ index » next coverage.py v7.3.1, created at 2023-10-02 07:59 +0000
« prev ^ index » next coverage.py v7.3.1, created at 2023-10-02 07:59 +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__ = ("CrossFamilyDimensionOverlapStorage",)
32import logging
33from collections.abc import Iterable, Mapping, Set
34from typing import TYPE_CHECKING
36import sqlalchemy
37from lsst.daf.relation import Relation
39from ...core import DatabaseDimensionElement, addDimensionForeignKey, ddl
40from ..interfaces import (
41 Database,
42 DatabaseDimensionOverlapStorage,
43 DatabaseDimensionRecordStorage,
44 GovernorDimensionRecordStorage,
45 StaticTablesContext,
46)
48if TYPE_CHECKING:
49 from .. import queries
52_LOG = logging.getLogger(__name__)
55class CrossFamilyDimensionOverlapStorage(DatabaseDimensionOverlapStorage):
56 """Basic implementation of materialized overlaps between
57 otherwise-unrelated dimension elements.
59 New instances should be constructed by calling `initialize`, not by calling
60 the constructor directly.
62 Parameters
63 ----------
64 db : `Database`
65 Interface to the underlying database engine and namespace.
66 elementStorage : `tuple` [ `DatabaseDimensionRecordStorage` ]
67 Storage objects for the elements this object will relate.
68 governorStorage : `tuple` [ `GovernorDimensionRecordStorage` ]
69 Storage objects for the governor dimensions of the elements this
70 object will relate.
71 summaryTable : `sqlalchemy.schema.Table`
72 Table that records which combinations of governor dimension values
73 have materialized overlap rows.
74 overlapTable : `sqlalchemy.schema.Table`
75 Table containing the actual materialized overlap rows.
77 Notes
78 -----
79 At present, this class (like its ABC) is just a stub that creates the
80 tables it will use, but does nothing else.
81 """
83 def __init__(
84 self,
85 db: Database,
86 elementStorage: tuple[DatabaseDimensionRecordStorage, DatabaseDimensionRecordStorage],
87 governorStorage: tuple[GovernorDimensionRecordStorage, GovernorDimensionRecordStorage],
88 summaryTable: sqlalchemy.schema.Table,
89 overlapTable: sqlalchemy.schema.Table,
90 ):
91 self._db = db
92 self._elementStorage = elementStorage
93 self._governorStorage = governorStorage
94 self._summaryTable = summaryTable
95 self._overlapTable = overlapTable
97 @classmethod
98 def initialize(
99 cls,
100 db: Database,
101 elementStorage: tuple[DatabaseDimensionRecordStorage, DatabaseDimensionRecordStorage],
102 governorStorage: tuple[GovernorDimensionRecordStorage, GovernorDimensionRecordStorage],
103 context: StaticTablesContext | None = None,
104 ) -> DatabaseDimensionOverlapStorage:
105 # Docstring inherited from DatabaseDimensionOverlapStorage.
106 if context is not None: 106 ↛ 109line 106 didn't jump to line 109, because the condition on line 106 was never false
107 op = context.addTable
108 else:
109 op = db.ensureTableExists
110 elements = (elementStorage[0].element, elementStorage[1].element)
111 summaryTable = op(
112 cls._SUMMARY_TABLE_NAME_SPEC.format(*elements),
113 cls._makeSummaryTableSpec(elements),
114 )
115 overlapTable = op(
116 cls._OVERLAP_TABLE_NAME_SPEC.format(*elements),
117 cls._makeOverlapTableSpec(elements),
118 )
119 return cls(
120 db,
121 elementStorage,
122 governorStorage,
123 summaryTable=summaryTable,
124 overlapTable=overlapTable,
125 )
127 @property
128 def elements(self) -> tuple[DatabaseDimensionElement, DatabaseDimensionElement]:
129 # Docstring inherited from DatabaseDimensionOverlapStorage.
130 return (self._elementStorage[0].element, self._elementStorage[1].element)
132 def digestTables(self) -> Iterable[sqlalchemy.schema.Table]:
133 # Docstring inherited from DatabaseDimensionOverlapStorage.
134 return [self._summaryTable, self._overlapTable]
136 _SUMMARY_TABLE_NAME_SPEC = "{0.name}_{1.name}_overlap_summary"
138 @classmethod
139 def _makeSummaryTableSpec(
140 cls, elements: tuple[DatabaseDimensionElement, DatabaseDimensionElement]
141 ) -> ddl.TableSpec:
142 """Create a specification for the table that records which combinations
143 of skypix dimension and governor value have materialized overlaps.
145 Parameters
146 ----------
147 elements : `tuple` [ `DatabaseDimensionElement` ]
148 Dimension elements whose overlaps are to be managed.
150 Returns
151 -------
152 tableSpec : `ddl.TableSpec`
153 Table specification.
154 """
155 assert elements[0].spatial is not None and elements[1].spatial is not None
156 assert elements[0].spatial.governor != elements[1].spatial.governor
157 tableSpec = ddl.TableSpec(fields=[])
158 addDimensionForeignKey(tableSpec, elements[0].spatial.governor, primaryKey=True)
159 addDimensionForeignKey(tableSpec, elements[1].spatial.governor, primaryKey=True)
160 return tableSpec
162 _OVERLAP_TABLE_NAME_SPEC = "{0.name}_{1.name}_overlap"
164 @classmethod
165 def _makeOverlapTableSpec(
166 cls, elements: tuple[DatabaseDimensionElement, DatabaseDimensionElement]
167 ) -> ddl.TableSpec:
168 """Create a specification for the table that holds materialized
169 overlap rows.
171 Parameters
172 ----------
173 elements : `tuple` [ `DatabaseDimensionElement` ]
174 Dimension elements whose overlaps are to be managed.
176 Returns
177 -------
178 tableSpec : `ddl.TableSpec`
179 Table specification.
180 """
181 assert elements[0].graph.required.isdisjoint(elements[1].graph.required)
182 tableSpec = ddl.TableSpec(fields=[])
183 # Add governor dimensions first, so they appear first in the primary
184 # key; we may often (in the future, perhaps always) know these at
185 # query-generation time.
186 for element in elements:
187 assert element.spatial is not None
188 addDimensionForeignKey(tableSpec, element.spatial.governor, primaryKey=True)
189 # Add remaining dimension keys.
190 for element in elements:
191 assert element.spatial is not None
192 for dimension in element.required:
193 if dimension != element.spatial.governor:
194 addDimensionForeignKey(tableSpec, dimension, primaryKey=True)
195 return tableSpec
197 def clearCaches(self) -> None:
198 # Docstring inherited from DatabaseDimensionOverlapStorage.
199 pass
201 def make_relation(
202 self,
203 context: queries.SqlQueryContext,
204 governor_constraints: Mapping[str, Set[str]],
205 ) -> Relation | None:
206 # Docstring inherited.
207 return None