예제 #1
0
    def __init__(self, connector=None):
        database.require(DBNAME, version='1')
        self.normalize_basedir()
        connector = BoundConnector(DBNAME, connector)
        self.DBFILENAME = connector.dblocation
        self.conn = connector.connection()
        self.db = self.conn.cursor()

        #I don't care about journaling!
        self.conn.execute('PRAGMA synchronous = OFF')
        self.conn.execute('PRAGMA journal_mode = MEMORY')
        self.load_db_to_memory()
예제 #2
0
    def __init__(self, connector=None):
        database.require(DBNAME, version='1')
        self.normalize_basedir()
        connector = BoundConnector(DBNAME, connector)
        self.DBFILENAME = connector.dblocation
        self.conn = connector.connection()
        self.db = self.conn.cursor()

        #I don't care about journaling!
        self.conn.execute('PRAGMA synchronous = OFF')
        self.conn.execute('PRAGMA journal_mode = MEMORY')
        self.load_db_to_memory()
예제 #3
0
class Updater(object):
    '''Handle the versioning needs of a single database.

    name : str
        The name of the database to manage.
    dbdef : dict
        The corresponding definition.
    connector : :class:`cherrymusicserver.database.connect.AbstractConnector`
        To connect to the database.
    '''

    _metatable = {
        'create.sql': """CREATE TABLE IF NOT EXISTS _meta_version(
                            version TEXT,
                            _created INTEGER NOT NULL DEFAULT (datetime('now'))
                        );""",
        'drop.sql': """DROP TABLE IF EXISTS _meta_version;"""
    }

    _classlock = threading.RLock()
    _dblockers = {}

    def __init__(self, name, dbdef):
        assert name and dbdef
        self.name = name
        self.desc = dbdef
        self.db = BoundConnector(self.name)
        with self:
            self._init_meta()

    def __del__(self):
        self._unlock()

    def __repr__(self):
        return 'updater({0!r}, {1} -> {2})'.format(
            self.name,
            self._version,
            self._target,
        )

    def __enter__(self):
        self._lock()
        return self

    def __exit__(self, exctype, exception, traceback):
        self._unlock()

    @property
    def _islocked(self):
        name, lockers = self.name, self._dblockers
        with self._classlock:
            return name in lockers and lockers[name] is self

    def _lock(self):
        name, lockers = self.name, self._dblockers
        with self._classlock:
            assert lockers.get(
                name, self) is self, (name + ': is locked by another updater')
            lockers[name] = self

    def _unlock(self):
        with self._classlock:
            if self._islocked:
                del self._dblockers[self.name]

    @property
    def needed(self):
        """ ``True`` if the database is unversioned or if its version is less
            then the maximum defined.
        """
        self._validate_locked()
        version, target = self._version, self._target
        log.d('%s update check: version=[%s] target=[%s]', self.name, version,
              target)
        return version is None or version < target

    @property
    def requires_consent(self):
        """`True` if any missing updates require user consent."""
        self._validate_locked()
        for version in self._updates_due:
            if 'prompt' in self.desc[version]:
                return True
        return False

    @property
    def prompts(self):
        """ Return an iterable of string prompts for updates that require user
            consent.
        """
        self._validate_locked()
        for version in self._updates_due:
            if 'prompt' in self.desc[version]:
                yield self.desc[version]['prompt']

    def run(self):
        """Update database schema to the highest possible version."""
        self._validate_locked()
        log.i('%r: updating database schema', self.name)
        log.d('from version %r to %r', self._version, self._target)
        if None is self._version:
            self._init_with_version(self._target)
        else:
            for version in self._updates_due:
                self._update_to_version(version)

    def reset(self):
        """Delete all content from the database along with supporting structures."""
        self._validate_locked()
        version = self._version
        log.i('%s: resetting database', self.name)
        log.d('version: %s', version)
        if None is version:
            log.d('nothing to reset.')
            return
        with self.db.connection() as cxn:
            cxn.executescript(self.desc[version]['drop.sql'])
            cxn.executescript(self._metatable['drop.sql'])
            cxn.executescript(self._metatable['create.sql'])
            self._setversion(None, cxn)
        cxn.close()

    def _validate_locked(self):
        assert self._islocked, 'must be called in updater context (use "with")'

    @property
    def _version(self):
        try:
            return self.__version
        except AttributeError:
            maxv = self.db.execute(
                'SELECT MAX(version) FROM _meta_version').fetchone()
            maxv = maxv and maxv[0]
            self.__version = maxv if maxv is None else str(maxv)
            return self.__version

    def _setversion(self, value, conn=None):
        del self.__version
        conn = conn or self.db.connection
        log.d('{0}: set version to {1}'.format(self.name, value))
        conn.execute('INSERT INTO _meta_version(version) VALUES (?)',
                     (value, ))

    @property
    def _target(self):
        return max(self.desc)

    @property
    def _updates_due(self):
        if None is self._version:
            return ()
        versions = sorted(self.desc)
        start = versions.index(self._version) + 1
        return versions[start:]

    def _init_meta(self):
        content = self.db.execute(
            'SELECT type, name FROM sqlite_master;').fetchall()
        content = [(t, n) for t, n in content
                   if n != '_meta_version' and not n.startswith('sqlite')]
        with self.db.connection() as cxn:
            cxn.isolation_level = "EXCLUSIVE"
            cxn.executescript(self._metatable['create.sql'])
            if content and self._version is None:
                log.d('%s: unversioned content found: %r', self.name, content)
                self._setversion(0, cxn)
        cxn.isolation_level = ''
        cxn.close()

    def _init_with_version(self, vnum):
        log.d('initializing database %r to version %s', self.name, vnum)
        cxn = self.db.connection()
        cxn.isolation_level = None  # autocommit
        self._runscript(vnum, 'create.sql', cxn)
        self._run_afterscript_if_exists(vnum, cxn)
        self._setversion(vnum, cxn)
        cxn.isolation_level = ''
        cxn.close()

    def _update_to_version(self, vnum):
        log.d('updating database %r to version %d', self.name, vnum)
        cxn = self.db.connection()
        cxn.isolation_level = None  # autocommit
        self._runscript(vnum, 'update.sql', cxn)
        self._run_afterscript_if_exists(vnum, cxn)
        self._setversion(vnum, cxn)
        cxn.isolation_level = ''
        cxn.close()

    def _run_afterscript_if_exists(self, vnum, conn):
        try:
            self._runscript(vnum, 'after.sql', conn)
        except KeyError:
            pass

    def _runscript(self, version, name, cxn):
        try:
            cxn.executescript(self.desc[version][name])
        except sqlite3.OperationalError:
            # update scripts are tested, so the problem's seems to be sqlite
            # itself
            log.x(_('Exception while updating database schema.'))
            log.e(
                _('Database error. This is probably due to your version of'
                  ' sqlite being too old. Try updating sqlite3 and'
                  ' updating python. If the problem persists, you will need'
                  ' to delete the database at ' + self.db.dblocation))
            import sys
            sys.exit(1)
