# 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; either version 2 of the License, or # (at your option) any later version. # # 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 Library 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. # copyright 2005 Duke University # # Copyright 2005 Dan Williams and Red Hat, Inc. import os, sys import types import exceptions from plague import BaseConfig import Config class ResultSet: """ Partially taken from sqlite's python DB-API module """ def __init__(self, result, desc): self.__dict__['_dbresult'] = result self.__dict__['_desc'] = desc self.__dict__['_xlatekey'] = {} # set up key translation of field name -> index for _i in range(len(desc)): self.__dict__['_xlatekey'][desc[_i][0].upper()] = _i def __getattr__(self, key): key = key.upper() if self.__dict__['_xlatekey'].has_key(key): return self.__dict__['_dbresult'][self.__dict__['_xlatekey'][key]] raise AttributeError, key def __len__(self): return len(self.__dict__['_dbresult']) def __getitem__(self, key): if isinstance(key, types.StringType): key = self.__dict__['_xlatekey'][key.upper()] return self.__dict__['_dbresult'][key] def __contains__(self, key): return self.__dict__['_xlatekey'].has_key(key.upper()) def __repr__(self): return repr(self.__dict__['_dbresult']) def __str__(self): return str(self.__dict__['_dbresult']) def __cmp__(self, other): return cmp(self.__dict__['_dbresult'], other) def description(self): return self.__dict__['_desc'] def keys(self): _k = [] for _i in self.__dict__['_desc']: _k.append(_i[0]) return _k def values(self): return self.__dict__['_dbresult'] def items(self): _items = [] for i in range(len(self.__dict__['_dbresult'])): _items.append((self.__dict__['_desc'][i][0], self.__dict__['_dbresult'][i])) return _items def has_key(self, key): return self.__dict__['_xlatekey'].has_key(key.upper()) class BaseDBEngineClass: def __init__(self, cfg): self._cfg = cfg self._dbcx = self._connect() def cursor(self): return self._dbcx.cursor() def commit(self): self._dbcx.commit() def get_uid_field_type(self): raise Exception("Need to implement this function for derived classes.") def get_uid_field_for_insert(self): raise Exception("Need to implement this function for derived classes.") def get_uid_value_for_insert(self): raise Exception("Need to implement this function for derived classes.") def convert_boolean(self, value): raise Exception("Need to implement this function for derived classes.") def fetchall(self, cursor): raise Exception("Need to implement this function for derived classes.") def fetchone(self, cursor): raise Exception("Need to implement this function for derived classes.") class sqliteEngineClass(BaseDBEngineClass): """ sqlite will autoincrement the primary key on insert when you specify the field and a value of NULL """ def get_uid_field_type(self): return "INTEGER PRIMARY KEY" def get_uid_field_for_insert(self): return "uid," def get_uid_value_for_insert(self): return "NULL," def convert_boolean(self, value): return value def fetchall(self, cursor): """ sqlite returns the items in a dict-like class already """ return cursor.fetchall() def fetchone(self, cursor): """ sqlite returns the items in a dict-like class already """ return cursor.fetchone() def _connect(self): try: dbloc = self._cfg.get_str("sqlite Engine", "database") except BaseConfig.ConfigError, e: print "Error: bad sqlite config options. '%s'" % e os._exit(1) try: tm = self._cfg.get_int("sqlite Engine", "timeout") except BaseConfig.ConfigError, e: tm = 3 try: dbcx = sqlite.connect(dbloc, encoding="utf-8", timeout=tm) except StandardError, e: print "Error: %s" % e os._exit(1) return dbcx class pgdbEngineClass(BaseDBEngineClass): """ postgres will autoincrement SERIAL fields on insert automatically, you don't need to specify its value in INSERT statements """ def get_uid_field_type(self): return "SERIAL PRIMARY KEY" def get_uid_field_for_insert(self): return "" def get_uid_value_for_insert(self): return "" def convert_boolean(self, value): if value == 1 or value == True: return True elif value == 0 or value == False: return False return value def fetchall(self, cursor): """ We need to convert the returned data to a ResultSet object so we can access it as a dict """ rows = cursor.fetchall() return [ResultSet(row, cursor.description) for row in rows] def fetchone(self, cursor): """ We need to convert the returned data to a ResultSet object so we can access it as a dict """ row = cursor.fetchone() if not row: return None return ResultSet(row, cursor.description) def _connect(self): try: host = self._cfg.get_str("pgdb Engine", "host") database = self._cfg.get_str("pgdb Engine", "database") user = self._cfg.get_str("pgdb Engine", "user") password = self._cfg.get_str("pgdb Engine", "password") except BaseConfig.ConfigError, e: print "Error: bad pgdb config options. '%s'" % e os._exit(1) try: dbcx = pgdb.connect(host=host, database=database, user=user, password=password) except StandardError, e: print "Error: %s" % e os._exit(1) return dbcx def __del__(self): self._dbcx.close() del self._dbcx class mysqlEngineClass(BaseDBEngineClass): """ MySQL will autoincrement the primary key since we define it as auto_increment """ def get_uid_field_type(self): return "int(11) NOT NULL auto_increment PRIMARY KEY" def get_uid_field_for_insert(self): return "" def get_uid_value_for_insert(self): return "" def convert_boolean(self, value): return value def fetchall(self, cursor): """ We need to convert the returned data to a ResultSet object so we can access it as a dict """ rows = cursor.fetchall() return [ResultSet(row, cursor.description) for row in rows] def fetchone(self, cursor): """ We need to convert the returned data to a ResultSet object so we can access it as a dict """ row = cursor.fetchone() if not row: return None return ResultSet(row, cursor.description) def _connect(self): try: host = self._cfg.get_str("mysql Engine", "host") database = self._cfg.get_str("mysql Engine", "database") user = self._cfg.get_str("mysql Engine", "user") password = self._cfg.get_str("mysql Engine", "password") except BaseConfig.ConfigError, e: print "Error: bad mysql config options. '%s'" % e os._exit(1) try: dbcx = MySQLdb.connect(host=host, db=database, user=user, passwd=password) except StandardError, e: print "Error: %s" % e os._exit(1) return dbcx def __del__(self): self._dbcx.close() del self._dbcx db_engines = { 'sqlite': sqliteEngineClass, 'pgdb': pgdbEngineClass, 'mysql': mysqlEngineClass } # Import our database engines, if any engine # isn't present, remove its class entry from # the db engine dict try: import sqlite except ImportError, e: db_engines['sqlite'] = None try: import pgdb except ImportError, e: db_engines['pgdb'] = None try: import MySQLdb except ImportError, e: db_engines['mysql'] = None class DBManager: def __init__(self, cfg): self._cfg = cfg try: engine = self._cfg.get_str("Database", "engine") self._db_engine_class = db_engines[engine] except KeyError: print "Error: unrecognized database engine '%s'. Must be " \ "one of: %s" % (engine, db_engines.keys()) os._exit(1) if not self._db_engine_class: print "Error: database engine '%s' could not be loaded. " \ "Perhaps you need the python module for it?" % engine os._exit(1) print "Using database engine %s." % engine # Make sure our database structure is what we want dbcx = self.dbcx() if not dbcx: os._exit(1) (success, msg) = self._ensure_tables(dbcx) del dbcx if not success: print msg os._exit(1) def dbcx(self): try: dbcx = self._db_engine_class(self._cfg) except StandardError, e: dbcx = None print "DB Error: %s" % e return dbcx def _ensure_tables(self, dbcx): """ Create database tables if they don't exist """ curs = dbcx.cursor() try: curs.execute('SELECT * FROM jobs LIMIT 4') except Exception, e: # If table wasn't created, try to create it try: uid_type = dbcx.get_uid_field_type() dbcx.commit() curs.execute('CREATE TABLE jobs (' \ 'uid %s, ' \ 'username VARCHAR(100), ' \ 'package VARCHAR(50), ' \ 'source VARCHAR(255), ' \ 'target_distro VARCHAR(20), ' \ 'target_target VARCHAR(20), ' \ 'target_repo VARCHAR(20), ' \ 'buildreq VARCHAR(75), ' \ 'starttime BIGINT, ' \ 'endtime BIGINT, ' \ 'status VARCHAR(20), ' \ 'result VARCHAR(15), ' \ 'rpm_epoch VARCHAR(4), ' \ 'rpm_version VARCHAR(25), ' \ 'rpm_release VARCHAR(25), ' \ 'archlist VARCHAR(75), ' \ 'result_msg TEXT' \ ')' % uid_type) dbcx.commit() except StandardError, e: return (False, "Could not access the job database. Reason: '%s'. Exiting..." % e) # Create the archjobs table try: curs.execute('SELECT * FROM archjobs LIMIT 4') except Exception, e: # If table wasn't created, try to create it try: dbcx.commit() curs.execute('CREATE TABLE archjobs (' \ 'jobid VARCHAR(40) PRIMARY KEY, ' \ 'parent_uid INTEGER, ' \ 'starttime BIGINT, ' \ 'endtime BIGINT, ' \ 'arch VARCHAR(15), ' \ 'builder_addr VARCHAR(100), ' \ 'status VARCHAR(15), ' \ 'builder_status VARCHAR(15)' \ ')') dbcx.commit() except StandardError, e: return (False, "Could not access the job database. Reason: '%s'. Exiting..." % e) # Create the users table try: curs.execute('SELECT * FROM users LIMIT 4') except Exception, e: # If table wasn't created, try to create it try: dbcx.commit() curs.execute('CREATE TABLE users (' \ 'email VARCHAR(50) PRIMARY KEY, ' \ 'own_jobs BOOLEAN, ' \ 'job_admin BOOLEAN, ' \ 'user_admin BOOLEAN, ' \ 'server_admin BOOLEAN' \ ')') dbcx.commit() except StandardError, e: return (False, "Could not access the user database. Reason: '%s'. Exiting..." % e) return (True, "")