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

21 

22from contextlib import contextmanager, closing 

23import os 

24import os.path 

25import secrets 

26from typing import List 

27import unittest 

28 

29import sqlalchemy 

30 

31from lsst.utils import doImport 

32from lsst.daf.butler import DimensionUniverse, ddl 

33from lsst.daf.butler.registry.databases.oracle import OracleDatabase 

34from lsst.daf.butler.registry import Registry 

35from lsst.daf.butler.registry.tests import DatabaseTests, RegistryTests 

36 

37ENVVAR = "DAF_BUTLER_ORACLE_TEST_URI" 

38TEST_URI = os.environ.get(ENVVAR) 

39 

40 

41def cleanUpPrefixes(connection: sqlalchemy.engine.Connection, prefixes: List[str]): 

42 """Drop all tables and other schema entities that start with any of the 

43 given prefixes. 

44 """ 

45 commands = [] 

46 dbapi = connection.connection 

47 with closing(dbapi.cursor()) as cursor: 

48 for objectType, objectName in cursor.execute("SELECT object_type, object_name FROM user_objects"): 

49 if not any(objectName.lower().startswith(prefix) for prefix in prefixes): 

50 continue 

51 if objectType == "TABLE": 

52 commands.append(f'DROP TABLE "{objectName}" CASCADE CONSTRAINTS') 

53 elif objectType in ("VIEW", "PROCEDURE", "SEQUENCE"): 

54 commands.append(f'DROP {objectType} "{objectName}"') 

55 for command in commands: 

56 cursor.execute(command) 

57 

58 

59@unittest.skipUnless(TEST_URI is not None, f"{ENVVAR} environment variable not set.") 

60class OracleDatabaseTestCase(unittest.TestCase, DatabaseTests): 

61 

62 @classmethod 

63 def setUpClass(cls): 

64 # Create a single engine for all Database instances we create, to avoid 

65 # repeatedly spending time connecting. 

66 cls._connection = OracleDatabase.connect(TEST_URI) 

67 cls._prefixes = [] 

68 

69 @classmethod 

70 def tearDownClass(cls): 

71 cleanUpPrefixes(cls._connection, cls._prefixes) 

72 

73 def makeEmptyDatabase(self, origin: int = 0) -> OracleDatabase: 

74 prefix = f"test_{secrets.token_hex(8).lower()}_" 

75 self._prefixes.append(prefix) 

76 return OracleDatabase(origin=origin, connection=self._connection, prefix=prefix) 

77 

78 def getNewConnection(self, database: OracleDatabase, *, writeable: bool) -> OracleDatabase: 

79 return OracleDatabase(origin=database.origin, connection=self._connection, 

80 prefix=database.prefix, writeable=writeable) 

81 

82 @contextmanager 

83 def asReadOnly(self, database: OracleDatabase) -> OracleDatabase: 

84 yield self.getNewConnection(database, writeable=False) 

85 

86 def testNameShrinking(self): 

87 """Test that too-long names for database entities other than tables 

88 and columns (which we preserve, and just expect to fit) are shrunk. 

89 """ 

90 db = self.makeEmptyDatabase(origin=1) 

91 with db.declareStaticTables(create=True) as context: 

92 # Table and field names are each below the 128-char limit even when 

93 # accounting for the prefix, but their combination (which will 

94 # appear in sequences and constraints) is not. 

95 tableName = "a_table_with_a_very_very_very_very_very_very_very_very_long_72_char_name" 

96 fieldName1 = "a_column_with_a_very_very_very_very_very_very_very_very_long_73_char_name" 

97 fieldName2 = "another_column_with_a_very_very_very_very_very_very_very_very_long_79_char_name" 

98 context.addTable( 

99 tableName, 

100 ddl.TableSpec( 

101 fields=[ 

102 ddl.FieldSpec( 

103 fieldName1, 

104 dtype=sqlalchemy.BigInteger, 

105 autoincrement=True, 

106 primaryKey=True 

107 ), 

108 ddl.FieldSpec( 

109 fieldName2, 

110 dtype=sqlalchemy.String, 

111 length=16, 

112 nullable=False, 

113 ), 

114 ], 

115 unique={(fieldName2,)}, 

116 ) 

117 ) 

