Database independence stuff.

This commit is contained in:
Jeremy Fincher 2004-09-11 20:24:52 +00:00
parent cebf388513
commit 49c465c1c7
4 changed files with 85 additions and 58 deletions

View File

@ -82,8 +82,6 @@ def configure(advanced):
from supybot.questions import expect, anything, something, yn from supybot.questions import expect, anything, something, yn
conf.registerPlugin('Infobot', True) conf.registerPlugin('Infobot', True)
filename = conf.supybot.directories.data.dirize('Infobot.db')
ends = ['!', ends = ['!',
'.', '.',
', $who.',] ', $who.',]
@ -123,11 +121,12 @@ initialAre = {'who': '<reply>',
} }
class PickleInfobotDB(object): class PickleInfobotDB(object):
def __init__(self): def __init__(self, filename):
self._changes = 0 self._changes = 0
self._responses = 0 self._responses = 0
self.filename = filename
try: try:
fd = file(filename) fd = file(self.filename)
except EnvironmentError: except EnvironmentError:
self._is = utils.InsensitivePreservingDict() self._is = utils.InsensitivePreservingDict()
self._are = utils.InsensitivePreservingDict() self._are = utils.InsensitivePreservingDict()
@ -143,7 +142,7 @@ class PickleInfobotDB(object):
raise dbi.InvalidDBError, str(e) raise dbi.InvalidDBError, str(e)
def flush(self): def flush(self):
fd = utils.transactionalFile(filename, 'wb') fd = utils.transactionalFile(self.filename, 'wb')
pickle.dump((self._is, self._are), fd) pickle.dump((self._is, self._are), fd)
fd.close() fd.close()
@ -228,7 +227,7 @@ class PickleInfobotDB(object):
return len(self._are.keys()) + len(self._is.keys()) return len(self._are.keys()) + len(self._is.keys())
class SqliteInfobotDB(object): class SqliteInfobotDB(object):
def __init__(self): def __init__(self, filename):
try: try:
import sqlite import sqlite
except ImportError: except ImportError:
@ -237,23 +236,13 @@ class SqliteInfobotDB(object):
'<http://pysqlite.sf.net/>' '<http://pysqlite.sf.net/>'
self._changes = 0 self._changes = 0
self._responses = 0 self._responses = 0
self.db = None self.filename = filename
def _getDb(self):
try: try:
import sqlite if os.path.exists(self.filename):
except ImportError: self.db = sqlite.connect(self.filename)
raise callbacks.Error, 'You need to have PySQLite installed to '\
'use this plugin. Download it at '\
'<http://pysqlite.sf.net/>'
if self.db is not None:
return self.db
try:
if os.path.exists(filename):
self.db = sqlite.connect(filename)
return self.db return self.db
#else: #else:
self.db = sqlite.connect(filename) self.db = sqlite.connect(self.filename)
cursor = self.db.cursor() cursor = self.db.cursor()
cursor.execute("""CREATE TABLE isFacts ( cursor.execute("""CREATE TABLE isFacts (
key TEXT UNIQUE ON CONFLICT REPLACE, key TEXT UNIQUE ON CONFLICT REPLACE,
@ -269,17 +258,14 @@ class SqliteInfobotDB(object):
for (k, v) in initialAre.iteritems(): for (k, v) in initialAre.iteritems():
self.setAre(k, v) self.setAre(k, v)
self._changes = 0 self._changes = 0
return self.db
except sqlite.DatabaseError, e: except sqlite.DatabaseError, e:
raise dbi.InvalidDBError, str(e) raise dbi.InvalidDBError, str(e)
def close(self): def close(self):
if self.db is not None: self.db.close()
self.db.close()
def changeIs(self, factoid, replacer): def changeIs(self, factoid, replacer):
db = self._getDb() cursor = self.db.cursor()
cursor = db.cursor()
cursor.execute("""SELECT value FROM isFacts WHERE key=%s""", factoid) cursor.execute("""SELECT value FROM isFacts WHERE key=%s""", factoid)
if cursor.rowcount == 0: if cursor.rowcount == 0:
raise dbi.NoRecordError raise dbi.NoRecordError
@ -287,42 +273,37 @@ class SqliteInfobotDB(object):
if replacer is not None: if replacer is not None:
cursor.execute("""UPDATE isFacts SET value=%s WHERE key=%s""", cursor.execute("""UPDATE isFacts SET value=%s WHERE key=%s""",
replacer(old), factoid) replacer(old), factoid)
db.commit() self.db.commit()
self._changes += 1 self._changes += 1
def getIs(self, factoid): def getIs(self, factoid):
db = self._getDb() cursor = self.db.cursor()
cursor = db.cursor()
cursor.execute("""SELECT value FROM isFacts WHERE key=%s""", factoid) cursor.execute("""SELECT value FROM isFacts WHERE key=%s""", factoid)
ret = cursor.fetchone()[0] ret = cursor.fetchone()[0]
self._responses += 1 self._responses += 1
return ret return ret
def setIs(self, fact, oid): def setIs(self, fact, oid):
db = self._getDb() cursor = self.db.cursor()
cursor = db.cursor()
cursor.execute("""INSERT INTO isFacts VALUES (%s, %s)""", fact, oid) cursor.execute("""INSERT INTO isFacts VALUES (%s, %s)""", fact, oid)
db.commit() self.db.commit()
self._changes += 1 self._changes += 1
def delIs(self, factoid): def delIs(self, factoid):
db = self._getDb() cursor = self.db.cursor()
cursor = db.cursor()
cursor.execute("""DELETE FROM isFacts WHERE key=%s""", factoid) cursor.execute("""DELETE FROM isFacts WHERE key=%s""", factoid)
if cursor.rowcount == 0: if cursor.rowcount == 0:
raise dbi.NoRecordError raise dbi.NoRecordError
db.commit() self.db.commit()
self._changes += 1 self._changes += 1
def hasIs(self, factoid): def hasIs(self, factoid):
db = self._getDb() cursor = self.db.cursor()
cursor = db.cursor()
cursor.execute("""SELECT * FROM isFacts WHERE key=%s""", factoid) cursor.execute("""SELECT * FROM isFacts WHERE key=%s""", factoid)
return cursor.rowcount == 1 return cursor.rowcount == 1
def changeAre(self, factoid, replacer): def changeAre(self, factoid, replacer):
db = self._getDb() cursor = self.db.cursor()
cursor = db.cursor()
cursor.execute("""SELECT value FROM areFacts WHERE key=%s""", factoid) cursor.execute("""SELECT value FROM areFacts WHERE key=%s""", factoid)
if cursor.rowcount == 0: if cursor.rowcount == 0:
raise dbi.NoRecordError raise dbi.NoRecordError
@ -330,36 +311,32 @@ class SqliteInfobotDB(object):
if replacer is not None: if replacer is not None:
cursor.execute("""UPDATE areFacts SET value=%s WHERE key=%s""", cursor.execute("""UPDATE areFacts SET value=%s WHERE key=%s""",
replacer(old), factoid) replacer(old), factoid)
db.commit() self.db.commit()
self._changes += 1 self._changes += 1
def getAre(self, factoid): def getAre(self, factoid):
db = self._getDb() cursor = self.db.cursor()
cursor = db.cursor()
cursor.execute("""SELECT value FROM areFacts WHERE key=%s""", factoid) cursor.execute("""SELECT value FROM areFacts WHERE key=%s""", factoid)
ret = cursor.fetchone()[0] ret = cursor.fetchone()[0]
self._responses += 1 self._responses += 1
return ret return ret
def setAre(self, fact, oid): def setAre(self, fact, oid):
db = self._getDb() cursor = self.db.cursor()
cursor = db.cursor()
cursor.execute("""INSERT INTO areFacts VALUES (%s, %s)""", fact, oid) cursor.execute("""INSERT INTO areFacts VALUES (%s, %s)""", fact, oid)
db.commit() self.db.commit()
self._changes += 1 self._changes += 1
def delAre(self, factoid): def delAre(self, factoid):
db = self._getDb() cursor = self.db.cursor()
cursor = db.cursor()
cursor.execute("""DELETE FROM areFacts WHERE key=%s""", factoid) cursor.execute("""DELETE FROM areFacts WHERE key=%s""", factoid)
if cursor.rowcount == 0: if cursor.rowcount == 0:
raise dbi.NoRecordError raise dbi.NoRecordError
db.commit() self.db.commit()
self._changes += 1 self._changes += 1
def hasAre(self, factoid): def hasAre(self, factoid):
db = self._getDb() cursor = self.db.cursor()
cursor = db.cursor()
cursor.execute("""SELECT * FROM areFacts WHERE key=%s""", factoid) cursor.execute("""SELECT * FROM areFacts WHERE key=%s""", factoid)
return cursor.rowcount == 1 return cursor.rowcount == 1
@ -376,16 +353,17 @@ class SqliteInfobotDB(object):
return self._responses return self._responses
def getNumFacts(self): def getNumFacts(self):
db = self._getDb() cursor = self.db.cursor()
cursor = db.cursor()
cursor.execute("""SELECT COUNT(*) FROM areFacts""") cursor.execute("""SELECT COUNT(*) FROM areFacts""")
areFacts = int(cursor.fetchone()[0]) areFacts = int(cursor.fetchone()[0])
cursor.execute("""SELECT COUNT(*) FROM isFacts""") cursor.execute("""SELECT COUNT(*) FROM isFacts""")
isFacts = int(cursor.fetchone()[0]) isFacts = int(cursor.fetchone()[0])
return areFacts + isFacts return areFacts + isFacts
def InfobotDB():
return SqliteInfobotDB() InfobotDB = plugins.DB('Infobot',
{'sqlite': SqliteInfobotDB,
'pickle': PickleInfobotDB})
class Dunno(Exception): class Dunno(Exception):
pass pass
@ -397,7 +375,7 @@ class Infobot(callbacks.PrivmsgCommandAndRegexp):
try: try:
self.db = InfobotDB() self.db = InfobotDB()
except Exception: except Exception:
self.log.exception('Error loading %s:', filename) self.log.exception('Error loading %s:', self.filename)
raise # So it doesn't get loaded without its database. raise # So it doesn't get loaded without its database.
self.irc = None self.irc = None
self.msg = None self.msg = None

View File

@ -155,9 +155,10 @@ class DbiNoteDB(dbi.DB):
return id return id
def NoteDB(): NoteDB = plugins.DB('Note', {'flat': DbiNoteDB})
# XXX This should eventually be smarter. ## def NoteDB():
return DbiNoteDB(conf.supybot.directories.data.dirize('Note.db')) ## # XXX This should eventually be smarter.
## return DbiNoteDB(conf.supybot.directories.data.dirize('Note.db'))
class Note(callbacks.Privmsg): class Note(callbacks.Privmsg):

View File

@ -55,14 +55,22 @@ import supybot.ircutils as ircutils
import supybot.webutils as webutils import supybot.webutils as webutils
try: try:
# We need to sweep away all that mx.* crap because our code doesn't account
# for PySQLite's arbitrary use of it. Whoever decided to change sqlite's
# behavior based on whether or not that module is installed was a *CRACK*
# **FIEND**, plain and simple.
mxCrap = {} mxCrap = {}
for (name, module) in sys.modules.items(): for (name, module) in sys.modules.items():
if name.startswith('mx'): if name.startswith('mx'):
mxCrap[name] = module mxCrap[name] = module
sys.modules[name] = None sys.modules[name] = None
# Now that the mx crap is gone, we can import sqlite.
import sqlite import sqlite
# And now we'll put it back, even though it sucks.
for (name, module) in mxCrap.items(): for (name, module) in mxCrap.items():
sys.modules[name] = module sys.modules[name] = module
# Just in case, we'll do this as well. It doesn't seem to work fine by
# itself, though, or else we'd just do this in the first place.
sqlite.have_datetime = False sqlite.have_datetime = False
Connection = sqlite.Connection Connection = sqlite.Connection
class MyConnection(sqlite.Connection): class MyConnection(sqlite.Connection):
@ -75,6 +83,30 @@ try:
except ImportError: except ImportError:
pass pass
class NoSuitableDatabase(Exception):
def __init__(self, suitable):
self.suitable = suitable
self.suitable.sort()
def __str__(self):
return 'No suitable databases were found. Suitable databases ' \
'include %s.' % utils.commaAndify(self.suitable)
def DB(filename, types):
filename = conf.supybot.directories.data.dirize(filename)
def MakeDB(*args, **kwargs):
for type in conf.supybot.databases():
# Can't do this because Python sucks. Go ahead, try it!
# filename = '.'.join([filename, type, 'db'])
fn = '.'.join([filename, type, 'db'])
try:
return types[type](fn, *args, **kwargs)
except KeyError:
continue
raise NoSuitableDatabase, types.keys()
return MakeDB
class DBHandler(object): class DBHandler(object):
def __init__(self, name=None, suffix='.db'): def __init__(self, name=None, suffix='.db'):
if name is None: if name is None:

View File

@ -627,6 +627,7 @@ registerGlobalValue(supybot.directories.data, 'tmp',
# Remember, we're *meant* to replace this nice little wrapper. # Remember, we're *meant* to replace this nice little wrapper.
def transactionalFile(*args, **kwargs): def transactionalFile(*args, **kwargs):
kwargs['tmpDir'] = supybot.directories.data.tmp() kwargs['tmpDir'] = supybot.directories.data.tmp()
# ??? Should we offer an option not to makeBackupIfSmaller?
return utils.AtomicFile(*args, **kwargs) return utils.AtomicFile(*args, **kwargs)
utils.transactionalFile = transactionalFile utils.transactionalFile = transactionalFile
@ -658,7 +659,20 @@ registerGlobalValue(supybot.plugins, 'alwaysLoadDefault',
### ###
# supybot.databases. For stuff relating to Supybot's databases (duh!) # supybot.databases. For stuff relating to Supybot's databases (duh!)
### ###
registerGroup(supybot, 'databases') class Databases(registry.SpaceSeparatedListOfStrings):
def __call__(self):
v = super(Databases, self).__call__()
if not v:
v = ['flat', 'cdb', 'pickle']
if 'sqlite' in sys.modules:
v.insert(0, 'sqlite')
return v
registerGlobalValue(supybot, 'databases',
Databases([], """Determines what databases are available for use. If this
value is not configured (that is, if its value is empty) then sane defaults
will be provided."""))
registerGroup(supybot.databases, 'users') registerGroup(supybot.databases, 'users')
registerGlobalValue(supybot.databases.users, 'filename', registerGlobalValue(supybot.databases.users, 'filename',
registry.String('users.conf', """Determines what filename will be used for registry.String('users.conf', """Determines what filename will be used for
@ -691,6 +705,8 @@ registerChannelValue(supybot.databases.plugins, 'channelSpecific',
can be channel-specific will be so. This can be overridden by individual can be channel-specific will be so. This can be overridden by individual
channels.""")) channels."""))
# XXX Configuration variables for dbi, sqlite, flat, mysql, etc.
### ###
# Protocol information. # Protocol information.
### ###