Make DB utils more general

This commit is contained in:
flan 2017-11-01 04:04:48 +01:00
parent 55bdbfacaa
commit ce3aa4a685
4 changed files with 98 additions and 115 deletions

View File

@ -0,0 +1 @@
import db_utils

View File

@ -3,38 +3,23 @@ import os
import sqlite3 import sqlite3
import subprocess import subprocess
from helpers.file_utils import FileUtils
def from_sql(path, sql):
class DBUtils(object):
"""Provides methods for creating and comparing sqlite databases."""
def __init__(self):
self.fileutils = FileUtils()
def clean_up(self):
self.fileutils.clean_up()
def create_sqlite_db_with_sql(self, sql_string):
""" """
Creates an SQLite db and executes the passed sql statements on it. Creates an SQLite db and executes the passed sql statements on it.
:param sql_string: the sql statements to execute on the newly created :param path: the path to the created db file
db :param sql: the sql statements to execute on the newly created db
:return: the path to the created db file
""" """
db_path = self.fileutils.create_file_path(suffix=".anki2") connection = sqlite3.connect(path)
connection = sqlite3.connect(db_path)
cursor = connection.cursor() cursor = connection.cursor()
cursor.executescript(sql_string) cursor.executescript(sql)
connection.commit() connection.commit()
connection.close() connection.close()
return db_path
@staticmethod def to_sql(database):
def sqlite_db_to_sql_string(database):
""" """
Returns a string containing the sql export of the database. Used for Returns a string containing the sql export of the database. Used for
debugging. debugging.
@ -56,44 +41,8 @@ class DBUtils(object):
return res return res
def media_dbs_differ(self, left_db_path, right_db_path, compare_timestamps=False):
"""
Compares two media sqlite database files for equality. mtime and dirMod
timestamps are not considered when comparing.
:param left_db_path: path to the left db file def diff(left_db_path, right_db_path):
:param right_db_path: path to the right db file
:param compare_timestamps: flag determining if timestamp values
(media.mtime and meta.dirMod) are included
in the comparison
:return: True if the specified databases differ, False else
"""
if not os.path.isfile(left_db_path):
raise IOError("file '" + left_db_path + "' does not exist")
elif not os.path.isfile(right_db_path):
raise IOError("file '" + right_db_path + "' does not exist")
# Create temporary copies of the files to act on.
left_db_path = self.fileutils.create_file_copy(left_db_path)
right_db_path = self.fileutils.create_file_copy(right_db_path)
if not compare_timestamps:
# Set all timestamps that are not NULL to 0.
for dbPath in [left_db_path, right_db_path]:
connection = sqlite3.connect(dbPath)
connection.execute("""UPDATE media SET mtime=0
WHERE mtime IS NOT NULL""")
connection.execute("""UPDATE meta SET dirMod=0
WHERE rowid=1""")
connection.commit()
connection.close()
return self.__sqlite_dbs_differ(left_db_path, right_db_path)
def __sqlite_dbs_differ(self, left_db_path, right_db_path):
""" """
Uses the sqldiff cli tool to compare two sqlite files for equality. Uses the sqldiff cli tool to compare two sqlite files for equality.
Returns True if the databases differ, False if they don't. Returns True if the databases differ, False if they don't.
@ -105,7 +54,6 @@ class DBUtils(object):
command = ["/bin/sqldiff", left_db_path, right_db_path] command = ["/bin/sqldiff", left_db_path, right_db_path]
try:
child_process = subprocess.Popen(command, child_process = subprocess.Popen(command,
shell=False, shell=False,
stdout=subprocess.PIPE) stdout=subprocess.PIPE)
@ -121,5 +69,3 @@ class DBUtils(object):
# Any output from sqldiff means the databases differ. # Any output from sqldiff means the databases differ.
return stdout != "" return stdout != ""
except OSError as err:
raise err

View File

