210 lines
6.4 KiB
Python
210 lines
6.4 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.
|
|
#
|
|
##############################################################################
|
|
"""TransactionControl implementations"""
|
|
|
|
from base64 import encodestring
|
|
from relstorage.adapters.interfaces import ITransactionControl
|
|
from zope.interface import implements
|
|
import logging
|
|
import re
|
|
import time
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
|
|
class TransactionControl(object):
|
|
"""Abstract base class"""
|
|
|
|
def commit_phase1(self, conn, cursor, tid):
|
|
"""Begin a commit. Returns the transaction name.
|
|
|
|
The transaction name must not be None.
|
|
|
|
This method should guarantee that commit_phase2() will succeed,
|
|
meaning that if commit_phase2() would raise any error, the error
|
|
should be raised in commit_phase1() instead.
|
|
"""
|
|
return '-'
|
|
|
|
def commit_phase2(self, conn, cursor, txn):
|
|
"""Final transaction commit.
|
|
|
|
txn is the name returned by commit_phase1.
|
|
"""
|
|
conn.commit()
|
|
|
|
def abort(self, conn, cursor, txn=None):
|
|
"""Abort the commit. If txn is not None, phase 1 is also aborted."""
|
|
conn.rollback()
|
|
|
|
|
|
class PostgreSQLTransactionControl(TransactionControl):
|
|
implements(ITransactionControl)
|
|
|
|
def __init__(self, keep_history):
|
|
self.keep_history = keep_history
|
|
|
|
def get_tid(self, cursor):
|
|
"""Returns the most recent tid."""
|
|
if self.keep_history:
|
|
stmt = """
|
|
SELECT tid
|
|
FROM transaction
|
|
ORDER BY tid DESC
|
|
LIMIT 1
|
|
"""
|
|
cursor.execute(stmt)
|
|
else:
|
|
stmt = """
|
|
SELECT tid
|
|
FROM object_state
|
|
ORDER BY tid DESC
|
|
LIMIT 1
|
|
"""
|
|
cursor.execute(stmt)
|
|
if not cursor.rowcount:
|
|
# nothing has been stored yet
|
|
return 0
|
|
|
|
assert cursor.rowcount == 1
|
|
return cursor.fetchone()[0]
|
|
|
|
def add_transaction(self, cursor, tid, username, description, extension,
|
|
packed=False):
|
|
"""Add a transaction."""
|
|
if self.keep_history:
|
|
stmt = """
|
|
INSERT INTO transaction
|
|
(tid, packed, username, description, extension)
|
|
VALUES (%s, %s,
|
|
decode(%s, 'base64'), decode(%s, 'base64'),
|
|
decode(%s, 'base64'))
|
|
"""
|
|
cursor.execute(stmt, (tid, packed,
|
|
encodestring(username), encodestring(description),
|
|
encodestring(extension)))
|
|
|
|
|
|
class MySQLTransactionControl(TransactionControl):
|
|
implements(ITransactionControl)
|
|
|
|
def __init__(self, keep_history, Binary):
|
|
self.keep_history = keep_history
|
|
self.Binary = Binary
|
|
|
|
def get_tid(self, cursor):
|
|
"""Returns the most recent tid."""
|
|
if self.keep_history:
|
|
stmt = """
|
|
SELECT tid
|
|
FROM transaction
|
|
ORDER BY tid DESC
|
|
LIMIT 1
|
|
"""
|
|
cursor.execute(stmt)
|
|
else:
|
|
stmt = """
|
|
SELECT tid
|
|
FROM object_state
|
|
ORDER BY tid DESC
|
|
LIMIT 1
|
|
"""
|
|
cursor.execute(stmt)
|
|
if not cursor.rowcount:
|
|
# nothing has been stored yet
|
|
return 0
|
|
|
|
assert cursor.rowcount == 1
|
|
return cursor.fetchone()[0]
|
|
|
|
def add_transaction(self, cursor, tid, username, description, extension,
|
|
packed=False):
|
|
"""Add a transaction."""
|
|
if self.keep_history:
|
|
stmt = """
|
|
INSERT INTO transaction
|
|
(tid, packed, username, description, extension)
|
|
VALUES (%s, %s, %s, %s, %s)
|
|
"""
|
|
cursor.execute(stmt, (
|
|
tid, packed, self.Binary(username),
|
|
self.Binary(description), self.Binary(extension)))
|
|
|
|
|
|
class OracleTransactionControl(TransactionControl):
|
|
implements(ITransactionControl)
|
|
|
|
def __init__(self, keep_history, Binary, twophase):
|
|
self.keep_history = keep_history
|
|
self.Binary = Binary
|
|
self.twophase = twophase
|
|
|
|
def commit_phase1(self, conn, cursor, tid):
|
|
"""Begin a commit. Returns the transaction name.
|
|
|
|
The transaction name must not be None.
|
|
|
|
This method should guarantee that commit_phase2() will succeed,
|
|
meaning that if commit_phase2() would raise any error, the error
|
|
should be raised in commit_phase1() instead.
|
|
"""
|
|
if self.twophase:
|
|
conn.prepare()
|
|
return '-'
|
|
|
|
def get_tid(self, cursor):
|
|
"""Returns the most recent tid.
|
|
"""
|
|
if self.keep_history:
|
|
stmt = """
|
|
SELECT MAX(tid)
|
|
FROM transaction
|
|
"""
|
|
cursor.execute(stmt)
|
|
rows = list(cursor)
|
|
else:
|
|
stmt = """
|
|
SELECT MAX(tid)
|
|
FROM object_state
|
|
"""
|
|
cursor.execute(stmt)
|
|
rows = list(cursor)
|
|
if not rows:
|
|
# nothing has been stored yet
|
|
return 0
|
|
|
|
assert len(rows) == 1
|
|
tid = rows[0][0]
|
|
if tid is None:
|
|
tid = 0
|
|
return tid
|
|
|
|
def add_transaction(self, cursor, tid, username, description, extension,
|
|
packed=False):
|
|
"""Add a transaction."""
|
|
if self.keep_history:
|
|
stmt = """
|
|
INSERT INTO transaction
|
|
(tid, packed, username, description, extension)
|
|
VALUES (:1, :2, :3, :4, :5)
|
|
"""
|
|
max_desc_len = 2000
|
|
if len(description) > max_desc_len:
|
|
log.warning('Trimming description of transaction %s '
|
|
'to %d characters', tid, max_desc_len)
|
|
description = description[:max_desc_len]
|
|
cursor.execute(stmt, (
|
|
tid, packed and 'Y' or 'N', self.Binary(username),
|
|
self.Binary(description), self.Binary(extension)))
|