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"""The default concrete implementations of the classes that manage 

23opaque tables for `Registry`. 

24""" 

25 

26__all__ = ["ByNameOpaqueTableStorage", "ByNameOpaqueTableStorageManager"] 

27 

28from typing import ( 

29 Any, 

30 ClassVar, 

31 Dict, 

32 Iterable, 

33 Iterator, 

34 Optional, 

35) 

36 

37import sqlalchemy 

38 

39from ..core.ddl import TableSpec, FieldSpec 

40from .interfaces import ( 

41 Database, 

42 OpaqueTableStorageManager, 

43 OpaqueTableStorage, 

44 StaticTablesContext, 

45 VersionTuple 

46) 

47 

48 

49# This has to be updated on every schema change 

50_VERSION = VersionTuple(0, 2, 0) 

51 

52 

53class ByNameOpaqueTableStorage(OpaqueTableStorage): 

54 """An implementation of `OpaqueTableStorage` that simply creates a true 

55 table for each different named opaque logical table. 

56 

57 A `ByNameOpaqueTableStorageManager` instance should always be used to 

58 construct and manage instances of this class. 

59 

60 Parameters 

61 ---------- 

62 db : `Database` 

63 Database engine interface for the namespace in which this table lives. 

64 name : `str` 

65 Name of the logical table (also used as the name of the actual table). 

66 table : `sqlalchemy.schema.Table` 

67 SQLAlchemy representation of the table, which must have already been 

68 created in the namespace managed by ``db`` (this is the responsibility 

69 of `ByNameOpaqueTableStorageManager`). 

70 """ 

71 def __init__(self, *, db: Database, name: str, table: sqlalchemy.schema.Table): 

72 super().__init__(name=name) 

73 self._db = db 

74 self._table = table 

75 

76 def insert(self, *data: dict) -> None: 

77 # Docstring inherited from OpaqueTableStorage. 

78 self._db.insert(self._table, *data) 

79 

80 def fetch(self, **where: Any) -> Iterator[dict]: 

81 # Docstring inherited from OpaqueTableStorage. 

82 sql = self._table.select() 

83 if where: 

84 clauses = [] 

85 for k, v in where.items(): 

86 column = self._table.columns[k] 

87 if isinstance(v, (list, tuple, set)): 87 ↛ 88line 87 didn't jump to line 88, because the condition on line 87 was never true

88 clause = column.in_(v) 

89 else: 

90 clause = column == v 

91 clauses.append(clause) 

92 sql = sql.where( 

93 sqlalchemy.sql.and_(*clauses) 

94 ) 

95 for row in self._db.query(sql): 

96 yield dict(row) 

97 

98 def delete(self, columns: Iterable[str], *rows: dict) -> None: 

99 # Docstring inherited from OpaqueTableStorage. 

100 self._db.delete(self._table, columns, *rows) 

101 

102 

103class ByNameOpaqueTableStorageManager(OpaqueTableStorageManager): 

104 """An implementation of `OpaqueTableStorageManager` that simply creates a 

105 true table for each different named opaque logical table. 

106 

107 Instances of this class should generally be constructed via the 

108 `initialize` class method instead of invoking ``__init__`` directly. 

109 

110 Parameters 

111 ---------- 

112 db : `Database` 

113 Database engine interface for the namespace in which this table lives. 

114 metaTable : `sqlalchemy.schema.Table` 

115 SQLAlchemy representation of the table that records which opaque 

116 logical tables exist. 

117 """ 

118 def __init__(self, db: Database, metaTable: sqlalchemy.schema.Table): 

119 self._db = db 

120 self._metaTable = metaTable 

121 self._storage: Dict[str, OpaqueTableStorage] = {} 

122 

123 _META_TABLE_NAME: ClassVar[str] = "opaque_meta" 

124 

125 _META_TABLE_SPEC: ClassVar[TableSpec] = TableSpec( 

126 fields=[ 

127 FieldSpec("table_name", dtype=sqlalchemy.String, length=128, primaryKey=True), 

128 ], 

129 ) 

130 

131 @classmethod 

132 def initialize(cls, db: Database, context: StaticTablesContext) -> OpaqueTableStorageManager: 

133 # Docstring inherited from OpaqueTableStorageManager. 

134 metaTable = context.addTable(cls._META_TABLE_NAME, cls._META_TABLE_SPEC) 

135 return cls(db=db, metaTable=metaTable) 

136 

137 def get(self, name: str) -> Optional[OpaqueTableStorage]: 

138 # Docstring inherited from OpaqueTableStorageManager. 

139 return self._storage.get(name) 

140 

141 def register(self, name: str, spec: TableSpec) -> OpaqueTableStorage: 

142 # Docstring inherited from OpaqueTableStorageManager. 

143 result = self._storage.get(name) 

144 if result is None: 144 ↛ 154line 144 didn't jump to line 154, because the condition on line 144 was never false

145 # Create the table itself. If it already exists but wasn't in 

146 # the dict because it was added by another client since this one 

147 # was initialized, that's fine. 

148 table = self._db.ensureTableExists(name, spec) 

149 # Add a row to the meta table so we can find this table in the 

150 # future. Also okay if that already exists, so we use sync. 

151 self._db.sync(self._metaTable, keys={"table_name": name}) 

152 result = ByNameOpaqueTableStorage(name=name, table=table, db=self._db) 

153 self._storage[name] = result 

154 return result 

155 

156 @classmethod 

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

158 # Docstring inherited from VersionedExtension. 

159 return _VERSION 

160 

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

162 # Docstring inherited from VersionedExtension. 

163 return self._defaultSchemaDigest([self._metaTable], self._db.dialect)