diff --git a/plugins/Factoids/plugin.py b/plugins/Factoids/plugin.py index fd56884e0..2e4c7cd50 100644 --- a/plugins/Factoids/plugin.py +++ b/plugins/Factoids/plugin.py @@ -42,12 +42,21 @@ import supybot.callbacks as callbacks from supybot.i18n import PluginInternationalization, internationalizeDocstring _ = PluginInternationalization('Factoids') +#try: + #import sqlite3 as sqlite +#except ImportError: + #raise callbacks.Error, 'You need to have PySQLite installed to use this ' \ + #'plugin. Download it at ' \ + #'' + try: - import sqlite + import sqlite3 except ImportError: - raise callbacks.Error, 'You need to have PySQLite installed to use this ' \ - 'plugin. Download it at ' \ - '' + from pysqlite2 import dbapi2 as sqlite3 # for python2.4 + +# these are needed cuz we are overriding getdb +import threading +import supybot.world as world def getFactoid(irc, msg, args, state): assert not state.channel @@ -85,8 +94,11 @@ class Factoids(callbacks.Plugin, plugins.ChannelDBHandler): def makeDb(self, filename): if os.path.exists(filename): - return sqlite.connect(filename) - db = sqlite.connect(filename) + db = sqlite3.connect(filename) + db.text_factory = str + return db + db = sqlite3.connect(filename) + db.text_factory = str cursor = db.cursor() cursor.execute("""CREATE TABLE keys ( id INTEGER PRIMARY KEY, @@ -110,6 +122,20 @@ class Factoids(callbacks.Plugin, plugins.ChannelDBHandler): db.commit() return db + # override this because sqlite3 doesn't have autocommit + # use isolation_level instead. + def getDb(self, channel): + """Use this to get a database for a specific channel.""" + currentThread = threading.currentThread() + if channel not in self.dbCache and currentThread == world.mainThread: + self.dbCache[channel] = self.makeDb(self.makeFilename(channel)) + if currentThread != world.mainThread: + db = self.makeDb(self.makeFilename(channel)) + else: + db = self.dbCache[channel] + db.isolation_level = None + return db + def getCommandHelp(self, command, simpleSyntax=None): method = self.getCommandMethod(command) if method.im_func.func_name == 'learn': @@ -131,12 +157,14 @@ class Factoids(callbacks.Plugin, plugins.ChannelDBHandler): def learn(self, irc, msg, args, channel, key, factoid): db = self.getDb(channel) cursor = db.cursor() - cursor.execute("SELECT id, locked FROM keys WHERE key LIKE %s", key) - if cursor.rowcount == 0: - cursor.execute("""INSERT INTO keys VALUES (NULL, %s, 0)""", key) + cursor.execute("SELECT id, locked FROM keys WHERE key LIKE ?", (key,)) + results = cursor.fetchall() + if len(results) == 0: + cursor.execute("""INSERT INTO keys VALUES (NULL, ?, 0)""", (key,)) db.commit() - cursor.execute("SELECT id, locked FROM keys WHERE key LIKE %s",key) - (id, locked) = map(int, cursor.fetchone()) + cursor.execute("SELECT id, locked FROM keys WHERE key LIKE ?", (key,)) + results = cursor.fetchall() + (id, locked) = map(int, results[0]) capability = ircdb.makeChannelCapability(channel, 'factoids') if not locked: if ircdb.users.hasUser(msg.prefix): @@ -144,8 +172,8 @@ class Factoids(callbacks.Plugin, plugins.ChannelDBHandler): else: name = msg.nick cursor.execute("""INSERT INTO factoids VALUES - (NULL, %s, %s, %s, %s, %s)""", - id, name, int(time.time()), 0, factoid) + (NULL, ?, ?, ?, ?, ?)""", + (id, name, int(time.time()), 0, factoid)) db.commit() irc.replySuccess() else: @@ -165,9 +193,9 @@ class Factoids(callbacks.Plugin, plugins.ChannelDBHandler): db = self.getDb(channel) cursor = db.cursor() cursor.execute("""SELECT factoids.fact, factoids.id FROM factoids, keys - WHERE keys.key LIKE %s AND factoids.key_id=keys.id + WHERE keys.key LIKE ? AND factoids.key_id=keys.id ORDER BY factoids.id - LIMIT 20""", key) + LIMIT 20""", (key,)) return cursor.fetchall() #return [t[0] for t in cursor.fetchall()] @@ -178,9 +206,9 @@ class Factoids(callbacks.Plugin, plugins.ChannelDBHandler): for (fact,id) in factoids: cursor.execute("""SELECT factoids.usage_count FROM factoids - WHERE factoids.id=%s""", id) + WHERE factoids.id=?""", (id,)) old_count = cursor.fetchall()[0][0] - cursor.execute("UPDATE factoids SET usage_count=%s WHERE id=%s", old_count + 1, id) + cursor.execute("UPDATE factoids SET usage_count=? WHERE id=?", (old_count + 1, id,)) db.commit() def _replyFactoids(self, irc, msg, key, channel, factoids, @@ -257,7 +285,7 @@ class Factoids(callbacks.Plugin, plugins.ChannelDBHandler): FROM keys, factoids WHERE factoids.key_id=keys.id ORDER BY factoids.usage_count DESC - LIMIT %s""", numfacts) + LIMIT ?""", (numfacts,)) factkeys = cursor.fetchall() s = [ "#%d %s (%d)" % (i+1, key[0], key[1]) for i, key in enumerate(factkeys) ] irc.reply(", ".join(s)) @@ -273,7 +301,7 @@ class Factoids(callbacks.Plugin, plugins.ChannelDBHandler): """ db = self.getDb(channel) cursor = db.cursor() - cursor.execute("UPDATE keys SET locked=1 WHERE key LIKE %s", key) + cursor.execute("UPDATE keys SET locked=1 WHERE key LIKE ?", (key,)) db.commit() irc.replySuccess() lock = wrap(lock, ['channel', 'text']) @@ -288,7 +316,7 @@ class Factoids(callbacks.Plugin, plugins.ChannelDBHandler): """ db = self.getDb(channel) cursor = db.cursor() - cursor.execute("UPDATE keys SET locked=0 WHERE key LIKE %s", key) + cursor.execute("UPDATE keys SET locked=0 WHERE key LIKE ?", (key,)) db.commit() irc.replySuccess() unlock = wrap(unlock, ['channel', 'text']) @@ -317,32 +345,33 @@ class Factoids(callbacks.Plugin, plugins.ChannelDBHandler): cursor = db.cursor() cursor.execute("""SELECT keys.id, factoids.id FROM keys, factoids - WHERE key LIKE %s AND - factoids.key_id=keys.id""", key) - if cursor.rowcount == 0: + WHERE key LIKE ? AND + factoids.key_id=keys.id""", (key,)) + results = cursor.fetchall() + if len(results) == 0: irc.error(_('There is no such factoid.')) - elif cursor.rowcount == 1 or number is True: - (id, foo) = cursor.fetchone() - cursor.execute("""DELETE FROM factoids WHERE key_id=%s""", id) - cursor.execute("""DELETE FROM keys WHERE key LIKE %s""", key) + elif len(results) == 1 or number is True: + (id, _) = results[0] + cursor.execute("""DELETE FROM factoids WHERE key_id=?""", (id,)) + cursor.execute("""DELETE FROM keys WHERE key LIKE ?""", (key,)) db.commit() irc.replySuccess() else: if number is not None: - results = cursor.fetchall() + #results = cursor.fetchall() try: (foo, id) = results[number-1] except IndexError: irc.error(_('Invalid factoid number.')) return - cursor.execute("DELETE FROM factoids WHERE id=%s", id) + cursor.execute("DELETE FROM factoids WHERE id=?", (id,)) db.commit() irc.replySuccess() else: irc.error(_('%s factoids have that key. ' 'Please specify which one to remove, ' 'or use * to designate all of them.') % - cursor.rowcount) + len(results)) forget = wrap(forget, ['channel', many('something')]) @internationalizeDocstring @@ -357,10 +386,11 @@ class Factoids(callbacks.Plugin, plugins.ChannelDBHandler): cursor.execute("""SELECT fact, key_id FROM factoids ORDER BY random() LIMIT 3""") - if cursor.rowcount != 0: + results = cursor.fetchall() + if len(results) != 0: L = [] - for (factoid, id) in cursor.fetchall(): - cursor.execute("""SELECT key FROM keys WHERE id=%s""", id) + for (factoid, id) in results: + cursor.execute("""SELECT key FROM keys WHERE id=?""", (id,)) (key,) = cursor.fetchone() L.append('"%s": %s' % (ircutils.bold(key), factoid)) irc.reply('; '.join(L)) @@ -378,14 +408,15 @@ class Factoids(callbacks.Plugin, plugins.ChannelDBHandler): """ db = self.getDb(channel) cursor = db.cursor() - cursor.execute("SELECT id, locked FROM keys WHERE key LIKE %s", key) - if cursor.rowcount == 0: + cursor.execute("SELECT id, locked FROM keys WHERE key LIKE ?", (key,)) + results = cursor.fetchall() + if len(results) == 0: irc.error(_('No factoid matches that key.')) return - (id, locked) = map(int, cursor.fetchone()) + (id, locked) = map(int, results[0]) cursor.execute("""SELECT added_by, added_at FROM factoids - WHERE key_id=%s - ORDER BY id""", id) + WHERE key_id=? + ORDER BY id""", (id,)) factoids = cursor.fetchall() L = [] counter = 0 @@ -413,16 +444,17 @@ class Factoids(callbacks.Plugin, plugins.ChannelDBHandler): cursor = db.cursor() cursor.execute("""SELECT factoids.id, factoids.fact FROM keys, factoids - WHERE keys.key LIKE %s AND - keys.id=factoids.key_id""", key) - if cursor.rowcount == 0: + WHERE keys.key LIKE ? AND + keys.id=factoids.key_id""", (key,)) + results = cursor.fetchall() + if len(results) == 0: irc.error(format(_('I couldn\'t find any key %q'), key)) return - elif cursor.rowcount < number: - irc.errorInvalid(_('key id')) - (id, fact) = cursor.fetchall()[number-1] + elif len(results) < number: + irc.errorInvalid('key id') + (id, fact) = results[number-1] newfact = replacer(fact) - cursor.execute("UPDATE factoids SET fact=%s WHERE id=%s", newfact, id) + cursor.execute("UPDATE factoids SET fact=? WHERE id=?", (newfact, id)) db.commit() irc.replySuccess() change = wrap(change, ['channel', 'something', @@ -458,7 +490,7 @@ class Factoids(callbacks.Plugin, plugins.ChannelDBHandler): db.create_function(predicateName, 1, p) predicateName += 'p' for glob in globs: - criteria.append('TARGET LIKE %s') + criteria.append('TARGET LIKE ?') formats.append(glob.translate(self._sqlTrans)) cursor = db.cursor() sql = """SELECT keys.key FROM %s WHERE %s""" % \ @@ -474,8 +506,17 @@ class Factoids(callbacks.Plugin, plugins.ChannelDBHandler): elif cursor.rowcount > 100: irc.reply(_('More than 100 keys matched that query; ' 'please narrow your query.')) + results = cursor.fetchall() + if len(results) == 0: + irc.reply(_('No keys matched that query.')) + elif len(results) == 1 and \ + self.registryValue('showFactoidIfOnlyOneMatch', channel): + self.whatis(irc, msg, [results[0][0]]) + elif len(results) > 100: + irc.reply(_('More than 100 keys matched that query; ' + 'please narrow your query.')) else: - keys = [repr(t[0]) for t in cursor.fetchall()] + keys = [repr(t[0]) for t in results] s = format('%L', keys) irc.reply(s) search = wrap(search, ['channel', diff --git a/plugins/Factoids/test.py b/plugins/Factoids/test.py index fb8fa7e35..30a8a9f06 100644 --- a/plugins/Factoids/test.py +++ b/plugins/Factoids/test.py @@ -99,8 +99,8 @@ if sqlite: self.assertRegexp('factoids search --regexp m/^j/ *ss*', 'jamessan') self.assertRegexp('factoids search --regexp /^j/', - 'jemfinch.*jamessan') - self.assertRegexp('factoids search j*', 'jemfinch.*jamessan') + 'jamessan.*jemfinch') + self.assertRegexp('factoids search j*', 'jamessan.*jemfinch') self.assertRegexp('factoids search *ke*', 'inkedmn.*strike|strike.*inkedmn') self.assertRegexp('factoids search ke', diff --git a/plugins/MoobotFactoids/plugin.py b/plugins/MoobotFactoids/plugin.py index 6576dadbd..1063c63dd 100644 --- a/plugins/MoobotFactoids/plugin.py +++ b/plugins/MoobotFactoids/plugin.py @@ -100,19 +100,21 @@ class SqliteMoobotDB(object): def _getDb(self, channel): try: - import sqlite + import sqlite3 except ImportError: - raise callbacks.Error, \ - 'You need to have PySQLite installed to use this ' \ - 'plugin. Download it at ' \ - '' + from pysqlite2 import dbapi2 as sqlite3 # for python2.4 + if channel in self.dbs: return self.dbs[channel] filename = plugins.makeChannelFilename(self.filename, channel) + if os.path.exists(filename): - self.dbs[channel] = sqlite.connect(filename) - return self.dbs[channel] - db = sqlite.connect(filename) + db = sqlite3.connect(filename) + db.text_factory = str + self.dbs[channel] = db + return db + db = sqlite3.connect(filename) + db.text_factory = str self.dbs[channel] = db cursor = db.cursor() cursor.execute("""CREATE TABLE factoids ( @@ -135,11 +137,12 @@ class SqliteMoobotDB(object): db = self._getDb(channel) cursor = db.cursor() cursor.execute("""SELECT fact FROM factoids - WHERE key LIKE %s""", key) - if cursor.rowcount == 0: + WHERE key LIKE ?""", (key,)) + results = cursor.fetchall() + if len(results) == 0: return None else: - return cursor.fetchall()[0] + return results[0] def getFactinfo(self, channel, key): db = self._getDb(channel) @@ -149,63 +152,65 @@ class SqliteMoobotDB(object): last_requested_by, last_requested_at, requested_count, locked_by, locked_at FROM factoids - WHERE key LIKE %s""", key) - if cursor.rowcount == 0: + WHERE key LIKE ?""", (key,)) + results = cursor.fetchall() + if len(results) == 0: return None else: - return cursor.fetchone() + return results[0] def randomFactoid(self, channel): db = self._getDb(channel) cursor = db.cursor() cursor.execute("""SELECT fact, key FROM factoids ORDER BY random() LIMIT 1""") - if cursor.rowcount == 0: + results = cursor.fetchall() + if len(results) == 0: return None else: - return cursor.fetchone() + return results[0] def addFactoid(self, channel, key, value, creator_id): db = self._getDb(channel) cursor = db.cursor() cursor.execute("""INSERT INTO factoids VALUES - (%s, %s, %s, NULL, NULL, NULL, NULL, - NULL, NULL, %s, 0)""", - key, creator_id, int(time.time()), value) + (?, ?, ?, NULL, NULL, NULL, NULL, + NULL, NULL, ?, 0)""", + (key, creator_id, int(time.time()), value)) db.commit() def updateFactoid(self, channel, key, newvalue, modifier_id): db = self._getDb(channel) cursor = db.cursor() cursor.execute("""UPDATE factoids - SET fact=%s, modified_by=%s, - modified_at=%s WHERE key LIKE %s""", - newvalue, modifier_id, int(time.time()), key) + SET fact=?, modified_by=?, + modified_at=? WHERE key LIKE ?""", + (newvalue, modifier_id, int(time.time()), key)) db.commit() def updateRequest(self, channel, key, hostmask): db = self._getDb(channel) cursor = db.cursor() cursor.execute("""UPDATE factoids SET - last_requested_by = %s, - last_requested_at = %s, + last_requested_by = ?, + last_requested_at = ?, requested_count = requested_count + 1 - WHERE key = %s""", - hostmask, int(time.time()), key) + WHERE key = ?""", + (hostmask, int(time.time()), key)) db.commit() def removeFactoid(self, channel, key): db = self._getDb(channel) cursor = db.cursor() - cursor.execute("""DELETE FROM factoids WHERE key LIKE %s""", - key) + cursor.execute("""DELETE FROM factoids WHERE key LIKE ?""", + (key,)) db.commit() def locked(self, channel, key): db = self._getDb(channel) cursor = db.cursor() cursor.execute ("""SELECT locked_by FROM factoids - WHERE key LIKE %s""", key) + WHERE key LIKE ?""", (key,)) if cursor.fetchone()[0] is None: return False else: @@ -215,17 +220,17 @@ class SqliteMoobotDB(object): db = self._getDb(channel) cursor = db.cursor() cursor.execute("""UPDATE factoids - SET locked_by=%s, locked_at=%s - WHERE key LIKE %s""", - locker_id, int(time.time()), key) + SET locked_by=?, locked_at=? + WHERE key LIKE ?""", + (locker_id, int(time.time()), key)) db.commit() def unlock(self, channel, key): db = self._getDb(channel) cursor = db.cursor() cursor.execute("""UPDATE factoids - SET locked_by=%s, locked_at=%s - WHERE key LIKE %s""", None, None, key) + SET locked_by=?, locked_at=? + WHERE key LIKE ?""", (None, None, key)) db.commit() def mostAuthored(self, channel, limit): @@ -233,14 +238,14 @@ class SqliteMoobotDB(object): cursor = db.cursor() cursor.execute("""SELECT created_by, count(key) FROM factoids GROUP BY created_by - ORDER BY count(key) DESC LIMIT %s""", limit) + ORDER BY count(key) DESC LIMIT ?""", (limit,)) return cursor.fetchall() def mostRecent(self, channel, limit): db = self._getDb(channel) cursor = db.cursor() cursor.execute("""SELECT key FROM factoids - ORDER BY created_at DESC LIMIT %s""", limit) + ORDER BY created_at DESC LIMIT ?""", (limit,)) return cursor.fetchall() def mostPopular(self, channel, limit): @@ -248,43 +253,35 @@ class SqliteMoobotDB(object): cursor = db.cursor() cursor.execute("""SELECT key, requested_count FROM factoids WHERE requested_count > 0 - ORDER BY requested_count DESC LIMIT %s""", limit) - if cursor.rowcount == 0: - return [] - else: - return cursor.fetchall() + ORDER BY requested_count DESC LIMIT ?""", (limit,)) + results = cursor.fetchall() + return results def getKeysByAuthor(self, channel, authorId): db = self._getDb(channel) cursor = db.cursor() - cursor.execute("""SELECT key FROM factoids WHERE created_by=%s - ORDER BY key""", authorId) - if cursor.rowcount == 0: - return [] - else: - return cursor.fetchall() + cursor.execute("""SELECT key FROM factoids WHERE created_by=? + ORDER BY key""", (authorId,)) + results = cursor.fetchall() + return results def getKeysByGlob(self, channel, glob): db = self._getDb(channel) cursor = db.cursor() glob = '%%%s%%' % glob - cursor.execute("""SELECT key FROM factoids WHERE key LIKE %s - ORDER BY key""", glob) - if cursor.rowcount == 0: - return [] - else: - return cursor.fetchall() + cursor.execute("""SELECT key FROM factoids WHERE key LIKE ? + ORDER BY key""", (glob,)) + results = cursor.fetchall() + return results def getKeysByValueGlob(self, channel, glob): db = self._getDb(channel) cursor = db.cursor() glob = '%%%s%%' % glob - cursor.execute("""SELECT key FROM factoids WHERE fact LIKE %s - ORDER BY key""", glob) - if cursor.rowcount == 0: - return [] - else: - return cursor.fetchall() + cursor.execute("""SELECT key FROM factoids WHERE fact LIKE ? + ORDER BY key""", (glob,)) + results = cursor.fetchall() + return results MoobotDB = plugins.DB('MoobotFactoids', {'sqlite': SqliteMoobotDB}) diff --git a/plugins/__init__.py b/plugins/__init__.py index c177eb840..2e9b2c14f 100644 --- a/plugins/__init__.py +++ b/plugins/__init__.py @@ -51,46 +51,51 @@ from supybot.commands import * import supybot.ircutils as ircutils import supybot.callbacks as callbacks +## i think we don't need any of this with sqlite3 +#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 = {} + #for (name, module) in sys.modules.items(): + #if name.startswith('mx'): + #mxCrap[name] = module + #sys.modules.pop(name) + ## Now that the mx crap is gone, we can import sqlite. + #import sqlite3 as sqlite + ## And now we'll put it back, even though it sucks. + #sys.modules.update(mxCrap) + ## 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 + #Connection = sqlite.Connection + #class MyConnection(sqlite.Connection): + #def commit(self, *args, **kwargs): + #if self.autocommit: + #return + #else: + #Connection.commit(self, *args, **kwargs) + + #def __del__(self): + #try: + #Connection.__del__(self) + #except AttributeError: + #pass + #except Exception, e: + #try: + #log.exception('Uncaught exception in __del__:') + #except: + #pass + #sqlite.Connection = MyConnection + ##del Connection.__del__ +#except ImportError: + #pass + 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 = {} - for (name, module) in sys.modules.items(): - if name.startswith('mx'): - mxCrap[name] = module - sys.modules.pop(name) - # Now that the mx crap is gone, we can import sqlite. - import sqlite - # And now we'll put it back, even though it sucks. - sys.modules.update(mxCrap) - # 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 - Connection = sqlite.Connection - class MyConnection(sqlite.Connection): - def commit(self, *args, **kwargs): - if self.autocommit: - return - else: - Connection.commit(self, *args, **kwargs) - - def __del__(self): - try: - Connection.__del__(self) - except AttributeError: - pass - except Exception, e: - try: - log.exception('Uncaught exception in __del__:') - except: - pass - sqlite.Connection = MyConnection - #del Connection.__del__ + import sqlite3 except ImportError: - pass - + from pysqlite2 import dbapi2 as sqlite3 # for python2.4 class NoSuitableDatabase(Exception): def __init__(self, suitable): @@ -176,7 +181,7 @@ class ChannelDBHandler(object): db = self.makeDb(self.makeFilename(channel)) else: db = self.dbCache[channel] - db.autocommit = 1 + db.isolation_level = None return db def die(self):