From 6f4cadafbb4780eec6ee78527a38427a968e5be9 Mon Sep 17 00:00:00 2001 From: Wessie Date: Tue, 17 Dec 2013 21:55:05 +0100 Subject: [PATCH] Added a foreign key relationship to the create table statements. - MySQL will also use the InnoDB engine now. - Added a ping call in the MySQL Cursor cache mechanism - Added a rollback call when a MySQLError occurs - Removed 'delete_orphans' which is not needed anymore due to foreign key constraints and delete on cascade - Changed SQLDatabase to accept options to create a cursor factory, instead of taking a pre-created cursor factory --- dejavu/cursor.py | 11 ++++++++-- dejavu/database.py | 52 ++++++++++++++++++++++++---------------------- 2 files changed, 36 insertions(+), 27 deletions(-) diff --git a/dejavu/cursor.py b/dejavu/cursor.py index 07649c6..50146f2 100644 --- a/dejavu/cursor.py +++ b/dejavu/cursor.py @@ -33,15 +33,22 @@ class Cursor(object): conn = self._cache.get_nowait() except Queue.Empty: conn = mysql.connect(**options) + else: + # Ping the connection before using it from the cache. + conn.ping(True) self.conn = conn self.cursor_type = cursor_type - def __enter__(slf): + def __enter__(self): self.cursor = self.conn.cursor(self.cursor_type) return self.cursor - def __exit__(self, type, value, traceback): + def __exit__(self, extype, exvalue, traceback): + # if we had a MySQL related error we try to rollback the cursor. + if extype is mysql.MySQLError: + self.cursor.rollback() + self.cursor.close() self.conn.commit() diff --git a/dejavu/database.py b/dejavu/database.py index a08bfcd..082484a 100755 --- a/dejavu/database.py +++ b/dejavu/database.py @@ -1,8 +1,10 @@ from __future__ import absolute_import -from binascii import unhexlify +from itertools import izip_longest +from dejavu.cursor import cursor_factory from MySQLdb.cursors import Cursor + class Database(object): def __init__(self): super(Database, self).__init__() @@ -69,11 +71,13 @@ class SQLDatabase(Database): `%s` mediumint unsigned not null, `%s` int unsigned not null, INDEX(%s), - UNIQUE(%s, %s, %s) - );""" % ( + UNIQUE(%s, %s, %s), + FOREIGN KEY (%s) REFERENCES %s(%s) ON DELETE CASCADE + ) ENGINE=INNODB;""" % ( FINGERPRINTS_TABLENAME, FIELD_HASH, FIELD_SONG_ID, FIELD_OFFSET, FIELD_HASH, FIELD_SONG_ID, FIELD_OFFSET, FIELD_HASH, + FIELD_SONG_ID, SONGS_TABLENAME, FIELD_SONG_ID ) CREATE_SONGS_TABLE = """ @@ -83,7 +87,7 @@ class SQLDatabase(Database): `%s` tinyint default 0, PRIMARY KEY (`%s`), UNIQUE KEY `%s` (`%s`) - );""" % ( + ) ENGINE=INNODB;""" % ( SONGS_TABLENAME, FIELD_SONG_ID, FIELD_SONGNAME, FIELD_FINGERPRINTED, FIELD_SONG_ID, FIELD_SONG_ID, FIELD_SONG_ID, ) @@ -141,18 +145,17 @@ class SQLDatabase(Database): DELETE FROM %s WHERE %s = 0; """ % (SONGS_TABLENAME, FIELD_FINGERPRINTED) - DELETE_ORPHANS = """ - delete from fingerprints - where not exists ( - select * from songs where fingerprints.song_id = songs.song_id - ); - """ - - def __init__(self, cursor): + def __init__(self, **options): super(SQLDatabase, self).__init__() - self.cursor = cursor + self.cursor = cursor_factory(**options) def setup(self): + """ + Creates any non-existing tables required for dejavu to function. + + This also removes all songs that have been added but have no + fingerprints associated with them. + """ with self.cursor() as cur: cur.execute(self.CREATE_FINGERPRINTS_TABLE) cur.execute(self.CREATE_SONGS_TABLE) @@ -160,7 +163,11 @@ class SQLDatabase(Database): def empty(self): """ - Drops all tables and re-adds them. Be carfeul with this! + Drops tables created by dejavu and then creates them again + by calling `SQLDatabase.setup`. + + .. warning: + This will result in a loss of data """ with self.cursor() as cur: cur.execute(self.DROP_FINGERPRINTS) @@ -168,16 +175,11 @@ class SQLDatabase(Database): self.setup() - def delete_orphans(self): - # TODO: SQLDatabase.DELETE_ORPHANS is not - # performant enough, need better query to - # delete fingerprints for which no song is tied to. - - # with self.cursor() as cur: - # cur.execute(self.DELETE_ORPHANS) - pass def delete_unfingerprinted_songs(self): + """ + Removes all songs that have no fingerprints associated with them. + """ with self.cursor() as cur: cur.execute(self.DELETE_UNFINGERPRINTED) @@ -188,8 +190,9 @@ class SQLDatabase(Database): with self.cursor() as cur: cur.execute(self.SELECT_UNIQUE_SONG_IDS) - for row in cur: - return row['n'] + for count, in cur: + return count + return 0 def get_num_fingerprints(self): """ @@ -304,7 +307,6 @@ class SQLDatabase(Database): yield (sid, offset - mapper[hash]) -from itertools import izip_longest def grouper(iterable, n, fillvalue=None): args = [iter(iterable)] * n return izip_longest(fillvalue=fillvalue, *args)