Coverage for python/lsst/daf/butler/registry/databases/oracle.py : 24%

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
# This file is part of daf_butler. # # Developed for the LSST Data Management System. # This product includes software developed by the LSST Project # (http://www.lsst.org). # See the COPYRIGHT file at the top-level directory of this distribution # for details of code ownership. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>.
"""A SQLAlchemy query that compiles to a MERGE invocation that is the equivalent of PostgreSQL and SQLite's INSERT ... ON CONFLICT REPLACE on the primary key constraint for the table. """
super().__init__() self.table = table
def _merge(merge, compiler, **kw): """Generate MERGE query for inserting or updating records. """ table = merge.table preparer = compiler.preparer
allColumns = [col.name for col in table.columns] pkColumns = [col.name for col in table.primary_key] nonPkColumns = [col for col in allColumns if col not in pkColumns]
# To properly support type decorators defined in core/schema.py we need # to pass column type to `bindparam`. selectColumns = [sqlalchemy.sql.bindparam(col.name, type_=col.type).label(col.name) for col in table.columns] selectClause = sqlalchemy.sql.select(selectColumns)
tableAlias = table.alias("t") tableAliasText = compiler.process(tableAlias, asfrom=True, **kw) selectAlias = selectClause.alias("d") selectAliasText = compiler.process(selectAlias, asfrom=True, **kw)
condition = sqlalchemy.sql.and_( *[tableAlias.columns[col] == selectAlias.columns[col] for col in pkColumns] ) conditionText = compiler.process(condition, **kw)
query = f"MERGE INTO {tableAliasText}" \ f"\nUSING {selectAliasText}" \ f"\nON ({conditionText})" updates = [] for col in nonPkColumns: src = compiler.process(selectAlias.columns[col], **kw) dst = compiler.process(tableAlias.columns[col], **kw) updates.append(f"{dst} = {src}") updates = ", ".join(updates) query += f"\nWHEN MATCHED THEN UPDATE SET {updates}"
insertColumns = ", ".join([preparer.format_column(col) for col in table.columns]) insertValues = ", ".join([compiler.process(selectAlias.columns[col], **kw) for col in allColumns])
query += f"\nWHEN NOT MATCHED THEN INSERT ({insertColumns}) VALUES ({insertValues})" return query
"""An implementation of the `Database` interface for Oracle.
Parameters ---------- connection : `sqlalchemy.engine.Connection` An existing connection created by a previous call to `connect`. origin : `int` An integer ID that should be used as the default for any datasets, quanta, or other entities that use a (autoincrement, origin) compound primary key. namespace : `str`, optional The namespace (schema) this database is associated with. If `None`, the default schema for the connection is used (which may be `None`). writeable : `bool`, optional If `True`, allow write operations on the database, including ``CREATE TABLE``. prefix : `str`, optional Prefix to add to all table names, effectively defining a virtual schema that can coexist with others within the same actual database schema. This prefix must not be used in the un-prefixed names of tables. """
namespace: Optional[str] = None, writeable: bool = True, prefix: Optional[str] = None): # Get the schema that was included/implicit in the URI we used to # connect. dbapi = connection.engine.raw_connection() namespace = dbapi.current_schema super().__init__(connection=connection, origin=origin, namespace=namespace) self._writeable = writeable self.dsn = dbapi.dsn self.prefix = prefix self._shrinker = NameShrinker(connection.engine.dialect.max_identifier_length)
connection = sqlalchemy.engine.create_engine(uri, pool_size=1).connect() # Work around SQLAlchemy assuming that the Oracle limit on identifier # names is even shorter than it is after 12.2. oracle_ver = connection.engine.dialect._get_server_version_info(connection) if oracle_ver < (12, 2): raise RuntimeError("Oracle server version >= 12.2 required.") connection.engine.dialect.max_identifier_length = 128 return connection
namespace: Optional[str] = None, writeable: bool = True) -> Database: return cls(connection=connection, origin=origin, writeable=writeable, namespace=namespace)
with super().transaction(interrupting=interrupting): if not self.isWriteable(): with closing(self._connection.connection.cursor()) as cursor: cursor.execute("SET TRANSACTION READ ONLY") yield
return self._writeable
if self.namespace is None: name = self.dsn else: name = f"{self.dsn:self.namespace}" return f"Oracle@{name}"
return self._shrinker.shrink(original)
return self._shrinker.expand(shrunk)
**kwds) -> sqlalchemy.schema.ForeignKeyConstraint: if self.prefix is not None: spec = copy.copy(spec) spec.table = self.prefix + spec.table return super()._convertForeignKeySpec(table, spec, metadata, **kwds)
**kwds) -> sqlalchemy.schema.Table: if self.prefix is not None and not name.startswith(self.prefix): name = self.prefix + name return super()._convertTableSpec(name, spec, metadata, **kwds)
if self.prefix is not None and not name.startswith(self.prefix): name = self.prefix + name return super().getExistingTable(name, spec)
if not self.isWriteable(): raise ReadOnlyDatabaseError(f"Attempt to replace into read-only database '{self}'.") self._connection.execute(_Merge(table), *rows)
"""A prefix included in all table names to simulate a database namespace (`str` or `None`). """
""" |