anki-sync-server/tests/helpers/db_utils.py

126 lines
4.4 KiB
Python
Raw Normal View History

# -*- coding: utf-8 -*-
import os
import sqlite3
import subprocess
from helpers.file_utils import FileUtils
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.
:param sql_string: 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(db_path)
cursor = connection.cursor()
cursor.executescript(sql_string)
connection.commit()
connection.close()
return db_path
@staticmethod
def sqlite_db_to_sql_string(database):
"""
Returns a string containing the sql export of the database. Used for
debugging.
:param database: either the path to the SQLite db file or an open
connection to it
:return: a string representing the sql export of the database
"""
if type(database) == str:
connection = sqlite3.connect(database)
else:
connection = database
res = '\n'.join(connection.iterdump())
if type(database) == str:
connection.close()
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
: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.
Returns True if the databases differ, False if they don't.
:param left_db_path: path to the left db file
:param right_db_path: path to the right db file
:return: True if the specified databases differ, False else
"""
command = ["/bin/sqldiff", left_db_path, right_db_path]
try:
child_process = subprocess.Popen(command,
shell=False,
stdout=subprocess.PIPE)
stdout, stderr = child_process.communicate()
exit_code = child_process.returncode
if exit_code != 0 or stderr is not None:
raise RuntimeError("Command {} encountered an error, exit "
"code: {}, stderr: {}"
.format(" ".join(command),
exit_code,
stderr))
# Any output from sqldiff means the databases differ.
return stdout != ""
except OSError as err:
raise err