Coverage for tests/test_oracle.py : 42%

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/>.
22from contextlib import contextmanager, closing
23import os
24import os.path
25import secrets
26from typing import List
27import unittest
29import sqlalchemy
31from lsst.daf.butler import DimensionUniverse, ddl
32from lsst.daf.butler.registry import RegistryConfig
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
37ENVVAR = "DAF_BUTLER_ORACLE_TEST_URI"
38TEST_URI = os.environ.get(ENVVAR)
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)
59@unittest.skipUnless(TEST_URI is not None, f"{ENVVAR} environment variable not set.")
60class OracleDatabaseTestCase(unittest.TestCase, DatabaseTests):
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 = []
69 @classmethod
70 def tearDownClass(cls):
71 cleanUpPrefixes(cls._connection, cls._prefixes)
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)
78 def getNewConnection(self, database: OracleDatabase, *, writeable: bool) -> OracleDatabase:
79 return OracleDatabase(origin=database.origin, connection=self._connection,
80 prefix=database.prefix, writeable=writeable)
82 @contextmanager
83 def asReadOnly(self, database: OracleDatabase) -> OracleDatabase:
84 yield self.getNewConnection(database, writeable=False)
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 )
144@unittest.skipUnless(TEST_URI is not None, f"{ENVVAR} environment variable not set.")
145class OracleRegistryTestCase(unittest.TestCase, RegistryTests):
146 """Tests for `Registry` backed by an `Oracle` database.
147 """
149 @classmethod
150 def setUpClass(cls):
151 # Create a single engine for all Database instances we create, to avoid
152 # repeatedly spending time connecting.
153 cls._connection = OracleDatabase.connect(TEST_URI)
154 cls._prefixes = []
156 @classmethod
157 def tearDownClass(cls):
158 cleanUpPrefixes(cls._connection, cls._prefixes)
160 def makeRegistry(self) -> Registry:
161 prefix = f"test_{secrets.token_hex(8).lower()}_"
162 self._prefixes.append(prefix)
163 config = RegistryConfig()
164 # Can't use Registry.fromConfig for these tests because we don't want
165 # to reconnect to the server every single time. But we at least use
166 # OracleDatabase.fromConnection rather than the constructor so
167 # we can try to pass a prefix through via "+" in a namespace.
168 database = OracleDatabase.fromConnection(connection=self._connection, origin=0,
169 namespace=f"+{prefix}")
170 return Registry(database=database, dimensions=DimensionUniverse(config), create=True)
173if __name__ == "__main__": 173 ↛ 174line 173 didn't jump to line 174, because the condition on line 173 was never true
174 unittest.main()