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