Coverage for python/lsst/daf/butler/registry/dimensions/overlaps.py: 94%
58 statements
« prev ^ index » next coverage.py v6.5.0, created at 2023-04-13 02:34 -0700
« prev ^ index » next coverage.py v6.5.0, created at 2023-04-13 02:34 -0700
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 program is free software: you can redistribute it and/or modify
10# it under the terms of the GNU General Public License as published by
11# the Free Software Foundation, either version 3 of the License, or
12# (at your option) any later version.
13#
14# This program is distributed in the hope that it will be useful,
15# but WITHOUT ANY WARRANTY; without even the implied warranty of
16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17# GNU General Public License for more details.
18#
19# You should have received a copy of the GNU General Public License
20# along with this program. If not, see <http://www.gnu.org/licenses/>.
22from __future__ import annotations
24__all__ = ("CrossFamilyDimensionOverlapStorage",)
26import logging
27from collections.abc import Iterable, Mapping, Set
28from typing import TYPE_CHECKING
30import sqlalchemy
31from lsst.daf.relation import Relation
33from ...core import DatabaseDimensionElement, addDimensionForeignKey, ddl
34from ..interfaces import (
35 Database,
36 DatabaseDimensionOverlapStorage,
37 DatabaseDimensionRecordStorage,
38 GovernorDimensionRecordStorage,
39 StaticTablesContext,
40)
42if TYPE_CHECKING:
43 from .. import queries
46_LOG = logging.getLogger(__name__)
49class CrossFamilyDimensionOverlapStorage(DatabaseDimensionOverlapStorage):
50 """Basic implementation of materialized overlaps between
51 otherwise-unrelated dimension elements.
53 New instances should be constructed by calling `initialize`, not by calling
54 the constructor directly.
56 Parameters
57 ----------
58 db : `Database`
59 Interface to the underlying database engine and namespace.
60 elementStorage : `tuple` [ `DatabaseDimensionRecordStorage` ]
61 Storage objects for the elements this object will relate.
62 governorStorage : `tuple` [ `GovernorDimensionRecordStorage` ]
63 Storage objects for the governor dimensions of the elements this
64 object will relate.
65 summaryTable : `sqlalchemy.schema.Table`
66 Table that records which combinations of governor dimension values
67 have materialized overlap rows.
68 overlapTable : `sqlalchemy.schema.Table`
69 Table containing the actual materialized overlap rows.
71 Notes
72 -----
73 At present, this class (like its ABC) is just a stub that creates the
74 tables it will use, but does nothing else.
75 """
77 def __init__(
78 self,
79 db: Database,
80 elementStorage: tuple[DatabaseDimensionRecordStorage, DatabaseDimensionRecordStorage],
81 governorStorage: tuple[GovernorDimensionRecordStorage, GovernorDimensionRecordStorage],
82 summaryTable: sqlalchemy.schema.Table,
83 overlapTable: sqlalchemy.schema.Table,
84 ):
85 self._db = db
86 self._elementStorage = elementStorage
87 self._governorStorage = governorStorage
88 self._summaryTable = summaryTable
89 self._overlapTable = overlapTable
91 @classmethod
92 def initialize(
93 cls,
94 db: Database,
95 elementStorage: tuple[DatabaseDimensionRecordStorage, DatabaseDimensionRecordStorage],
96 governorStorage: tuple[GovernorDimensionRecordStorage, GovernorDimensionRecordStorage],
97 context: StaticTablesContext | None = None,
98 ) -> DatabaseDimensionOverlapStorage:
99 # Docstring inherited from DatabaseDimensionOverlapStorage.
100 if context is not None: 100 ↛ 103line 100 didn't jump to line 103, because the condition on line 100 was never false
101 op = context.addTable
102 else:
103 op = db.ensureTableExists
104 elements = (elementStorage[0].element, elementStorage[1].element)
105 summaryTable = op(
106 cls._SUMMARY_TABLE_NAME_SPEC.format(*elements),
107 cls._makeSummaryTableSpec(elements),
108 )
109 overlapTable = op(
110 cls._OVERLAP_TABLE_NAME_SPEC.format(*elements),
111 cls._makeOverlapTableSpec(elements),
112 )
113 return cls(
114 db,
115 elementStorage,
116 governorStorage,
117 summaryTable=summaryTable,
118 overlapTable=overlapTable,
119 )
121 @property
122 def elements(self) -> tuple[DatabaseDimensionElement, DatabaseDimensionElement]:
123 # Docstring inherited from DatabaseDimensionOverlapStorage.
124 return (self._elementStorage[0].element, self._elementStorage[1].element)
126 def digestTables(self) -> Iterable[sqlalchemy.schema.Table]:
127 # Docstring inherited from DatabaseDimensionOverlapStorage.
128 return [self._summaryTable, self._overlapTable]
130 _SUMMARY_TABLE_NAME_SPEC = "{0.name}_{1.name}_overlap_summary"
132 @classmethod
133 def _makeSummaryTableSpec(
134 cls, elements: tuple[DatabaseDimensionElement, DatabaseDimensionElement]
135 ) -> ddl.TableSpec:
136 """Create a specification for the table that records which combinations
137 of skypix dimension and governor value have materialized overlaps.
139 Parameters
140 ----------
141 elements : `tuple` [ `DatabaseDimensionElement` ]
142 Dimension elements whose overlaps are to be managed.
144 Returns
145 -------
146 tableSpec : `ddl.TableSpec`
147 Table specification.
148 """
149 assert elements[0].spatial is not None and elements[1].spatial is not None
150 assert elements[0].spatial.governor != elements[1].spatial.governor
151 tableSpec = ddl.TableSpec(fields=[])
152 addDimensionForeignKey(tableSpec, elements[0].spatial.governor, primaryKey=True)
153 addDimensionForeignKey(tableSpec, elements[1].spatial.governor, primaryKey=True)
154 return tableSpec
156 _OVERLAP_TABLE_NAME_SPEC = "{0.name}_{1.name}_overlap"
158 @classmethod
159 def _makeOverlapTableSpec(
160 cls, elements: tuple[DatabaseDimensionElement, DatabaseDimensionElement]
161 ) -> ddl.TableSpec:
162 """Create a specification for the table that holds materialized
163 overlap rows.
165 Parameters
166 ----------
167 elements : `tuple` [ `DatabaseDimensionElement` ]
168 Dimension elements whose overlaps are to be managed.
170 Returns
171 -------
172 tableSpec : `ddl.TableSpec`
173 Table specification.
174 """
175 assert elements[0].graph.required.isdisjoint(elements[1].graph.required)
176 tableSpec = ddl.TableSpec(fields=[])
177 # Add governor dimensions first, so they appear first in the primary
178 # key; we may often (in the future, perhaps always) know these at
179 # query-generation time.
180 for element in elements:
181 assert element.spatial is not None
182 addDimensionForeignKey(tableSpec, element.spatial.governor, primaryKey=True)
183 # Add remaining dimension keys.
184 for element in elements:
185 assert element.spatial is not None
186 for dimension in element.required:
187 if dimension != element.spatial.governor:
188 addDimensionForeignKey(tableSpec, dimension, primaryKey=True)
189 return tableSpec
191 def clearCaches(self) -> None:
192 # Docstring inherited from DatabaseDimensionOverlapStorage.
193 pass
195 def make_relation(
196 self,
197 context: queries.SqlQueryContext,
198 governor_constraints: Mapping[str, Set[str]],
199 ) -> Relation | None:
200 # Docstring inherited.
201 return None