Hide keyboard shortcuts

Hot-keys 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

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

21from __future__ import annotations 

22 

23__all__ = ["SpatialDimensionRecordStorage"] 

24 

25from typing import Optional 

26 

27import sqlalchemy 

28 

29from ...core import DimensionElement, DimensionRecord, Timespan, ddl 

30from ...core.utils import NamedKeyDict, NamedValueSet 

31from ...core.dimensions.schema import makeElementTableSpec, REGION_FIELD_SPEC, addDimensionForeignKey 

32from ..interfaces import Database, DimensionRecordStorage, StaticTablesContext 

33from ..queries import QueryBuilder 

34from .table import TableDimensionRecordStorage 

35 

36 

37_OVERLAP_TABLE_NAME_PATTERN = "{0}_{1}_overlap" 

38 

39 

40def _makeOverlapTableSpec(a: DimensionElement, b: DimensionElement) -> ddl.TableSpec: 

41 """Create a specification for a table that represents a many-to-many 

42 relationship between two `DimensionElement` tables. 

43 

44 Parameters 

45 ---------- 

46 a : `DimensionElement` 

47 First element in the relationship. 

48 b : `DimensionElement` 

49 Second element in the relationship. 

50 

51 Returns 

52 ------- 

53 spec : `TableSpec` 

54 Database-agnostic specification for a table. 

55 """ 

56 tableSpec = ddl.TableSpec( 

57 fields=NamedValueSet(), 

58 unique=set(), 

59 foreignKeys=[], 

60 ) 

61 for dimension in a.graph.required: 

62 addDimensionForeignKey(tableSpec, dimension, primaryKey=True) 

63 for dimension in b.graph.required: 

64 addDimensionForeignKey(tableSpec, dimension, primaryKey=True) 

65 return tableSpec 

66 

67 

68class SpatialDimensionRecordStorage(TableDimensionRecordStorage): 

69 """A record storage implementation for spatial dimension elements that uses 

70 a regular database table. 

71 

72 Parameters 

73 ---------- 

74 db : `Database` 

75 Interface to the database engine and namespace that will hold these 

76 dimension records. 

77 element : `DimensionElement` 

78 The element whose records this storage will manage. 

79 table : `sqlalchemy.schema.Table` 

80 The logical table for the element. 

81 commonSkyPixOverlapTable : `sqlalchemy.schema.Table`, optional 

82 The logical table for the overlap table with the dimension universe's 

83 common skypix dimension. 

84 """ 

85 def __init__(self, db: Database, element: DimensionElement, *, table: sqlalchemy.schema.Table, 

86 commonSkyPixOverlapTable: sqlalchemy.schema.Table): 

87 super().__init__(db, element, table=table) 

88 self._commonSkyPixOverlapTable = commonSkyPixOverlapTable 

89 assert element.spatial 

90 

91 @classmethod 

92 def initialize(cls, db: Database, element: DimensionElement, *, 

93 context: Optional[StaticTablesContext] = None) -> DimensionRecordStorage: 

94 # Docstring inherited from DimensionRecordStorage. 

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

96 method = context.addTable 

97 else: 

98 method = db.ensureTableExists 

99 return cls( 

100 db, 

101 element, 

102 table=method(element.name, makeElementTableSpec(element)), 

103 commonSkyPixOverlapTable=method( 

104 _OVERLAP_TABLE_NAME_PATTERN.format(element.name, element.universe.commonSkyPix.name), 

105 _makeOverlapTableSpec(element, element.universe.commonSkyPix) 

106 ) 

107 ) 

108 

109 def join( 

110 self, 

111 builder: QueryBuilder, *, 

112 regions: Optional[NamedKeyDict[DimensionElement, sqlalchemy.sql.ColumnElement]] = None, 

113 timespans: Optional[NamedKeyDict[DimensionElement, Timespan[sqlalchemy.sql.ColumnElement]]] = None, 

114 ): 

115 # Docstring inherited from DimensionRecordStorage. 

116 if regions is not None: 

117 dimensions = NamedValueSet(self.element.graph.required) 

118 dimensions.add(self.element.universe.universe.commonSkyPix) 

119 builder.joinTable(self._commonSkyPixOverlapTable, dimensions) 

120 regions[self.element] = self._table.columns[REGION_FIELD_SPEC.name] 

121 super().join(builder, regions=None, timespans=timespans) 

122 

123 def insert(self, *records: DimensionRecord): 

124 # Docstring inherited from DimensionRecordStorage.insert. 

125 commonSkyPixRows = [] 

126 commonSkyPix = self.element.universe.commonSkyPix 

127 for record in records: 

128 if record.region is None: 

129 # TODO: should we warn about this case? 

130 continue 

131 base = record.dataId.byName() 

132 for begin, end in commonSkyPix.pixelization.envelope(record.region): 

133 for skypix in range(begin, end): 

134 row = base.copy() 

135 row[commonSkyPix.name] = skypix 

136 commonSkyPixRows.append(row) 

137 with self._db.transaction(): 

138 super().insert(*records) 

139 if commonSkyPixRows: 

140 self._db.insert(self._commonSkyPixOverlapTable, *commonSkyPixRows)