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