예제 #4
0
파일: sql.py 프로젝트: cheese83/cherrymusic
class Updater(object):
    '''Handle the versioning needs of a single database.

    name : str
        The name of the database to manage.
    dbdef : dict
        The corresponding definition.
    connector : :class:`cherrymusicserver.database.connect.AbstractConnector`
        To connect to the database.
    '''

    _metatable = {
        'create.sql': """CREATE TABLE IF NOT EXISTS _meta_version(
                            version TEXT,
                            _created INTEGER NOT NULL DEFAULT (datetime('now'))
                        );""",
        'drop.sql': """DROP TABLE IF EXISTS _meta_version;"""
    }

    _classlock = threading.RLock()
    _dblockers = {}

    def __init__(self, name, dbdef):
        assert name and dbdef
        self.name = name
        self.desc = dbdef
        self.db = BoundConnector(self.name)
        with self:
            self._init_meta()

    def __del__(self):
        self._unlock()

    def __repr__(self):
        return 'updater({0!r}, {1} -> {2})'.format(
            self.name,
            self._version,
            self._target,
        )

    def __enter__(self):
        self._lock()
        return self

    def __exit__(self, exctype, exception, traceback):
        self._unlock()

    @property
    def _islocked(self):
        name, lockers = self.name, self._dblockers
        with self._classlock:
            return name in lockers and lockers[name] is self

    def _lock(self):
        name, lockers = self.name, self._dblockers
        with self._classlock:
            assert lockers.get(name, self) is self, (
                name + ': is locked by another updater')
            lockers[name] = self

    def _unlock(self):
        with self._classlock:
            if self._islocked:
                del self._dblockers[self.name]

    @property
    def needed(self):
        """ ``True`` if the database is unversioned or if its version is less
            then the maximum defined.
        """
        self._validate_locked()
        version, target = self._version, self._target
        log.d('%s update check: version=[%s] target=[%s]',
              self.name, version, target)
        return version is None or version < target

    @property
    def requires_consent(self):
        """`True` if any missing updates require user consent."""
        self._validate_locked()
        for version in self._updates_due:
            if 'prompt' in self.desc[version]:
                return True
        return False

    @property
    def prompts(self):
        """ Return an iterable of string prompts for updates that require user
            consent.
        """
        self._validate_locked()
        for version in self._updates_due:
            if 'prompt' in self.desc[version]:
                yield self.desc[version]['prompt']

    def run(self):
        """Update database schema to the highest possible version."""
        self._validate_locked()
        log.i('%r: updating database schema', self.name)
        log.d('from version %r to %r', self._version, self._target)
        if None is self._version:
            self._init_with_version(self._target)
        else:
            for version in self._updates_due:
                self._update_to_version(version)

    def reset(self):
        """Delete all content from the database along with supporting structures."""
        self._validate_locked()
        version = self._version
        log.i('%s: resetting database', self.name)
        log.d('version: %s', version)
        if None is version:
            log.d('nothing to reset.')
            return
        with self.db.connection() as cxn:
            cxn.executescript(self.desc[version]['drop.sql'])
            cxn.executescript(self._metatable['drop.sql'])
            cxn.executescript(self._metatable['create.sql'])
            self._setversion(None, cxn)
        cxn.close()

    def _validate_locked(self):
        assert self._islocked, 'must be called in updater context (use "with")'

    @property
    def _version(self):
        try:
            return self.__version
        except AttributeError:
            maxv = self.db.execute('SELECT MAX(version) FROM _meta_version').fetchone()
            maxv = maxv and maxv[0]
            self.__version = maxv if maxv is None else str(maxv)
            return self.__version

    def _setversion(self, value, conn=None):
        del self.__version
        conn = conn or self.db.connection
        log.d('{0}: set version to {1}'.format(self.name, value))
        conn.execute('INSERT INTO _meta_version(version) VALUES (?)', (value,))

    @property
    def _target(self):
        return max(self.desc)

    @property
    def _updates_due(self):
        if None is self._version:
            return ()
        versions = sorted(self.desc)
        start = versions.index(self._version) + 1
        return versions[start:]

    def _init_meta(self):
        content = self.db.execute('SELECT type, name FROM sqlite_master;').fetchall()
        content = [(t, n) for t, n in content if n != '_meta_version' and not n.startswith('sqlite')]
        with self.db.connection() as cxn:
            cxn.isolation_level = "EXCLUSIVE"
            cxn.executescript(self._metatable['create.sql'])
            if content and self._version is None:
                log.d('%s: unversioned content found: %r', self.name, content)
                self._setversion(0, cxn)
        cxn.isolation_level = ''
        cxn.close()

    def _init_with_version(self, vnum):
        log.d('initializing database %r to version %s', self.name, vnum)
        cxn = self.db.connection()
        cxn.isolation_level = None  # autocommit
        self._runscript(vnum, 'create.sql', cxn)
        self._run_afterscript_if_exists(vnum, cxn)
        self._setversion(vnum, cxn)
        cxn.isolation_level = ''
        cxn.close()

    def _update_to_version(self, vnum):
        log.d('updating database %r to version %d', self.name, vnum)
        cxn = self.db.connection()
        cxn.isolation_level = None  # autocommit
        self._runscript(vnum, 'update.sql', cxn)
        self._run_afterscript_if_exists(vnum, cxn)
        self._setversion(vnum, cxn)
        cxn.isolation_level = ''
        cxn.close()

    def _run_afterscript_if_exists(self, vnum, conn):
        try:
            self._runscript(vnum, 'after.sql', conn)
        except KeyError:
            pass

    def _runscript(self, version, name, cxn):
        try:
            cxn.executescript(self.desc[version][name])
        except sqlite3.OperationalError:
            # update scripts are tested, so the problem's seems to be sqlite
            # itself
            log.x(_('Exception while updating database schema.'))
            log.e(_('Database error. This is probably due to your version of'
                    ' sqlite being too old. Try updating sqlite3 and'
                    ' updating python. If the problem persists, you will need'
                    ' to delete the database at ' + self.db.dblocation))
            import sys
            sys.exit(1)
