openlp/openlp/core/lib/db.py

386 lines
14 KiB
Python
Raw Normal View History

2010-05-28 00:26:49 +00:00
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
2010-12-26 11:04:47 +00:00
# Copyright (c) 2008-2011 Raoul Snyman #
2011-05-26 16:25:54 +00:00
# Portions copyright (c) 2008-2011 Tim Bentley, Gerald Britton, Jonathan #
# Corwin, Michael Gorven, Scott Guerrieri, Matthias Hub, Meinert Jordan, #
2011-05-26 17:11:22 +00:00
# Armin Köhler, Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias #
2011-06-12 16:02:52 +00:00
# Põldaru, Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, #
2011-06-12 15:41:01 +00:00
# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Frode Woldsund #
2010-05-28 00:26:49 +00:00
# --------------------------------------------------------------------------- #
# This program is free software; you can redistribute it and/or modify it #
# under the terms of the GNU General Public License as published by the Free #
# Software Foundation; version 2 of the License. #
# #
# This program is distributed in the hope that it will be useful, but WITHOUT #
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
# more details. #
# #
# You should have received a copy of the GNU General Public License along #
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
"""
The :mod:`db` module provides the core database functionality for OpenLP
"""
2010-06-12 23:00:14 +00:00
import logging
2010-06-18 01:26:01 +00:00
import os
2011-11-08 06:22:35 +00:00
from urllib import quote_plus as urlquote
2010-06-12 23:00:14 +00:00
from PyQt4 import QtCore
2011-08-25 09:02:59 +00:00
from sqlalchemy import Table, MetaData, Column, types, create_engine
2011-08-25 11:47:07 +00:00
from sqlalchemy.exc import SQLAlchemyError, InvalidRequestError, DBAPIError
2011-08-25 09:02:59 +00:00
from sqlalchemy.orm import scoped_session, sessionmaker, mapper
2011-05-27 07:54:49 +00:00
from sqlalchemy.pool import NullPool
from openlp.core.lib import translate
from openlp.core.lib.ui import critical_error_message_box
2011-01-14 18:58:47 +00:00
from openlp.core.utils import AppLocation, delete_file
2010-06-12 23:00:14 +00:00
log = logging.getLogger(__name__)
def init_db(url, auto_flush=True, auto_commit=False):
"""
Initialise and return the session and metadata for a database
``url``
The database to initialise connection with
``auto_flush``
Sets the flushing behaviour of the session
``auto_commit``
Sets the commit behaviour of the session
"""
2011-05-27 07:54:49 +00:00
engine = create_engine(url, poolclass=NullPool)
metadata = MetaData(bind=engine)
session = scoped_session(sessionmaker(autoflush=auto_flush,
autocommit=auto_commit, bind=engine))
return session, metadata
2010-05-28 00:26:49 +00:00
2011-08-25 09:02:59 +00:00
def upgrade_db(url, upgrade):
"""
Upgrade a database.
``url``
The url of the database to upgrade.
``upgrade``
The python module that contains the upgrade instructions.
"""
session, metadata = init_db(url)
2011-08-26 19:09:25 +00:00
class Metadata(BaseModel):
"""
Provides a class for the metadata table.
"""
pass
2011-08-27 18:43:05 +00:00
load_changes = True
try:
tables = upgrade.upgrade_setup(metadata)
except (SQLAlchemyError, DBAPIError):
2011-08-27 18:43:05 +00:00
load_changes = False
2011-08-25 09:02:59 +00:00
metadata_table = Table(u'metadata', metadata,
Column(u'key', types.Unicode(64), primary_key=True),
Column(u'value', types.UnicodeText(), default=None)
)
metadata_table.create(checkfirst=True)
mapper(Metadata, metadata_table)
version_meta = session.query(Metadata).get(u'version')
if version_meta is None:
version_meta = Metadata.populate(key=u'version', value=u'0')
version = 0
else:
version = int(version_meta.value)
if version > upgrade.__version__:
return version, upgrade.__version__
2011-08-25 09:02:59 +00:00
version += 1
2011-08-27 18:43:05 +00:00
if load_changes:
while hasattr(upgrade, u'upgrade_%d' % version):
log.debug(u'Running upgrade_%d', version)
try:
getattr(upgrade, u'upgrade_%d' % version) \
(session, metadata, tables)
version_meta.value = unicode(version)
except (SQLAlchemyError, DBAPIError):
2011-08-27 18:43:05 +00:00
log.exception(u'Could not run database upgrade script '
'"upgrade_%s", upgrade process has been halted.', version)
break
version += 1
else:
version_meta = Metadata.populate(key=u'version',
value=int(upgrade.__version__))
2011-08-25 09:02:59 +00:00
session.add(version_meta)
session.commit()
return int(version_meta.value), upgrade.__version__
2011-08-25 09:02:59 +00:00
2011-08-26 19:09:25 +00:00
2010-06-18 01:26:01 +00:00
def delete_database(plugin_name, db_file_name=None):
"""
Remove a database file from the system.
``plugin_name``
The name of the plugin to remove the database for
``db_file_name``
2011-02-25 17:05:01 +00:00
The database file name. Defaults to None resulting in the
2010-06-18 01:26:01 +00:00
plugin_name being used.
"""
db_file_path = None
if db_file_name:
db_file_path = os.path.join(
AppLocation.get_section_data_path(plugin_name), db_file_name)
else:
db_file_path = os.path.join(
AppLocation.get_section_data_path(plugin_name), plugin_name)
2011-01-14 18:58:47 +00:00
return delete_file(db_file_path)
2010-06-18 01:26:01 +00:00
2011-08-25 09:02:59 +00:00
2010-05-28 00:26:49 +00:00
class BaseModel(object):
"""
BaseModel provides a base object with a set of generic functions
"""
@classmethod
def populate(cls, **kwargs):
"""
Creates an instance of a class and populates it, returning the instance
"""
2011-02-07 22:07:48 +00:00
instance = cls()
2011-05-07 14:14:34 +00:00
for key, value in kwargs.iteritems():
instance.__setattr__(key, value)
2011-02-07 22:07:48 +00:00
return instance
2010-06-12 23:00:14 +00:00
class Manager(object):
"""
Provide generic object persistence management
"""
def __init__(self, plugin_name, init_schema, db_file_name=None,
upgrade_mod=None):
2010-06-12 23:00:14 +00:00
"""
Runs the initialisation process that includes creating the connection
to the database and the tables if they don't exist.
``plugin_name``
The name to setup paths and settings section names
2010-06-15 00:44:06 +00:00
``init_schema``
The init_schema function for this database
2011-08-25 09:02:59 +00:00
``upgrade_schema``
The upgrade_schema function for this database
2010-06-15 00:44:06 +00:00
``db_file_name``
2011-02-25 17:05:01 +00:00
The file name to use for this database. Defaults to None resulting
2010-06-15 00:44:06 +00:00
in the plugin_name being used.
2010-06-12 23:00:14 +00:00
"""
settings = QtCore.QSettings()
settings.beginGroup(plugin_name)
self.db_url = u''
2010-10-28 17:02:28 +00:00
self.is_dirty = False
2010-06-12 23:00:14 +00:00
db_type = unicode(
settings.value(u'db type', QtCore.QVariant(u'sqlite')).toString())
if db_type == u'sqlite':
2010-06-15 00:44:06 +00:00
if db_file_name:
self.db_url = u'sqlite:///%s/%s' % (
AppLocation.get_section_data_path(plugin_name),
db_file_name)
else:
self.db_url = u'sqlite:///%s/%s.sqlite' % (
AppLocation.get_section_data_path(plugin_name), plugin_name)
2010-06-12 23:00:14 +00:00
else:
self.db_url = u'%s://%s:%s@%s/%s' % (db_type,
2011-11-08 06:22:35 +00:00
urlquote(unicode(settings.value(u'db username').toString())),
urlquote(unicode(settings.value(u'db password').toString())),
urlquote(unicode(settings.value(u'db hostname').toString())),
urlquote(unicode(settings.value(u'db database').toString())))
2010-06-12 23:00:14 +00:00
settings.endGroup()
if upgrade_mod:
db_ver, up_ver = upgrade_db(self.db_url, upgrade_mod)
if db_ver > up_ver:
critical_error_message_box(
translate('OpenLP.Manager', 'Database Error'),
unicode(translate('OpenLP.Manager', 'The database being '
'loaded was created in a more recent version of '
'OpenLP. The database is version %d, while OpenLP '
'expects version %d. The database will not be loaded.'
'\n\nDatabase: %s')) % \
(db_ver, up_ver, self.db_url)
)
return
try:
self.session = init_schema(self.db_url)
except (SQLAlchemyError, DBAPIError):
log.exception(u'Error loading database: %s', self.db_url)
critical_error_message_box(
translate('OpenLP.Manager', 'Database Error'),
unicode(translate('OpenLP.Manager', 'OpenLP cannot load your '
'database.\n\nDatabase: %s')) % self.db_url
)
2010-06-12 23:00:14 +00:00
2010-07-20 15:22:05 +00:00
def save_object(self, object_instance, commit=True):
2010-06-12 23:00:14 +00:00
"""
Save an object to the database
``object_instance``
The object to save
2010-07-20 15:22:05 +00:00
``commit``
Commit the session with this object
2010-06-12 23:00:14 +00:00
"""
try:
self.session.add(object_instance)
2010-07-20 15:22:05 +00:00
if commit:
self.session.commit()
2010-10-28 17:02:28 +00:00
self.is_dirty = True
2010-06-12 23:00:14 +00:00
return True
except InvalidRequestError:
self.session.rollback()
log.exception(u'Object save failed')
return False
2010-11-16 06:19:23 +00:00
def save_objects(self, object_list, commit=True):
"""
Save a list of objects to the database
``object_list``
The list of objects to save
``commit``
Commit the session with this object
"""
try:
self.session.add_all(object_list)
if commit:
self.session.commit()
self.is_dirty = True
return True
except InvalidRequestError:
self.session.rollback()
log.exception(u'Object list save failed')
return False
2010-06-18 01:26:01 +00:00
def get_object(self, object_class, key=None):
2010-06-12 23:00:14 +00:00
"""
Return the details of an object
``object_class``
The type of object to return
2010-06-18 01:26:01 +00:00
``key``
The unique reference or primary key for the instance to return
2010-06-12 23:00:14 +00:00
"""
2010-06-18 01:26:01 +00:00
if not key:
2010-06-12 23:00:14 +00:00
return object_class()
else:
2010-06-18 01:26:01 +00:00
return self.session.query(object_class).get(key)
2010-06-12 23:00:14 +00:00
2010-06-30 22:05:51 +00:00
def get_object_filtered(self, object_class, filter_clause):
2010-06-15 18:08:02 +00:00
"""
Returns an object matching specified criteria
``object_class``
The type of object to return
2010-06-30 22:05:51 +00:00
``filter_clause``
2010-06-15 18:08:02 +00:00
The criteria to select the object by
"""
2010-06-30 22:05:51 +00:00
return self.session.query(object_class).filter(filter_clause).first()
2010-06-15 18:08:02 +00:00
2010-07-18 23:37:24 +00:00
def get_all_objects(self, object_class, filter_clause=None,
2010-06-30 22:05:51 +00:00
order_by_ref=None):
2010-06-15 02:08:22 +00:00
"""
2010-07-18 23:37:24 +00:00
Returns all the objects from the database
2010-06-15 02:08:22 +00:00
``object_class``
The type of objects to return
2010-06-30 22:05:51 +00:00
``filter_clause``
2011-02-25 17:05:01 +00:00
The filter governing selection of objects to return. Defaults to
2010-07-18 23:37:24 +00:00
None.
2010-06-30 22:05:51 +00:00
``order_by_ref``
2011-02-25 17:05:01 +00:00
Any parameters to order the returned objects by. Defaults to None.
2010-06-15 02:08:22 +00:00
"""
2010-07-18 23:37:24 +00:00
query = self.session.query(object_class)
2010-08-02 02:53:14 +00:00
if filter_clause is not None:
2010-07-18 23:37:24 +00:00
query = query.filter(filter_clause)
if isinstance(order_by_ref, list):
return query.order_by(*order_by_ref).all()
elif order_by_ref is not None:
2010-07-01 10:31:37 +00:00
return query.order_by(order_by_ref).all()
return query.all()
2010-06-15 02:08:22 +00:00
2010-11-16 06:19:23 +00:00
def get_object_count(self, object_class, filter_clause=None):
"""
Returns a count of the number of objects in the database.
``object_class``
The type of objects to return.
``filter_clause``
2011-02-25 17:05:01 +00:00
The filter governing selection of objects to return. Defaults to
2010-11-16 06:19:23 +00:00
None.
"""
query = self.session.query(object_class)
if filter_clause is not None:
query = query.filter(filter_clause)
return query.count()
2010-06-18 01:26:01 +00:00
def delete_object(self, object_class, key):
2010-06-12 23:00:14 +00:00
"""
Delete an object from the database
``object_class``
The type of object to delete
2010-06-18 01:26:01 +00:00
``key``
The unique reference or primary key for the instance to be deleted
2010-06-12 23:00:14 +00:00
"""
2010-06-18 01:26:01 +00:00
if key != 0:
object_instance = self.get_object(object_class, key)
2010-06-12 23:00:14 +00:00
try:
2010-06-18 01:26:01 +00:00
self.session.delete(object_instance)
2010-06-12 23:00:14 +00:00
self.session.commit()
2010-10-28 17:02:28 +00:00
self.is_dirty = True
2010-06-12 23:00:14 +00:00
return True
except InvalidRequestError:
self.session.rollback()
log.exception(u'Failed to delete object')
return False
else:
return True
2010-07-18 23:37:24 +00:00
def delete_all_objects(self, object_class, filter_clause=None):
2010-06-12 23:00:14 +00:00
"""
Delete all object records
``object_class``
The type of object to delete
"""
try:
2010-07-18 23:37:24 +00:00
query = self.session.query(object_class)
2010-08-02 02:53:14 +00:00
if filter_clause is not None:
2010-07-18 23:37:24 +00:00
query = query.filter(filter_clause)
query.delete(synchronize_session=False)
2010-06-12 23:00:14 +00:00
self.session.commit()
2010-10-28 17:02:28 +00:00
self.is_dirty = True
2010-06-12 23:00:14 +00:00
return True
except InvalidRequestError:
self.session.rollback()
2010-07-18 23:37:24 +00:00
log.exception(u'Failed to delete %s records', object_class.__name__)
2010-07-27 09:32:52 +00:00
return False
def finalise(self):
"""
VACUUM the database on exit.
"""
2010-10-28 17:02:28 +00:00
if self.is_dirty:
engine = create_engine(self.db_url)
2010-11-28 15:20:53 +00:00
if self.db_url.startswith(u'sqlite'):
2011-01-14 18:58:47 +00:00
engine.execute("vacuum")