@ -5,7 +5,6 @@ from webtest import TestApp
from ankisyncd.users import SqliteUserManager from ankisyncd.users import SqliteUserManager
from helpers.collection_utils import CollectionUtils from helpers.collection_utils import CollectionUtils
from helpers.db_utils import DBUtils
from helpers.file_utils import FileUtils from helpers.file_utils import FileUtils
from helpers.mock_servers import MockRemoteServer from helpers.mock_servers import MockRemoteServer
from helpers.monkey_patches import monkeypatch_db, unpatch_db from helpers.monkey_patches import monkeypatch_db, unpatch_db
@ -19,7 +18,6 @@ class SyncAppFunctionalTestBase(unittest.TestCase):
cls.fileutils = FileUtils() cls.fileutils = FileUtils()
cls.colutils = CollectionUtils() cls.colutils = CollectionUtils()
cls.serverutils = ServerUtils() cls.serverutils = ServerUtils()
cls.dbutils = DBUtils()
@classmethod @classmethod
def tearDownClass(cls): def tearDownClass(cls):
@ -32,9 +30,6 @@ class SyncAppFunctionalTestBase(unittest.TestCase):
cls.serverutils.clean_up() cls.serverutils.clean_up()
cls.serverutils = None cls.serverutils = None
cls.dbutils.clean_up()
cls.dbutils = None
def setUp(self): def setUp(self):
monkeypatch_db() monkeypatch_db()

View File

@ -1,7 +1,10 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import tempfile
import filecmp import filecmp
import sqlite3
import os import os
import helpers.db_utils
from anki.sync import MediaSyncer from anki.sync import MediaSyncer
from helpers.mock_servers import MockRemoteMediaServer from helpers.mock_servers import MockRemoteMediaServer
from helpers.monkey_patches import monkeypatch_mediamanager, unpatch_mediamanager from helpers.monkey_patches import monkeypatch_mediamanager, unpatch_mediamanager
@ -34,6 +37,43 @@ class SyncAppFunctionalMediaTest(SyncAppFunctionalTestBase):
server=mock_remote_server) server=mock_remote_server)
return media_syncer return media_syncer
def media_dbs_differ(self, left_db_path, right_db_path, compare_timestamps=False):
"""
Compares two media sqlite database files for equality. mtime and dirMod
timestamps are not considered when comparing.
:param left_db_path: path to the left db file
:param right_db_path: path to the right db file
:param compare_timestamps: flag determining if timestamp values
(media.mtime and meta.dirMod) are included
in the comparison
:return: True if the specified databases differ, False else
"""
if not os.path.isfile(right_db_path):
raise IOError("file '" + left_db_path + "' does not exist")
elif not os.path.isfile(right_db_path):
raise IOError("file '" + right_db_path + "' does not exist")
# Create temporary copies of the files to act on.
left_db_path = self.fileutils.create_file_copy(left_db_path)
right_db_path = self.fileutils.create_file_copy(right_db_path)
if not compare_timestamps:
# Set all timestamps that are not NULL to 0.
for dbPath in [left_db_path, right_db_path]:
connection = sqlite3.connect(dbPath)
connection.execute("""UPDATE media SET mtime=0
WHERE mtime IS NOT NULL""")
connection.execute("""UPDATE meta SET dirMod=0
WHERE rowid=1""")
connection.commit()
connection.close()
return helpers.db_utils.diff(left_db_path, right_db_path)
def test_sync_empty_media_dbs(self): def test_sync_empty_media_dbs(self):
# With both the client and the server having no media to sync, # With both the client and the server having no media to sync,
# syncing should change nothing. # syncing should change nothing.
@ -105,8 +145,7 @@ class SyncAppFunctionalMediaTest(SyncAppFunctionalTestBase):
# Except for timestamps, the media databases of client and server # Except for timestamps, the media databases of client and server
# should be identical. # should be identical.
self.assertFalse( self.assertFalse(
self.dbutils.media_dbs_differ(client.col.media.db._path, self.media_dbs_differ(client.col.media.db._path, server.col.media.db._path)
server.col.media.db._path)
) )
def test_sync_different_files(self): def test_sync_different_files(self):
@ -301,11 +340,13 @@ class SyncAppFunctionalMediaTest(SyncAppFunctionalTestBase):
CREATE INDEX idx_media_dirty on media (dirty); CREATE INDEX idx_media_dirty on media (dirty);
""" % chksum) """ % chksum)
temp_db_path = self.dbutils.create_sqlite_db_with_sql(sql) _, dbpath = tempfile.mkstemp(suffix=".anki2")
helpers.db_utils.from_sql(dbpath, sql)
# Except for timestamps, the client's db after sync should be identical # Except for timestamps, the client's db after sync should be identical
# to the expected data. # to the expected data.
self.assertFalse(self.dbutils.media_dbs_differ( self.assertFalse(self.media_dbs_differ(
client.col.media.db._path, client.col.media.db._path,
temp_db_path dbpath
)) ))
os.unlink(dbpath)