예제 #5
0
파일: sql.py 프로젝트: rborisov/cherrymusic
class Updater(object):
    """Handle the versioning needs of a single database.

    name : str
        The name of the database to manage.
    dbdef : dict
        The corresponding definition.
    connector : :class:`cherrymusicserver.database.connect.AbstractConnector`
        To connect to the database.
    """

    _metatable = {
        "create.sql": """CREATE TABLE IF NOT EXISTS _meta_version(
                            version TEXT,
                            _created INTEGER NOT NULL DEFAULT (datetime('now'))
                        );""",
        "drop.sql": """DROP TABLE IF EXISTS _meta_version;""",
    }

    _classlock = threading.RLock()
    _dblockers = {}

    def __init__(self, name, dbdef):
        assert name and dbdef
        self.name = name
        self.desc = dbdef
        self.db = BoundConnector(self.name)
        with self:
            self._init_meta()

    def __del__(self):
        self._unlock()

    def __repr__(self):
        return "updater({0!r}, {1} -> {2})".format(self.name, self._version, self._target)

    def __enter__(self):
        self._lock()
        return self

    def __exit__(self, exctype, exception, traceback):
        self._unlock()

    @property
    def _islocked(self):
        name, lockers = self.name, self._dblockers
        with self._classlock:
            return name in lockers and lockers[name] is self

    def _lock(self):
        name, lockers = self.name, self._dblockers
        with self._classlock:
            assert lockers.get(name, self) is self, name + ": is locked by another updater"
            lockers[name] = self

    def _unlock(self):
        with self._classlock:
            if self._islocked:
                del self._dblockers[self.name]

    @property
    def needed(self):
        """ ``True`` if the database is unversioned or if its version is less
            then the maximum defined.
        """
        self._validate_locked()
        version, target = self._version, self._target
        log.d("%s update check: version=[%s] target=[%s]", self.name, version, target)
        return version is None or version < target

    @property
    def requires_consent(self):
        """`True` if any missing updates require user consent."""
        self._validate_locked()
        for version in self._updates_due:
            if "prompt" in self.desc[version]:
                return True
        return False

    @property
    def prompts(self):
        """ Return an iterable of string prompts for updates that require user
            consent.
        """
        self._validate_locked()
        for version in self._updates_due:
            if "prompt" in self.desc[version]:
                yield self.desc[version]["prompt"]

    def run(self):
        """Update database schema to the highest possible version."""
        self._validate_locked()
        log.i("%r: updating database schema", self.name)
        log.d("from version %r to %r", self._version, self._target)
        if None is self._version:
            self._init_with_version(self._target)
        else:
            for version in self._updates_due:
                self._update_to_version(version)

    def reset(self):
        """Delete all content from the database along with supporting structures."""
        self._validate_locked()
        version = self._version
        log.i("%s: resetting database", self.name)
        log.d("version: %s", version)
        if None is version:
            log.d("nothing to reset.")
            return
        with self.db.connection() as cxn:
            cxn.executescript(self.desc[version]["drop.sql"])
            cxn.executescript(self._metatable["drop.sql"])
            cxn.executescript(self._metatable["create.sql"])
            self._setversion(None, cxn)
        cxn.close()

    def _validate_locked(self):
        assert self._islocked, 'must be called in updater context (use "with")'

    @property
    def _version(self):
        try:
            return self.__version
        except AttributeError:
            maxv = self.db.execute("SELECT MAX(version) FROM _meta_version").fetchone()
            maxv = maxv and maxv[0]
            self.__version = maxv if maxv is None else str(maxv)
            return self.__version

    def _setversion(self, value, conn=None):
        del self.__version
        conn = conn or self.db.connection
        log.d("{0}: set version to {1}".format(self.name, value))
        conn.execute("INSERT INTO _meta_version(version) VALUES (?)", (value,))

    @property
    def _target(self):
        return max(self.desc)

    @property
    def _updates_due(self):
        if None is self._version:
            return ()
        versions = sorted(self.desc)
        start = versions.index(self._version) + 1
        return versions[start:]

    def _init_meta(self):
        content = self.db.execute("SELECT type, name FROM sqlite_master;").fetchall()
        content = [(t, n) for t, n in content if n != "_meta_version" and not n.startswith("sqlite")]
        with self.db.connection() as cxn:
            cxn.isolation_level = "EXCLUSIVE"
            cxn.executescript(self._metatable["create.sql"])
            if content and self._version is None:
                log.d("%s: unversioned content found: %r", self.name, content)
                self._setversion(0, cxn)
        cxn.isolation_level = ""
        cxn.close()

    def _init_with_version(self, vnum):
        log.d("initializing database %r to version %s", self.name, vnum)
        cxn = self.db.connection()
        cxn.isolation_level = None  # autocommit
        cxn.executescript(self.desc[vnum]["create.sql"])
        self._run_afterscript_if_exists(vnum, cxn)
        self._setversion(vnum, cxn)
        cxn.isolation_level = ""
        cxn.close()

    def _update_to_version(self, vnum):
        log.d("updating database %r to version %d", self.name, vnum)
        cxn = self.db.connection()
        cxn.isolation_level = None  # autocommit
        cxn.executescript(self.desc[vnum]["update.sql"])
        self._run_afterscript_if_exists(vnum, cxn)
        self._setversion(vnum, cxn)
        cxn.isolation_level = ""
        cxn.close()

    def _run_afterscript_if_exists(self, vnum, conn):
        try:
            conn.executescript(self.desc[vnum]["after.sql"])
        except KeyError:
            pass