relstorage/relstorage/adapters/oracle/schema.py

455 lines
15 KiB
Python

##############################################################################
#
# Copyright (c) 2009 Zope Foundation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Database schema installers
"""
from __future__ import absolute_import
from ..interfaces import ISchemaInstaller
from ..schema import AbstractSchemaInstaller
from zope.interface import implementer
import re
# Versions of the installed stored procedures. Change these when
# the corresponding code changes.
oracle_package_version = '1.5A'
oracle_history_preserving_package = """
CREATE OR REPLACE PACKAGE relstorage_op AS
TYPE numlist IS TABLE OF NUMBER(20) INDEX BY BINARY_INTEGER;
TYPE md5list IS TABLE OF VARCHAR2(32) INDEX BY BINARY_INTEGER;
TYPE statelist IS TABLE OF RAW(2000) INDEX BY BINARY_INTEGER;
PROCEDURE store_temp(
zoids IN numlist,
prev_tids IN numlist,
md5s IN md5list,
states IN statelist);
PROCEDURE restore(
zoids IN numlist,
tids IN numlist,
md5s IN md5list,
states IN statelist);
END relstorage_op;
/
CREATE OR REPLACE PACKAGE BODY relstorage_op AS
/* Version: %(oracle_package_version)s */
PROCEDURE store_temp(
zoids IN numlist,
prev_tids IN numlist,
md5s IN md5list,
states IN statelist) IS
BEGIN
FORALL indx IN zoids.first..zoids.last
DELETE FROM temp_store WHERE zoid = zoids(indx);
FORALL indx IN zoids.first..zoids.last
INSERT INTO temp_store (zoid, prev_tid, md5, state) VALUES
(zoids(indx), prev_tids(indx), md5s(indx), states(indx));
END store_temp;
PROCEDURE restore(
zoids IN numlist,
tids IN numlist,
md5s IN md5list,
states IN statelist) IS
BEGIN
FORALL indx IN zoids.first..zoids.last
DELETE FROM object_state
WHERE zoid = zoids(indx)
AND tid = tids(indx);
FORALL indx IN zoids.first..zoids.last
INSERT INTO object_state
(zoid, tid, prev_tid, md5, state_size, state)
VALUES (zoids(indx), tids(indx),
COALESCE((SELECT tid
FROM current_object
WHERE zoid = zoids(indx)), 0),
md5s(indx), COALESCE(LENGTH(states(indx)), 0), states(indx));
END restore;
END relstorage_op;
/
""" % globals()
oracle_history_free_package = """
CREATE OR REPLACE PACKAGE relstorage_op AS
TYPE numlist IS TABLE OF NUMBER(20) INDEX BY BINARY_INTEGER;
TYPE md5list IS TABLE OF VARCHAR2(32) INDEX BY BINARY_INTEGER;
TYPE statelist IS TABLE OF RAW(2000) INDEX BY BINARY_INTEGER;
PROCEDURE store_temp(
zoids IN numlist,
prev_tids IN numlist,
md5s IN md5list,
states IN statelist);
PROCEDURE restore(
zoids IN numlist,
tids IN numlist,
states IN statelist);
END relstorage_op;
/
CREATE OR REPLACE PACKAGE BODY relstorage_op AS
/* Version: %(oracle_package_version)s */
PROCEDURE store_temp(
zoids IN numlist,
prev_tids IN numlist,
md5s IN md5list,
states IN statelist) IS
BEGIN
FORALL indx IN zoids.first..zoids.last
DELETE FROM temp_store WHERE zoid = zoids(indx);
FORALL indx IN zoids.first..zoids.last
INSERT INTO temp_store (zoid, prev_tid, md5, state) VALUES
(zoids(indx), prev_tids(indx), md5s(indx), states(indx));
END store_temp;
PROCEDURE restore(
zoids IN numlist,
tids IN numlist,
states IN statelist) IS
BEGIN
FORALL indx IN zoids.first..zoids.last
DELETE FROM object_state WHERE zoid = zoids(indx);
FORALL indx IN zoids.first..zoids.last
INSERT INTO object_state (zoid, tid, state_size, state) VALUES (
zoids(indx),
tids(indx),
COALESCE(LENGTH(states(indx)), 0),
states(indx));
END restore;
END relstorage_op;
/
""" % globals()
@implementer(ISchemaInstaller)
class OracleSchemaInstaller(AbstractSchemaInstaller):
database_type = 'oracle'
def get_database_name(self, cursor):
cursor.execute("SELECT ora_database_name FROM DUAL")
for (name,) in cursor:
return name
def prepare(self):
"""Create the database schema if it does not already exist."""
def callback(_conn, cursor):
tables = self.list_tables(cursor)
if 'object_state' not in tables:
self.create(cursor)
else:
self.check_compatibility(cursor, tables)
self.update_schema(cursor, tables)
packages = self.list_packages(cursor)
package_name = 'relstorage_op'
if packages.get(package_name) != oracle_package_version:
self.install_package(cursor)
packages = self.list_packages(cursor)
if packages.get(package_name) != oracle_package_version:
raise AssertionError(
"Could not get version information after "
"installing the %s package." % package_name)
self.connmanager.open_and_call(callback)
def install_package(self, cursor):
"""Install the package containing stored procedures"""
if self.keep_history:
code = oracle_history_preserving_package
else:
code = oracle_history_free_package
for stmt in code.split('\n/\n'):
if stmt.strip():
cursor.execute(stmt)
def list_tables(self, cursor):
cursor.execute("SELECT table_name FROM user_tables")
return [name.lower() for (name,) in cursor.fetchall()]
def list_sequences(self, cursor):
cursor.execute("SELECT sequence_name FROM user_sequences")
return [name.lower() for (name,) in cursor.fetchall()]
def list_packages(self, cursor):
"""List installed stored procedure packages.
Returns {package name: version}. version may be None.
"""
stmt = """
SELECT object_name
FROM user_objects
WHERE object_type = 'PACKAGE'
"""
cursor.execute(stmt)
names = [name for (name,) in cursor.fetchall()]
res = {}
for name in names:
version = None
stmt = """
SELECT TEXT FROM USER_SOURCE
WHERE TYPE='PACKAGE BODY'
AND NAME=:1
"""
cursor.execute(stmt, (name,))
for (text,) in cursor:
match = re.search(r'Version:\s*([0-9a-zA-Z.]+)', text)
if match is not None:
version = match.group(1)
break
res[name.lower()] = version
return res
def _create_commit_lock(self, cursor):
return
def _create_pack_lock(self, cursor):
stmt = "CREATE TABLE pack_lock (dummy CHAR);"
self.runner.run_script(cursor, stmt)
def _create_transaction(self, cursor):
if self.keep_history:
stmt = """
CREATE TABLE transaction (
tid NUMBER(20) NOT NULL PRIMARY KEY,
packed CHAR DEFAULT 'N' CHECK (packed IN ('N', 'Y')),
empty CHAR DEFAULT 'N' CHECK (empty IN ('N', 'Y')),
username RAW(500),
description RAW(2000),
extension RAW(2000)
);
"""
self.runner.run_script(cursor, stmt)
def _create_new_oid(self, cursor):
stmt = """
CREATE SEQUENCE zoid_seq;
"""
self.runner.run_script(cursor, stmt)
def _create_object_state(self, cursor):
if self.keep_history:
stmt = """
CREATE TABLE object_state (
zoid NUMBER(20) NOT NULL,
tid NUMBER(20) NOT NULL REFERENCES transaction,
PRIMARY KEY (zoid, tid),
CONSTRAINT tid_min CHECK (tid > 0),
prev_tid NUMBER(20) NOT NULL REFERENCES transaction,
md5 CHAR(32),
state_size NUMBER(20) NOT NULL,
CONSTRAINT state_size_min CHECK (state_size >= 0),
state BLOB
);
CREATE INDEX object_state_tid ON object_state (tid);
CREATE INDEX object_state_prev_tid ON object_state (prev_tid);
"""
else:
stmt = """
CREATE TABLE object_state (
zoid NUMBER(20) NOT NULL PRIMARY KEY,
tid NUMBER(20) NOT NULL,
CONSTRAINT tid_min CHECK (tid > 0),
state_size NUMBER(20) NOT NULL,
CONSTRAINT state_size_min CHECK (state_size >= 0),
state BLOB
);
CREATE INDEX object_state_tid ON object_state (tid);
"""
self.runner.run_script(cursor, stmt)
def _create_blob_chunk(self, cursor):
if self.keep_history:
stmt = """
CREATE TABLE blob_chunk (
zoid NUMBER(20) NOT NULL,
tid NUMBER(20) NOT NULL,
chunk_num NUMBER(20) NOT NULL,
PRIMARY KEY (zoid, tid, chunk_num),
chunk BLOB
);
CREATE INDEX blob_chunk_lookup ON blob_chunk (zoid, tid);
ALTER TABLE blob_chunk ADD CONSTRAINT blob_chunk_fk
FOREIGN KEY (zoid, tid)
REFERENCES object_state (zoid, tid)
ON DELETE CASCADE;
"""
else:
stmt = """
CREATE TABLE blob_chunk (
zoid NUMBER(20) NOT NULL,
chunk_num NUMBER(20) NOT NULL,
PRIMARY KEY (zoid, chunk_num),
tid NUMBER(20) NOT NULL,
chunk BLOB
);
CREATE INDEX blob_chunk_lookup ON blob_chunk (zoid);
ALTER TABLE blob_chunk ADD CONSTRAINT blob_chunk_fk
FOREIGN KEY (zoid)
REFERENCES object_state (zoid)
ON DELETE CASCADE;
"""
self.runner.run_script(cursor, stmt)
def _create_current_object(self, cursor):
if self.keep_history:
stmt = """
CREATE TABLE current_object (
zoid NUMBER(20) NOT NULL PRIMARY KEY,
tid NUMBER(20) NOT NULL,
FOREIGN KEY (zoid, tid)
REFERENCES object_state (zoid, tid)
);
CREATE INDEX current_object_tid ON current_object (tid);
"""
self.runner.run_script(cursor, stmt)
def _create_object_ref(self, cursor):
if self.keep_history:
stmt = """
CREATE TABLE object_ref (
zoid NUMBER(20) NOT NULL,
tid NUMBER(20) NOT NULL,
to_zoid NUMBER(20) NOT NULL,
PRIMARY KEY (tid, zoid, to_zoid)
);
"""
else:
stmt = """
CREATE TABLE object_ref (
zoid NUMBER(20) NOT NULL,
to_zoid NUMBER(20) NOT NULL,
tid NUMBER(20) NOT NULL,
PRIMARY KEY (zoid, to_zoid)
);
"""
self.runner.run_script(cursor, stmt)
def _create_object_refs_added(self, cursor):
if self.keep_history:
stmt = """
CREATE TABLE object_refs_added (
tid NUMBER(20) NOT NULL PRIMARY KEY
);
"""
else:
stmt = """
CREATE TABLE object_refs_added (
zoid NUMBER(20) NOT NULL PRIMARY KEY,
tid NUMBER(20) NOT NULL
);
"""
self.runner.run_script(cursor, stmt)
def _create_pack_object(self, cursor):
stmt = """
CREATE TABLE pack_object (
zoid NUMBER(20) NOT NULL PRIMARY KEY,
keep CHAR NOT NULL CHECK (keep IN ('N', 'Y')),
keep_tid NUMBER(20) NOT NULL,
visited CHAR DEFAULT 'N' NOT NULL CHECK (visited IN ('N', 'Y'))
);
CREATE INDEX pack_object_keep_zoid ON pack_object (keep, zoid);
"""
self.runner.run_script(cursor, stmt)
def _create_pack_state(self, cursor):
if self.keep_history:
stmt = """
CREATE TABLE pack_state (
tid NUMBER(20) NOT NULL,
zoid NUMBER(20) NOT NULL,
PRIMARY KEY (tid, zoid)
);
"""
self.runner.run_script(cursor, stmt)
def _create_pack_state_tid(self, cursor):
if self.keep_history:
stmt = """
CREATE TABLE pack_state_tid (
tid NUMBER(20) NOT NULL PRIMARY KEY
);
"""
self.runner.run_script(cursor, stmt)
def _reset_oid(self, cursor):
stmt = """
DROP SEQUENCE zoid_seq;
CREATE SEQUENCE zoid_seq;
"""
self.runner.run_script(cursor, stmt)
def _create_temp_store(self, cursor):
stmt = """
CREATE GLOBAL TEMPORARY TABLE temp_store (
zoid NUMBER(20) NOT NULL PRIMARY KEY,
prev_tid NUMBER(20) NOT NULL,
md5 CHAR(32),
state BLOB,
blobdata BLOB
) ON COMMIT DELETE ROWS;
"""
self.runner.run_script(cursor, stmt)
def _create_temp_blob_chunk(self, cursor):
stmt = """
CREATE GLOBAL TEMPORARY TABLE temp_blob_chunk (
zoid NUMBER(20) NOT NULL,
chunk_num NUMBER(20) NOT NULL,
chunk BLOB
) ON COMMIT DELETE ROWS;
"""
self.runner.run_script(cursor, stmt)
def _create_temp_pack_visit(self, cursor):
stmt = """
CREATE GLOBAL TEMPORARY TABLE temp_pack_visit (
zoid NUMBER(20) NOT NULL PRIMARY KEY,
keep_tid NUMBER(20) NOT NULL
);
"""
self.runner.run_script(cursor, stmt)
def _create_temp_undo(self, cursor):
if self.keep_history:
stmt = """
CREATE GLOBAL TEMPORARY TABLE temp_undo (
zoid NUMBER(20) NOT NULL PRIMARY KEY,
prev_tid NUMBER(20) NOT NULL
);
"""
self.runner.run_script(cursor, stmt)
def _init_after_create(self, cursor):
if self.keep_history:
stmt = """
INSERT INTO transaction (tid, username, description)
VALUES (0,
UTL_I18N.STRING_TO_RAW('system', 'US7ASCII'),
UTL_I18N.STRING_TO_RAW(
'special transaction for object creation', 'US7ASCII'));
"""
self.runner.run_script(cursor, stmt)