118 # Add another table, this time dynamically, with a foreign key to the 

119 # first table. 

120 db.ensureTableExists( 

121 tableName + "_b", 

122 ddl.TableSpec( 

123 fields=[ 

124 ddl.FieldSpec( 

125 fieldName1 + "_b", 

126 dtype=sqlalchemy.BigInteger, 

127 autoincrement=True, 

128 primaryKey=True 

129 ), 

130 ddl.FieldSpec( 

131 fieldName2 + "_b", 

132 dtype=sqlalchemy.String, 

133 length=16, 

134 nullable=False, 

135 ), 

136 ], 

137 foreignKeys=[ 

138 ddl.ForeignKeySpec(tableName, source=(fieldName2 + "_b",), target=(fieldName2,)), 

139 ] 

140 ) 

141 ) 

142 

143 

144@unittest.skipUnless(TEST_URI is not None, f"{ENVVAR} environment variable not set.") 

145class OracleRegistryTests(RegistryTests): 

146 """Tests for `Registry` backed by an `Oracle` database. 

147 

148 Note 

149 ---- 

150 This is not a subclass of `unittest.TestCase` but to avoid repetition it 

151 defines methods that override `unittest.TestCase` methods. To make this 

152 work sublasses have to have this class first in the bases list. 

153 """ 

154 

155 @classmethod 

156 def setUpClass(cls): 

157 # Create a single engine for all Database instances we create, to avoid 

158 # repeatedly spending time connecting. 

159 cls._connection = OracleDatabase.connect(TEST_URI) 

160 cls._prefixes = [] 

161 

162 @classmethod 

163 def tearDownClass(cls): 

164 cleanUpPrefixes(cls._connection, cls._prefixes) 

165 

166 @classmethod 

167 def getDataDir(cls) -> str: 

168 return os.path.normpath(os.path.join(os.path.dirname(__file__), "data", "registry")) 

169 

170 def makeRegistry(self) -> Registry: 

171 prefix = f"test_{secrets.token_hex(8).lower()}_" 

172 self._prefixes.append(prefix) 

173 config = self.makeRegistryConfig() 

174 # Can't use Registry.fromConfig for these tests because we don't want 

175 # to reconnect to the server every single time. But we at least use 

176 # OracleDatabase.fromConnection rather than the constructor so 

177 # we can try to pass a prefix through via "+" in a namespace. 

178 database = OracleDatabase.fromConnection(connection=self._connection, origin=0, 

179 namespace=f"+{prefix}") 

180 opaque = doImport(config["managers", "opaque"]) 

181 dimensions = doImport(config["managers", "dimensions"]) 

182 collections = doImport(config["managers", "collections"]) 

183 datasets = doImport(config["managers", "datasets"]) 

184 return Registry(database=database, 

185 opaque=opaque, 

186 dimensions=dimensions, 

187 collections=collections, 

188 datasets=datasets, 

189 universe=DimensionUniverse(config), create=True) 

190 

191 

192class OracleRegistryNameKeyCollMgrTestCase(OracleRegistryTests, unittest.TestCase): 

193 """Tests for `Registry` backed by a Oracle database. 

194 

195 This test case uses NameKeyCollectionManager. 

196 """ 

197 collectionsManager = "lsst.daf.butler.registry.collections.nameKey.NameKeyCollectionManager" 

198 

199 

200class OracleRegistrySynthIntKeyCollMgrTestCase(OracleRegistryTests, unittest.TestCase): 

201 """Tests for `Registry` backed by a Oracle database. 

202 

203 This test case uses SynthIntKeyCollectionManager. 

204 """ 

205 collectionsManager = "lsst.daf.butler.registry.collections.synthIntKey.SynthIntKeyCollectionManager" 

206 

207 

208if __name__ == "__main__": 208 ↛ 209line 208 didn't jump to line 209, because the condition on line 208 was never true

209 unittest.main()