Coverage for python/lsst/daf/butler/registry/dimensions/overlaps.py: 97%

Shortcuts on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

52 statements  

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__ = ("CrossFamilyDimensionOverlapStorage",) 

25 

26import logging 

27from typing import Iterable, Optional, Tuple 

28 

29import sqlalchemy 

30 

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

32from ..interfaces import ( 

33 Database, 

34 DatabaseDimensionOverlapStorage, 

35 DatabaseDimensionRecordStorage, 

36 GovernorDimensionRecordStorage, 

37 StaticTablesContext, 

38) 

39 

40_LOG = logging.getLogger(__name__) 

41 

42 

43class CrossFamilyDimensionOverlapStorage(DatabaseDimensionOverlapStorage): 

44 """Basic implementation of materialized overlaps between 

45 otherwise-unrelated dimension elements. 

46 

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

48 the constructor directly. 

49 

50 Parameters 

51 ---------- 

52 db : `Database` 

53 Interface to the underlying database engine and namespace. 

54 elementStorage : `tuple` [ `DatabaseDimensionRecordStorage` ] 

55 Storage objects for the elements this object will relate. 

56 governorStorage : `tuple` [ `GovernorDimensionRecordStorage` ] 

57 Storage objects for the governor dimensions of the elements this 

58 object will relate. 

59 summaryTable : `sqlalchemy.schema.Table` 

60 Table that records which combinations of governor dimension values 

61 have materialized overlap rows. 

62 overlapTable : `sqlalchemy.schema.Table` 

63 Table containing the actual materialized overlap rows. 

64 

65 Notes 

66 ----- 

67 At present, this class (like its ABC) is just a stub that creates the 

68 tables it will use, but does nothing else. 

69 """ 

70 

71 def __init__( 

72 self, 

73 db: Database, 

74 elementStorage: Tuple[DatabaseDimensionRecordStorage, DatabaseDimensionRecordStorage], 

75 governorStorage: Tuple[GovernorDimensionRecordStorage, GovernorDimensionRecordStorage], 

76 summaryTable: sqlalchemy.schema.Table, 

77 overlapTable: sqlalchemy.schema.Table, 

78 ): 

79 self._db = db 

80 self._elementStorage = elementStorage 

81 self._governorStorage = governorStorage 

82 self._summaryTable = summaryTable 

83 self._overlapTable = overlapTable 

84 

85 @classmethod 

86 def initialize( 

87 cls, 

88 db: Database, 

89 elementStorage: Tuple[DatabaseDimensionRecordStorage, DatabaseDimensionRecordStorage], 

90 governorStorage: Tuple[GovernorDimensionRecordStorage, GovernorDimensionRecordStorage], 

91 context: Optional[StaticTablesContext] = None, 

92 ) -> DatabaseDimensionOverlapStorage: 

93 # Docstring inherited from DatabaseDimensionOverlapStorage. 

94 if context is not None: 94 ↛ 97line 94 didn't jump to line 97, because the condition on line 94 was never false

95 op = context.addTable 

96 else: 

97 op = db.ensureTableExists 

98 elements = (elementStorage[0].element, elementStorage[1].element) 

99 summaryTable = op( 

100 cls._SUMMARY_TABLE_NAME_SPEC.format(*elements), 

101 cls._makeSummaryTableSpec(elements), 

102 ) 

103 overlapTable = op( 

104 cls._OVERLAP_TABLE_NAME_SPEC.format(*elements), 

105 cls._makeOverlapTableSpec(elements), 

106 ) 

107 return CrossFamilyDimensionOverlapStorage( 

108 db, 

109 elementStorage, 

110 governorStorage, 

111 summaryTable=summaryTable, 

112 overlapTable=overlapTable, 

113 ) 

114 

115 @property 

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

117 # Docstring inherited from DatabaseDimensionOverlapStorage. 

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

119 

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

121 # Docstring inherited from DatabaseDimensionOverlapStorage. 

122 return [self._summaryTable, self._overlapTable] 

123 

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

125 

126 @classmethod 

127 def _makeSummaryTableSpec( 

128 cls, elements: Tuple[DatabaseDimensionElement, DatabaseDimensionElement] 

129 ) -> ddl.TableSpec: 

130 """Create a specification for the table that records which combinations 

131 of skypix dimension and governor value have materialized overlaps. 

132 

133 Parameters 

134 ---------- 

135 elements : `tuple` [ `DatabaseDimensionElement` ] 

136 Dimension elements whose overlaps are to be managed. 

137 

138 Returns 

139 ------- 

140 tableSpec : `ddl.TableSpec` 

141 Table specification. 

142 """ 

143 assert elements[0].spatial is not None and elements[1].spatial is not None 

144 assert elements[0].spatial.governor != elements[1].spatial.governor 

145 tableSpec = ddl.TableSpec(fields=[]) 

146 addDimensionForeignKey(tableSpec, elements[0].spatial.governor, primaryKey=True) 

147 addDimensionForeignKey(tableSpec, elements[1].spatial.governor, primaryKey=True) 

148 return tableSpec 

149 

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

151 

152 @classmethod 

153 def _makeOverlapTableSpec( 

154 cls, elements: Tuple[DatabaseDimensionElement, DatabaseDimensionElement] 

155 ) -> ddl.TableSpec: 

156 """Create a specification for the table that holds materialized 

157 overlap rows. 

158 

159 Parameters 

160 ---------- 

161 elements : `tuple` [ `DatabaseDimensionElement` ] 

162 Dimension elements whose overlaps are to be managed. 

163 

164 Returns 

165 ------- 

166 tableSpec : `ddl.TableSpec` 

167 Table specification. 

168 """ 

169 assert elements[0].graph.required.isdisjoint(elements[1].graph.required) 

170 tableSpec = ddl.TableSpec(fields=[]) 

171 # Add governor dimensions first, so they appear first in the primary 

172 # key; we may often (in the future, perhaps always) know these at 

173 # query-generation time. 

174 for element in elements: 

175 assert element.spatial is not None 

176 addDimensionForeignKey(tableSpec, element.spatial.governor, primaryKey=True) 

177 # Add remaining dimension keys. 

178 for element in elements: 

179 assert element.spatial is not None 

180 for dimension in element.required: 

181 if dimension != element.spatial.governor: 

182 addDimensionForeignKey(tableSpec, dimension, primaryKey=True) 

183 return tableSpec