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
This commit is contained in:
Wessie 2013-12-17 21:55:05 +01:00
parent bed11f3de7
commit 6f4cadafbb
2 changed files with 36 additions and 27 deletions

View file

@ -33,15 +33,22 @@ class Cursor(object):
conn = self._cache.get_nowait() conn = self._cache.get_nowait()
except Queue.Empty: except Queue.Empty:
conn = mysql.connect(**options) conn = mysql.connect(**options)
else:
# Ping the connection before using it from the cache.
conn.ping(True)
self.conn = conn self.conn = conn
self.cursor_type = cursor_type self.cursor_type = cursor_type
def __enter__(slf): def __enter__(self):
self.cursor = self.conn.cursor(self.cursor_type) self.cursor = self.conn.cursor(self.cursor_type)
return self.cursor 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.cursor.close()
self.conn.commit() self.conn.commit()

View file

@ -1,8 +1,10 @@
from __future__ import absolute_import 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 from MySQLdb.cursors import Cursor
class Database(object): class Database(object):
def __init__(self): def __init__(self):
super(Database, self).__init__() super(Database, self).__init__()
@ -69,11 +71,13 @@ class SQLDatabase(Database):
`%s` mediumint unsigned not null, `%s` mediumint unsigned not null,
`%s` int unsigned not null, `%s` int unsigned not null,
INDEX(%s), 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, FINGERPRINTS_TABLENAME, FIELD_HASH,
FIELD_SONG_ID, FIELD_OFFSET, FIELD_HASH, FIELD_SONG_ID, FIELD_OFFSET, 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 = """ CREATE_SONGS_TABLE = """
@ -83,7 +87,7 @@ class SQLDatabase(Database):
`%s` tinyint default 0, `%s` tinyint default 0,
PRIMARY KEY (`%s`), PRIMARY KEY (`%s`),
UNIQUE KEY `%s` (`%s`) UNIQUE KEY `%s` (`%s`)
);""" % ( ) ENGINE=INNODB;""" % (
SONGS_TABLENAME, FIELD_SONG_ID, FIELD_SONGNAME, FIELD_FINGERPRINTED, SONGS_TABLENAME, FIELD_SONG_ID, FIELD_SONGNAME, FIELD_FINGERPRINTED,
FIELD_SONG_ID, FIELD_SONG_ID, FIELD_SONG_ID, FIELD_SONG_ID, FIELD_SONG_ID, FIELD_SONG_ID,
) )
@ -141,18 +145,17 @@ class SQLDatabase(Database):
DELETE FROM %s WHERE %s = 0; DELETE FROM %s WHERE %s = 0;
""" % (SONGS_TABLENAME, FIELD_FINGERPRINTED) """ % (SONGS_TABLENAME, FIELD_FINGERPRINTED)
DELETE_ORPHANS = """ def __init__(self, **options):
delete from fingerprints
where not exists (
select * from songs where fingerprints.song_id = songs.song_id
);
"""
def __init__(self, cursor):
super(SQLDatabase, self).__init__() super(SQLDatabase, self).__init__()
self.cursor = cursor self.cursor = cursor_factory(**options)
def setup(self): 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: with self.cursor() as cur:
cur.execute(self.CREATE_FINGERPRINTS_TABLE) cur.execute(self.CREATE_FINGERPRINTS_TABLE)
cur.execute(self.CREATE_SONGS_TABLE) cur.execute(self.CREATE_SONGS_TABLE)
@ -160,7 +163,11 @@ class SQLDatabase(Database):
def empty(self): 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: with self.cursor() as cur:
cur.execute(self.DROP_FINGERPRINTS) cur.execute(self.DROP_FINGERPRINTS)
@ -168,16 +175,11 @@ class SQLDatabase(Database):
self.setup() 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): def delete_unfingerprinted_songs(self):
"""
Removes all songs that have no fingerprints associated with them.
"""
with self.cursor() as cur: with self.cursor() as cur:
cur.execute(self.DELETE_UNFINGERPRINTED) cur.execute(self.DELETE_UNFINGERPRINTED)
@ -188,8 +190,9 @@ class SQLDatabase(Database):
with self.cursor() as cur: with self.cursor() as cur:
cur.execute(self.SELECT_UNIQUE_SONG_IDS) cur.execute(self.SELECT_UNIQUE_SONG_IDS)
for row in cur: for count, in cur:
return row['n'] return count
return 0
def get_num_fingerprints(self): def get_num_fingerprints(self):
""" """
@ -304,7 +307,6 @@ class SQLDatabase(Database):
yield (sid, offset - mapper[hash]) yield (sid, offset - mapper[hash])
from itertools import izip_longest
def grouper(iterable, n, fillvalue=None): def grouper(iterable, n, fillvalue=None):
args = [iter(iterable)] * n args = [iter(iterable)] * n
return izip_longest(fillvalue=fillvalue, *args) return izip_longest(fillvalue=fillvalue, *args)