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

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

21 

22from __future__ import annotations 

23 

24__all__ = ( 

25 "CrossFamilyDimensionOverlapStorage", 

26) 

27 

28import logging 

29from typing import ( 

30 Iterable, 

31 Optional, 

32 Tuple, 

33) 

34 

35import sqlalchemy 

36 

37from ...core import ( 

38 addDimensionForeignKey, 

39 DatabaseDimensionElement, 

40 ddl, 

41) 

42from ..interfaces import ( 

43 Database, 

44 DatabaseDimensionOverlapStorage, 

45 DatabaseDimensionRecordStorage, 

46 GovernorDimensionRecordStorage, 

47 StaticTablesContext, 

48) 

49 

50 

51_LOG = logging.getLogger(__name__) 

52 

53 

54class CrossFamilyDimensionOverlapStorage(DatabaseDimensionOverlapStorage): 

55 """Basic implementation of materialized overlaps between 

56 otherwise-unrelated dimension elements. 

57 

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

59 the constructor directly. 

60 

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. 

75 

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 

94 

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 ) 

124 

125 @property 

126 def elements(self) -> Tuple[DatabaseDimensionElement, DatabaseDimensionElement]: 

127 # Docstring inherited from DatabaseDimensionOverlapStorage. 

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

129 

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

131 # Docstring inherited from DatabaseDimensionOverlapStorage. 

132 return [self._summaryTable, self._overlapTable] 

133 

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

135 

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. 

141 

142 Parameters 

143 ---------- 

144 elements : `tuple` [ `DatabaseDimensionElement` ] 

145 Dimension elements whose overlaps are to be managed. 

146 

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 

158 

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

160 

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. 

166 

167 Parameters 

168 ---------- 

169 elements : `tuple` [ `DatabaseDimensionElement` ] 

170 Dimension elements whose overlaps are to be managed. 

171 

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