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

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/>. 

27 

28from __future__ import annotations 

29 

30__all__ = ("CrossFamilyDimensionOverlapStorage",) 

31 

32import logging 

33from collections.abc import Iterable, Mapping, Set 

34from typing import TYPE_CHECKING 

35 

36import sqlalchemy 

37from lsst.daf.relation import Relation 

38 

39from ...core import DatabaseDimensionElement, addDimensionForeignKey, ddl 

40from ..interfaces import ( 

41 Database, 

42 DatabaseDimensionOverlapStorage, 

43 DatabaseDimensionRecordStorage, 

44 GovernorDimensionRecordStorage, 

45 StaticTablesContext, 

46) 

47 

48if TYPE_CHECKING: 

49 from .. import queries 

50 

51 

52_LOG = logging.getLogger(__name__) 

53 

54 

55class CrossFamilyDimensionOverlapStorage(DatabaseDimensionOverlapStorage): 

56 """Basic implementation of materialized overlaps between 

57 otherwise-unrelated dimension elements. 

58 

59 New instances should be constructed by calling `initialize`, not by calling 

60 the constructor directly. 

61 

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. 

76 

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 """ 

82 

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 

96 

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 ) 

126 

127 @property 

128 def elements(self) -> tuple[DatabaseDimensionElement, DatabaseDimensionElement]: 

129 # Docstring inherited from DatabaseDimensionOverlapStorage. 

130 return (self._elementStorage[0].element, self._elementStorage[1].element) 

131 

132 def digestTables(self) -> Iterable[sqlalchemy.schema.Table]: 

133 # Docstring inherited from DatabaseDimensionOverlapStorage. 

134 return [self._summaryTable, self._overlapTable] 

135 

136 _SUMMARY_TABLE_NAME_SPEC = "{0.name}_{1.name}_overlap_summary" 

137 

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. 

144 

145 Parameters 

146 ---------- 

147 elements : `tuple` [ `DatabaseDimensionElement` ] 

148 Dimension elements whose overlaps are to be managed. 

149 

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 

161 

162 _OVERLAP_TABLE_NAME_SPEC = "{0.name}_{1.name}_overlap" 

163 

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. 

170 

171 Parameters 

172 ---------- 

173 elements : `tuple` [ `DatabaseDimensionElement` ] 

174 Dimension elements whose overlaps are to be managed. 

175 

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 

196 

197 def clearCaches(self) -> None: 

198 # Docstring inherited from DatabaseDimensionOverlapStorage. 

199 pass 

200 

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