mirror of
https://github.com/samsonjs/vdirsyncer.git
synced 2026-04-27 14:57:41 +00:00
Fix transactions once again
This commit is contained in:
parent
07f6c3af12
commit
fbe3f9910d
1 changed files with 59 additions and 52 deletions
|
|
@ -25,6 +25,18 @@ from .utils import uniq
|
||||||
sync_logger = logging.getLogger(__name__)
|
sync_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@contextlib.contextmanager
|
||||||
|
def _exclusive_transaction(conn):
|
||||||
|
try:
|
||||||
|
c = conn.execute('BEGIN EXCLUSIVE TRANSACTION')
|
||||||
|
yield c
|
||||||
|
c.execute('COMMIT')
|
||||||
|
except BaseException:
|
||||||
|
_, e, tb = sys.exc_info()
|
||||||
|
c.execute('ROLLBACK')
|
||||||
|
raise e.with_traceback(tb)
|
||||||
|
|
||||||
|
|
||||||
class SyncError(exceptions.Error):
|
class SyncError(exceptions.Error):
|
||||||
'''Errors related to synchronization.'''
|
'''Errors related to synchronization.'''
|
||||||
|
|
||||||
|
|
@ -208,48 +220,47 @@ class SqliteStatus(_StatusBase):
|
||||||
|
|
||||||
# If we ever bump the schema version, we will need a way to migrate
|
# If we ever bump the schema version, we will need a way to migrate
|
||||||
# data.
|
# data.
|
||||||
|
with _exclusive_transaction(self._c) as c:
|
||||||
|
c.execute('CREATE TABLE meta ( "version" INTEGER PRIMARY KEY )')
|
||||||
|
c.execute('INSERT INTO meta (version) VALUES (?)',
|
||||||
|
(self.SCHEMA_VERSION,))
|
||||||
|
|
||||||
self._c.execute('''CREATE TABLE meta (
|
# I know that this is a bad schema, but right there is just too
|
||||||
"version" INTEGER PRIMARY KEY
|
# little gain in deduplicating the .._a and .._b columns.
|
||||||
); ''')
|
c.execute('''CREATE TABLE status (
|
||||||
self._c.execute('INSERT INTO meta (version) VALUES (?)',
|
"ident" TEXT PRIMARY KEY NOT NULL,
|
||||||
(self.SCHEMA_VERSION,))
|
"href_a" TEXT,
|
||||||
|
"href_b" TEXT,
|
||||||
|
"hash_a" TEXT NOT NULL,
|
||||||
|
"hash_b" TEXT NOT NULL,
|
||||||
|
"etag_a" TEXT,
|
||||||
|
"etag_b" TEXT
|
||||||
|
); ''')
|
||||||
|
c.execute('CREATE UNIQUE INDEX by_href_a ON status(href_a)')
|
||||||
|
c.execute('CREATE UNIQUE INDEX by_href_b ON status(href_b)')
|
||||||
|
|
||||||
# I know that this is a bad schema, but right there is just too little
|
# We cannot add NOT NULL here because data is first fetched for the
|
||||||
# gain in deduplicating the .._a and .._b columns.
|
# storage a, then storage b. Inbetween the `_b`-columns are filled
|
||||||
self._c.execute('''CREATE TABLE status (
|
# with NULL.
|
||||||
"ident" TEXT PRIMARY KEY NOT NULL,
|
#
|
||||||
"href_a" TEXT,
|
# In an ideal world we would be able to start a transaction with
|
||||||
"href_b" TEXT,
|
# one cursor, write our new data into status and simultaneously
|
||||||
"hash_a" TEXT NOT NULL,
|
# query the old status data using a different cursor.
|
||||||
"hash_b" TEXT NOT NULL,
|
# Unfortunately sqlite enforces NOT NULL constraints immediately,
|
||||||
"etag_a" TEXT,
|
# not just at commit. Since there is also no way to alter
|
||||||
"etag_b" TEXT
|
# constraints on a table (disable constraints on start of
|
||||||
); ''')
|
# transaction and reenable on end), it's a separate table now that
|
||||||
self._c.execute('CREATE UNIQUE INDEX by_href_a ON status(href_a)')
|
# just gets copied over before we commit. That's a lot of copying,
|
||||||
self._c.execute('CREATE UNIQUE INDEX by_href_b ON status(href_b)')
|
# sadly.
|
||||||
|
c.execute('''CREATE TABLE new_status (
|
||||||
# We cannot add NOT NULL here because data is first fetched for the
|
"ident" TEXT PRIMARY KEY NOT NULL,
|
||||||
# storage a, then storage b. Inbetween the `_b`-columns are filled with
|
"href_a" TEXT,
|
||||||
# NULL.
|
"href_b" TEXT,
|
||||||
#
|
"hash_a" TEXT,
|
||||||
# In an ideal world we would be able to start a transaction with one
|
"hash_b" TEXT,
|
||||||
# cursor, write our new data into status and simultaneously query the
|
"etag_a" TEXT,
|
||||||
# old status data using a different cursor. Unfortunately sqlite
|
"etag_b" TEXT
|
||||||
# enforces NOT NULL constraints immediately, not just at commit. Since
|
); ''')
|
||||||
# there is also no way to alter constraints on a table (disable
|
|
||||||
# constraints on start of transaction and reenable on end), it's a
|
|
||||||
# separate table now that just gets copied over before we commit.
|
|
||||||
# That's a lot of copying, sadly.
|
|
||||||
self._c.execute('''CREATE TABLE new_status (
|
|
||||||
"ident" TEXT PRIMARY KEY NOT NULL,
|
|
||||||
"href_a" TEXT,
|
|
||||||
"href_b" TEXT,
|
|
||||||
"hash_a" TEXT,
|
|
||||||
"hash_b" TEXT,
|
|
||||||
"etag_a" TEXT,
|
|
||||||
"etag_b" TEXT
|
|
||||||
); ''')
|
|
||||||
|
|
||||||
def _is_latest_version(self):
|
def _is_latest_version(self):
|
||||||
try:
|
try:
|
||||||
|
|
@ -262,19 +273,15 @@ class SqliteStatus(_StatusBase):
|
||||||
|
|
||||||
@contextlib.contextmanager
|
@contextlib.contextmanager
|
||||||
def transaction(self):
|
def transaction(self):
|
||||||
|
old_c = self._c
|
||||||
try:
|
try:
|
||||||
old_c = self._c
|
with _exclusive_transaction(self._c) as new_c:
|
||||||
self._c = self._c.execute('BEGIN EXCLUSIVE TRANSACTION')
|
self._c = new_c
|
||||||
yield
|
yield
|
||||||
self._c.execute('DELETE FROM status')
|
self._c.execute('DELETE FROM status')
|
||||||
self._c.execute('INSERT INTO status '
|
self._c.execute('INSERT INTO status '
|
||||||
'SELECT * FROM new_status')
|
'SELECT * FROM new_status')
|
||||||
self._c.execute('DELETE FROM new_status')
|
self._c.execute('DELETE FROM new_status')
|
||||||
self._c.execute('COMMIT')
|
|
||||||
except BaseException:
|
|
||||||
_, e, tb = sys.exc_info()
|
|
||||||
self._c.execute('ROLLBACK')
|
|
||||||
raise e.with_traceback(tb)
|
|
||||||
finally:
|
finally:
|
||||||
self._c = old_c
|
self._c = old_c
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue