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 DatabaseTimespanRepresentation, 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, 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(0, 3, 0) 

55 

56 

57def _makeTableSpecs(tsRepr: Type[DatabaseTimespanRepresentation]) -> 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 """ 

89 def __init__(self, db: Database, tables: CollectionTablesTuple, collectionIdName: str): 

90 super().__init__(db=db, tables=tables, collectionIdName=collectionIdName) 

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

92 

93 @classmethod 

94 def initialize(cls, db: Database, context: StaticTablesContext) -> SynthIntKeyCollectionManager: 

95 # Docstring inherited from CollectionManager. 

96 return cls( 

97 db, 

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

99 collectionIdName="collection_id" 

100 ) 

101 

102 @classmethod 

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

104 # Docstring inherited from CollectionManager. 

105 return f"{prefix}_id" 

106 

107 @classmethod 

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

109 # Docstring inherited from CollectionManager. 

110 return f"{prefix}_id" 

111 

112 @classmethod 

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

114 onDelete: Optional[str] = None, 

115 constraint: bool = True, 

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

117 # Docstring inherited from CollectionManager. 

118 original = _KEY_FIELD_SPEC 

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

120 autoincrement=False, **kwargs) 

121 tableSpec.fields.add(copy) 

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

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

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

125 return copy 

126 

127 @classmethod 

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

129 onDelete: Optional[str] = None, 

130 constraint: bool = True, 

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

132 # Docstring inherited from CollectionManager. 

133 original = _KEY_FIELD_SPEC 

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

135 autoincrement=False, **kwargs) 

136 tableSpec.fields.add(copy) 

137 if constraint: 

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

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

140 return copy 

141 

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

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

144 old cached records will be removed. 

145 """ 

146 self._records = {} 

147 self._nameCache = {} 

148 for record in records: 

149 self._records[record.key] = record 

150 self._nameCache[record.name] = record 

151 

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

153 """Add single record to cache. 

154 """ 

155 self._records[record.key] = record 

156 self._nameCache[record.name] = record 

157 

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

159 """Remove single record from cache. 

160 """ 

161 del self._records[record.key] 

162 del self._nameCache[record.name] 

163 

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

165 # Docstring inherited from DefaultCollectionManager. 

166 return self._nameCache.get(name) 

167 

168 @classmethod 

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

170 # Docstring inherited from VersionedExtension. 

171 return _VERSION 

172 

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

174 # Docstring inherited from VersionedExtension. 

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