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__ = ["SynthIntKeyCollectionManager"] 

24 

25from typing import ( 

26 Any, 

27 Dict, 

28 Iterable, 

29 Optional, 

30 Type, 

31 TYPE_CHECKING, 

32) 

33 

34import sqlalchemy 

35 

36from ._base import ( 

37 CollectionTablesTuple, 

38 DefaultCollectionManager, 

39 makeRunTableSpec, 

40 makeCollectionChainTableSpec, 

41) 

42from ...core import TimespanDatabaseRepresentation, ddl 

43from ..interfaces import CollectionRecord, VersionTuple 

44 

45if TYPE_CHECKING: 45 ↛ 46line 45 didn't jump to line 46, because the condition on line 45 was never true

46 from ..interfaces import Database, DimensionRecordStorageManager, StaticTablesContext 

47 

48 

49_KEY_FIELD_SPEC = ddl.FieldSpec("collection_id", dtype=sqlalchemy.BigInteger, primaryKey=True, 

50 autoincrement=True) 

51 

52 

53# This has to be updated on every schema change 

54_VERSION = VersionTuple(1, 0, 0) 

55 

56 

57def _makeTableSpecs(tsRepr: Type[TimespanDatabaseRepresentation]) -> CollectionTablesTuple: 

58 return CollectionTablesTuple( 

59 collection=ddl.TableSpec( 

60 fields=[ 

61 _KEY_FIELD_SPEC, 

62 ddl.FieldSpec("name", dtype=sqlalchemy.String, length=64, nullable=False), 

63 ddl.FieldSpec("type", dtype=sqlalchemy.SmallInteger, nullable=False), 

64 ], 

65 unique=[("name",)], 

66 ), 

67 run=makeRunTableSpec("collection_id", sqlalchemy.BigInteger, tsRepr), 

68 collection_chain=makeCollectionChainTableSpec("collection_id", sqlalchemy.BigInteger), 

69 ) 

70 

71 

72class SynthIntKeyCollectionManager(DefaultCollectionManager): 

73 """A `CollectionManager` implementation that uses synthetic primary key 

74 (auto-incremented integer) for collections table. 

75 

76 Most of the logic, including caching policy, is implemented in the base 

77 class, this class only adds customizations specific to this particular 

78 table schema. 

79 

80 Parameters 

81 ---------- 

82 db : `Database` 

83 Interface to the underlying database engine and namespace. 

84 tables : `NamedTuple` 

85 Named tuple of SQLAlchemy table objects. 

86 collectionIdName : `str` 

87 Name of the column in collections table that identifies it (PK). 

88 dimensions : `DimensionRecordStorageManager` 

89 Manager object for the dimensions in this `Registry`. 

90 """ 

91 def __init__( 

92 self, 

93 db: Database, 

94 tables: CollectionTablesTuple, 

95 collectionIdName: str, 

96 dimensions: DimensionRecordStorageManager, 

97 ): 

98 super().__init__(db=db, tables=tables, collectionIdName=collectionIdName, dimensions=dimensions) 

99 self._nameCache: Dict[str, CollectionRecord] = {} # indexed by collection name 

100 

101 @classmethod 

102 def initialize( 

103 cls, 

104 db: Database, 

105 context: StaticTablesContext, *, 

106 dimensions: DimensionRecordStorageManager, 

107 ) -> SynthIntKeyCollectionManager: 

108 # Docstring inherited from CollectionManager. 

109 return cls( 

110 db, 

111 tables=context.addTableTuple(_makeTableSpecs(db.getTimespanRepresentation())), # type: ignore 

112 collectionIdName="collection_id", 

113 dimensions=dimensions, 

114 ) 

115 

116 @classmethod 

117 def getCollectionForeignKeyName(cls, prefix: str = "collection") -> str: 

118 # Docstring inherited from CollectionManager. 

119 return f"{prefix}_id" 

120 

121 @classmethod 

122 def getRunForeignKeyName(cls, prefix: str = "run") -> str: 

123 # Docstring inherited from CollectionManager. 

124 return f"{prefix}_id" 

125 

126 @classmethod 

127 def addCollectionForeignKey(cls, tableSpec: ddl.TableSpec, *, prefix: str = "collection", 

128 onDelete: Optional[str] = None, 

129 constraint: bool = True, 

130 **kwargs: Any) -> ddl.FieldSpec: 

131 # Docstring inherited from CollectionManager. 

132 original = _KEY_FIELD_SPEC 

133 copy = ddl.FieldSpec(cls.getCollectionForeignKeyName(prefix), dtype=original.dtype, 

134 autoincrement=False, **kwargs) 

135 tableSpec.fields.add(copy) 

136 if constraint: 136 ↛ 139line 136 didn't jump to line 139, because the condition on line 136 was never false

137 tableSpec.foreignKeys.append(ddl.ForeignKeySpec("collection", source=(copy.name,), 

138 target=(original.name,), onDelete=onDelete)) 

139 return copy 

140 

141 @classmethod 

142 def addRunForeignKey(cls, tableSpec: ddl.TableSpec, *, prefix: str = "run", 

143 onDelete: Optional[str] = None, 

144 constraint: bool = True, 

145 **kwargs: Any) -> ddl.FieldSpec: 

146 # Docstring inherited from CollectionManager. 

147 original = _KEY_FIELD_SPEC 

148 copy = ddl.FieldSpec(cls.getRunForeignKeyName(prefix), dtype=original.dtype, 

149 autoincrement=False, **kwargs) 

150 tableSpec.fields.add(copy) 

151 if constraint: 

152 tableSpec.foreignKeys.append(ddl.ForeignKeySpec("run", source=(copy.name,), 

153 target=(original.name,), onDelete=onDelete)) 

154 return copy 

155 

156 def _setRecordCache(self, records: Iterable[CollectionRecord]) -> None: 

157 """Set internal record cache to contain given records, 

158 old cached records will be removed. 

159 """ 

160 self._records = {} 

161 self._nameCache = {} 

162 for record in records: 

163 self._records[record.key] = record 

164 self._nameCache[record.name] = record 

165 

166 def _addCachedRecord(self, record: CollectionRecord) -> None: 

167 """Add single record to cache. 

168 """ 

169 self._records[record.key] = record 

170 self._nameCache[record.name] = record 

171 

172 def _removeCachedRecord(self, record: CollectionRecord) -> None: 

173 """Remove single record from cache. 

174 """ 

175 del self._records[record.key] 

176 del self._nameCache[record.name] 

177 

178 def _getByName(self, name: str) -> Optional[CollectionRecord]: 

179 # Docstring inherited from DefaultCollectionManager. 

180 return self._nameCache.get(name) 

181 

182 @classmethod 

183 def currentVersion(cls) -> Optional[VersionTuple]: 

184 # Docstring inherited from VersionedExtension. 

185 return _VERSION 

186 

187 def schemaDigest(self) -> Optional[str]: 

188 # Docstring inherited from VersionedExtension. 

189 return self._defaultSchemaDigest(self._tables, self._db.dialect)