Выполнение некоторого кода SQL в неудачной транзакции

В моем приложении CherryPy + Peewee я часто использую следующий шаблон: запускаю транзакцию, выполняю список операций и возвращаю страницу, показывающую результат. Если во время какой-либо операции что-то пойдет не так, я добавляю строку в таблицу регистрации, а затем перенаправляюсь на страницу, которая показывает эту строку.

Проблема в том, что перенаправление в CherryPy выполняется путем возбуждения исключения, а исключение вызывает откат нескольких транзакций. Откат — это то, что мне нужно для неудачной операции (и всех предыдущих операций, даже если они были успешными), но это не то, что я хочу для ведения журнала.

Например, со следующим кодом, если пользователь переходит на a_page?a=1&b=2&c=3:

  • do_this найдет x != y и не выполнит show_message
  • do_this обновит одну запись Table1
  • do_that найдет x == y и выполнит show_message
  • show_message добавит одну строку в таблицу журнала Message
  • show_message вызовет исключение, чтобы перенаправить на страницу, которая покажет только что зарегистрированное сообщение

Поскольку во время транзакции возникло исключение, обновление Table1, сделанное в do_this, и сообщение, зарегистрированное в show_message, будут отменены.

Как зафиксировать строки в таблице регистрации и отменить все остальные изменения?

@cherrypy.expose
def a_page(self, a, b, c):
    with db.transaction():
        self.do_this(a, b)
        self.do_that(b, c)
    return render('it_worked.html')

def do_this(self, x, y):
    if x == y:
        self.show_message('Wrong this')
    Table1.update(f2=x).where(f1 == y).execute()

def do_that(self, x, y):
    if x != y:
        self.show_message('Wrong that')
    Table1.update(f3=x).where(f1 == z).execute()

def show_message(self, message)
    msg = Message.create(msg=message)
    raise cherrypy.HTTPRedirect('show_message?id={}'.format(msg.id))

person stenci    schedule 15.09.2016    source источник
comment
Вероятно, вложенные транзакции, т.е. точки сохранения.   -  person CL.    schedule 15.09.2016
comment
@КЛ. Насколько я знаю, вложенные транзакции позволяют не сохранять внутренние операции. Мне нужно наоборот: я не хочу сохранять выполненные до сих пор транзакции, а хочу сохранить самую последнюю.   -  person stenci    schedule 16.09.2016
comment
Я нашел решение, которое мне не нравится, потому что (1) оно требует передачи транзакции в функцию и (2) не будет работать, если есть вложенные транзакции. Это в моем ответе ниже. Пожалуйста, дайте мне знать, если это хорошее решение, или я могу сделать лучше   -  person stenci    schedule 16.09.2016
comment
Вам придется писать журнал после отката к точке сохранения.   -  person CL.    schedule 16.09.2016
comment
@КЛ. Это то, что я сделал в примере ниже. Log.show_log() нужно знать самую внешнюю транзакцию, чтобы откатить ее, и я пока не знаю, нравится ли мне это.   -  person stenci    schedule 16.09.2016


Ответы (1)


Кажется, это решение работает, но мне оно не нравится, потому что оно требует передачи транзакции в функцию и потому что оно может (пока не уверен) не работать с вложенными транзакциями.

import cherrypy
import peewee

db = peewee.SqliteDatabase('test.db', threadlocals=True)

class PeeweeModel(peewee.Model):
    class Meta:
        database = db

class Table1(PeeweeModel):
    field1 = peewee.CharField()

Table1.drop_table(True)
Table1.create_table(True)

class Log(PeeweeModel):
    msg = peewee.CharField()

    @staticmethod
    def show_log(msg, txn):
        txn.rollback()
        msg = Log.create(msg=msg)
        txn.commit()
        raise cherrypy.HTTPRedirect('show_log?msg_id={}'.format(msg.id))

Log.drop_table(True)
Log.create_table(True)

def table_content():
    return '<br>'.join(['{} {}'.format(row.id, row.field1) for row in Table1.select()])

html = """<!DOCTYPE HTML>
<html>
<head>
</head>
<body>
<a href="index">index</a><br>
<a href="add_row">add_row</a><br>
{}<br>
Table1<br>
{}<br>
</body>
</html>"""

class App():

    @cherrypy.expose
    def index(self, msg='Hello'):
        return html.format(msg, table_content())

    @cherrypy.expose
    def add_row(self):
        with db.transaction() as txn:
            Table1.update(field1=Table1.field1 + 1).execute()
            Table1.update(field1=Table1.field1 + 1).where(Table1.id == 2).execute()
            if Table1.select().count() == 5:
                raise Log.show_log('Something went wrong', txn)
            Table1.create(field1=1, field2=2)
        return html.format('Added record to Table1', table_content())

    @cherrypy.expose
    def show_log(self, msg_id):
        msg = Log.get(Log.id == msg_id)
        text = 'Message id {}: {}'.format(msg.id, msg.msg)
        return html.format(text, table_content())

cherrypy.quickstart(App(), '/')
person stenci    schedule 15.09.2016