diff options
author | Rob Austein <sra@hactrn.net> | 2015-10-19 03:36:42 +0000 |
---|---|---|
committer | Rob Austein <sra@hactrn.net> | 2015-10-19 03:36:42 +0000 |
commit | 7f5e75188ad4527e3c3425a155dfed0847a389dd (patch) | |
tree | 400301cae01f51141e380664cf0b382b8204a00d /rpki/sql.py | |
parent | 7ab6040f7eb05a7ac4424e0294d228256e9a64dd (diff) |
Amputate old SQL code out of rpkid with a fire axe, replacing it with
Django ORM. Duct tape and bailing wire everywhere, much clean-up left
to do, but basic "make yamltest" suite runs. Much of the clean-up
isn't worth doing until after revamping the I/O system, as it'll all
change again at that point anyway.
svn path=/branches/tk705/; revision=6127
Diffstat (limited to 'rpki/sql.py')
-rw-r--r-- | rpki/sql.py | 472 |
1 files changed, 0 insertions, 472 deletions
diff --git a/rpki/sql.py b/rpki/sql.py deleted file mode 100644 index 55e6f7cb..00000000 --- a/rpki/sql.py +++ /dev/null @@ -1,472 +0,0 @@ -# $Id$ -# -# Copyright (C) 2009-2013 Internet Systems Consortium ("ISC") -# -# Permission to use, copy, modify, and distribute this software for any -# purpose with or without fee is hereby granted, provided that the above -# copyright notice and this permission notice appear in all copies. -# -# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH -# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY -# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, -# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM -# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE -# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR -# PERFORMANCE OF THIS SOFTWARE. -# -# Portions copyright (C) 2007--2008 American Registry for Internet Numbers ("ARIN") -# -# Permission to use, copy, modify, and distribute this software for any -# purpose with or without fee is hereby granted, provided that the above -# copyright notice and this permission notice appear in all copies. -# -# THE SOFTWARE IS PROVIDED "AS IS" AND ARIN DISCLAIMS ALL WARRANTIES WITH -# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY -# AND FITNESS. IN NO EVENT SHALL ARIN BE LIABLE FOR ANY SPECIAL, DIRECT, -# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM -# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE -# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR -# PERFORMANCE OF THIS SOFTWARE. - -""" -SQL interface code. -""" - -import logging -import weakref - -from rpki.mysql_import import (MySQLdb, _mysql_exceptions) - -import rpki.x509 -import rpki.resource_set -import rpki.sundial -import rpki.log - -logger = logging.getLogger(__name__) - -class session(object): - """ - SQL session layer. - """ - - ## @var ping_threshold - # Timeout after which we should issue a ping command before the real - # one. Intent is to keep the MySQL connection alive without pinging - # before every single command. - - ping_threshold = rpki.sundial.timedelta(seconds = 60) - - def __init__(self, cfg, autocommit = True): - - self.username = cfg.get("sql-username") - self.database = cfg.get("sql-database") - self.password = cfg.get("sql-password") - self.autocommit = autocommit - - self.conv = MySQLdb.converters.conversions.copy() - self.conv.update({ - rpki.sundial.datetime : MySQLdb.converters.DateTime2literal, - MySQLdb.converters.FIELD_TYPE.DATETIME : rpki.sundial.datetime.DateTime_or_None }) - - self.cache = weakref.WeakValueDictionary() - self.dirty = set() - - self.connect() - - def connect(self): - self.db = MySQLdb.connect(user = self.username, - db = self.database, - passwd = self.password, - conv = self.conv) - self.cur = self.db.cursor() - self.db.autocommit(self.autocommit) - self.timestamp = rpki.sundial.now() - - def close(self): - if self.cur: - self.cur.close() - self.cur = None - if self.db: - self.db.close() - self.db = None - - def _wrap_execute(self, func, query, args): - try: - now = rpki.sundial.now() - if now > self.timestamp + self.ping_threshold: - self.db.ping(True) - self.timestamp = now - return func(query, args) - except _mysql_exceptions.MySQLError: - if self.dirty: - logger.warning("MySQL exception with dirty objects in SQL cache!") - raise - - def execute(self, query, args = None): - return self._wrap_execute(self.cur.execute, query, args) - - def executemany(self, query, args): - return self._wrap_execute(self.cur.executemany, query, args) - - def fetchall(self): - return self.cur.fetchall() - - def lastrowid(self): - return self.cur.lastrowid - - def commit(self): - """ - Sweep cache, then commit SQL. - """ - - self.sweep() - logger.debug("Executing SQL COMMIT") - self.db.commit() - - def rollback(self): - """ - SQL rollback, then clear cache and dirty cache. - - NB: We have no way of clearing other references to cached objects, - so if you call this method you MUST forget any state that might - cause you to retain such references. This is probably tricky, and - is itself a good argument for switching to something like the - Django ORM's @commit_on_success semantics, but we do what we can. - """ - - logger.debug("Executing SQL ROLLBACK, discarding SQL cache and dirty set") - self.db.rollback() - self.dirty.clear() - self.cache.clear() - - def cache_clear(self): - """ - Clear the SQL object cache. Shouldn't be necessary now that the - cache uses weak references, but should be harmless. - """ - - logger.debug("Clearing SQL cache") - self.assert_pristine() - self.cache.clear() - - def assert_pristine(self): - """ - Assert that there are no dirty objects in the cache. - """ - - assert not self.dirty, "Dirty objects in SQL cache: %s" % self.dirty - - def sweep(self): - """ - Write any dirty objects out to SQL. - """ - - for s in self.dirty.copy(): - logger.debug("Sweeping (%s) %r", "deleting" if s.sql_deleted else "storing", s) - if s.sql_deleted: - s.sql_delete() - else: - s.sql_store() - self.assert_pristine() - -class template(object): - """ - SQL template generator. - """ - - def __init__(self, table_name, index_column, *data_columns): - """ - Build a SQL template. - """ - - type_map = dict((x[0], x[1]) for x in data_columns if isinstance(x, tuple)) - data_columns = tuple(isinstance(x, tuple) and x[0] or x for x in data_columns) - columns = (index_column,) + data_columns - self.table = table_name - self.index = index_column - self.columns = columns - self.map = type_map - self.select = "SELECT %s FROM %s" % (", ".join("%s.%s" % (table_name, c) for c in columns), table_name) - self.insert = "INSERT %s (%s) VALUES (%s)" % (table_name, - ", ".join(data_columns), - ", ".join("%(" + s + ")s" for s in data_columns)) - self.update = "UPDATE %s SET %s WHERE %s = %%(%s)s" % (table_name, - ", ".join(s + " = %(" + s + ")s" for s in data_columns), - index_column, - index_column) - self.delete = "DELETE FROM %s WHERE %s = %%s" % (table_name, index_column) - -class sql_persistent(object): - """ - Mixin for persistent class that needs to be stored in SQL. - """ - - ## @var sql_in_db - # Whether this object is already in SQL or not. - - sql_in_db = False - - ## @var sql_deleted - # Whether our cached copy of this object has been deleted. - - sql_deleted = False - - ## @var sql_debug - # Enable logging of SQL actions - - sql_debug = False - - ## @var sql_cache_debug - # Enable debugging of SQL cache actions - - sql_cache_debug = False - - @classmethod - def sql_fetch(cls, gctx, id): # pylint: disable=W0622 - """ - Fetch one object from SQL, based on its primary key. - - Since in this one case we know that the primary index is also the - cache key, we check for a cache hit directly in the hope of - bypassing the SQL lookup entirely. - - This method is usually called via a one-line class-specific - wrapper. As a convenience, we also accept an id of None, and just - return None in this case. - """ - - if id is None: - return None - assert isinstance(id, (int, long)), "id should be an integer, was %r" % type(id) - key = (cls, id) - if key in gctx.sql.cache: - return gctx.sql.cache[key] - else: - return cls.sql_fetch_where1(gctx, "%s = %%s" % cls.sql_template.index, (id,)) - - @classmethod - def sql_fetch_where1(cls, gctx, where, args = None, also_from = None): - """ - Fetch one object from SQL, based on an arbitrary SQL WHERE expression. - """ - - results = cls.sql_fetch_where(gctx, where, args, also_from) - if len(results) == 0: - return None - elif len(results) == 1: - return results[0] - else: - raise rpki.exceptions.DBConsistancyError( - "Database contained multiple matches for %s where %s: %r" % - (cls.__name__, where % tuple(repr(a) for a in args), results)) - - @classmethod - def sql_fetch_all(cls, gctx): - """ - Fetch all objects of this type from SQL. - """ - - return cls.sql_fetch_where(gctx, None) - - @classmethod - def sql_fetch_where(cls, gctx, where, args = None, also_from = None): - """ - Fetch objects of this type matching an arbitrary SQL WHERE expression. - """ - - if where is None: - assert args is None and also_from is None - if cls.sql_debug: - logger.debug("sql_fetch_where(%r)", cls.sql_template.select) - gctx.sql.execute(cls.sql_template.select) - else: - query = cls.sql_template.select - if also_from is not None: - query += "," + also_from - query += " WHERE " + where - if cls.sql_debug: - logger.debug("sql_fetch_where(%r, %r)", query, args) - gctx.sql.execute(query, args) - results = [] - for row in gctx.sql.fetchall(): - key = (cls, row[0]) - if key in gctx.sql.cache: - results.append(gctx.sql.cache[key]) - else: - results.append(cls.sql_init(gctx, row, key)) - return results - - @classmethod - def sql_init(cls, gctx, row, key): - """ - Initialize one Python object from the result of a SQL query. - """ - - self = cls() - self.gctx = gctx - self.sql_decode(dict(zip(cls.sql_template.columns, row))) - gctx.sql.cache[key] = self - self.sql_in_db = True - self.sql_fetch_hook() - return self - - def sql_mark_dirty(self): - """ - Mark this object as needing to be written back to SQL. - """ - - if self.sql_cache_debug and not self.sql_is_dirty: - logger.debug("Marking %r SQL dirty", self) - self.gctx.sql.dirty.add(self) - - def sql_mark_clean(self): - """ - Mark this object as not needing to be written back to SQL. - """ - - if self.sql_cache_debug and self.sql_is_dirty: - logger.debug("Marking %r SQL clean", self) - self.gctx.sql.dirty.discard(self) - - @property - def sql_is_dirty(self): - """ - Query whether this object needs to be written back to SQL. - """ - - return self in self.gctx.sql.dirty - - def sql_mark_deleted(self): - """ - Mark this object as needing to be deleted in SQL. - """ - - self.sql_deleted = True - self.sql_mark_dirty() - - def sql_store(self): - """ - Store this object to SQL. - """ - - args = self.sql_encode() - if not self.sql_in_db: - if self.sql_debug: - logger.debug("sql_store(%r, %r)", self.sql_template.insert, args) - self.gctx.sql.execute(self.sql_template.insert, args) - setattr(self, self.sql_template.index, self.gctx.sql.lastrowid()) - self.gctx.sql.cache[(self.__class__, self.gctx.sql.lastrowid())] = self - self.sql_insert_hook() - else: - if self.sql_debug: - logger.debug("sql_store(%r, %r)", self.sql_template.update, args) - self.gctx.sql.execute(self.sql_template.update, args) - self.sql_update_hook() - key = (self.__class__, getattr(self, self.sql_template.index)) - assert key in self.gctx.sql.cache and self.gctx.sql.cache[key] == self - self.sql_mark_clean() - self.sql_in_db = True - - def sql_delete(self): - """ - Delete this object from SQL. - """ - - if self.sql_in_db: - id = getattr(self, self.sql_template.index) # pylint: disable=W0622 - if self.sql_debug: - logger.debug("sql_delete(%r, %r)", self.sql_template.delete, id) - self.sql_delete_hook() - self.gctx.sql.execute(self.sql_template.delete, (id,)) - key = (self.__class__, id) - if self.gctx.sql.cache.get(key) == self: - del self.gctx.sql.cache[key] - self.sql_in_db = False - self.sql_mark_clean() - - def sql_encode(self): - """ - Convert object attributes into a dict for use with canned SQL - queries. This is a default version that assumes a one-to-one - mapping between column names in SQL and attribute names in Python. - If you need something fancier, override this. - """ - - d = dict((a, getattr(self, a, None)) for a in self.sql_template.columns) - for i in self.sql_template.map: - if d.get(i) is not None: - d[i] = self.sql_template.map[i].to_sql(d[i]) - return d - - def sql_decode(self, vals): - """ - Initialize an object with values returned by self.sql_fetch(). - This is a default version that assumes a one-to-one mapping - between column names in SQL and attribute names in Python. If you - need something fancier, override this. - """ - - for a in self.sql_template.columns: - if vals.get(a) is not None and a in self.sql_template.map: - setattr(self, a, self.sql_template.map[a].from_sql(vals[a])) - else: - setattr(self, a, vals[a]) - - def sql_fetch_hook(self): - """ - Customization hook. - """ - - pass - - def sql_insert_hook(self): - """ - Customization hook. - """ - - pass - - def sql_update_hook(self): - """ - Customization hook. - """ - - self.sql_delete_hook() - self.sql_insert_hook() - - def sql_delete_hook(self): - """ - Customization hook. - """ - - pass - - -def cache_reference(func): - """ - Decorator for use with property methods which just do an SQL lookup based on an ID. - Check for an existing reference to the object, just return that if we find it, - otherwise perform the SQL lookup. - - Not 100% certain this is a good idea, but I //think// it should work well with the - current weak reference SQL cache, so long as we create no circular references. - So don't do that. - """ - - attr_name = "_" + func.__name__ - - def wrapped(self): - try: - value = getattr(self, attr_name) - assert value is not None - except AttributeError: - value = func(self) - if value is not None: - setattr(self, attr_name, value) - return value - - wrapped.__name__ = func.__name__ - wrapped.__doc__ = func.__doc__ - wrapped.__dict__.update(func.__dict__) - - return wrapped |