From 8821161f7199ea81a0b07772192ec0d2bc04e1ec Mon Sep 17 00:00:00 2001 From: mrhanky Date: Fri, 30 Jun 2017 15:03:01 +0200 Subject: [PATCH] Now running pgsql, added rollbacks for failed insert/updates/deletes --- nxy/plugins/mcmaniac.py | 59 +++++++++++----- nxy/plugins/quotes.py | 149 +++++++++++++++++++++++++++++++--------- nxy/plugins/rape.py | 49 +++++++------ nxy/plugins/seen.py | 29 ++++---- nxy/plugins/tell.py | 41 ++++++----- nxy/plugins/timer.py | 55 ++++++++------- schema.sql | 23 ------- 7 files changed, 257 insertions(+), 148 deletions(-) diff --git a/nxy/plugins/mcmaniac.py b/nxy/plugins/mcmaniac.py index 346b9ef..ae0fc79 100644 --- a/nxy/plugins/mcmaniac.py +++ b/nxy/plugins/mcmaniac.py @@ -4,46 +4,69 @@ import irc3 from docopt import Dict as DocOptDict from irc3.plugins.command import command from irc3.utils import IrcString +from psycopg2 import Error from . import DatabasePlugin from ..utils import parse_int +# TODO: fix sql shit +@irc3.plugin class McManiac(DatabasePlugin): requires = ['irc3.plugins.command', - 'nxy.plugins.database'] - table = 'mcmaniacs' + 'nxy.plugins.storage'] @command(options_first=True) - def mcmaniac(self, mask: IrcString, channel: IrcString, args: DocOptDict): + def mcmaniac(self, mask: IrcString, target: IrcString, args: DocOptDict): """Get a random McManiaC or by index. %%mcmaniac [] """ index = args.get('') + if index: index = parse_int(index) if not index: return index, order, _ = index order = 'id {order}'.format(order=order) - extra = 'offset ?' - binds = [index] + offset = 'offset %s' else: order = 'random()' - extra = '' - binds = [] - self.cur.execute('''select item, - (select count(id) from mcmaniacs b where a.id >= b.id) as idx, - (select count(id) from mcmaniacs) as len - from mcmaniacs a order by {order} limit 1 {extra} - '''.format(order=order, extra=extra), binds) - result = self.cur.fetchone() - if result: - return '[{idx}/{len}] {item}'.format(**result) + offset = '' - @irc3.event(r'(?i)^:(?P\S+) PRIVMSG \S+ :.*(?PMc\S+iaC).*') + # Fetch result from database + self.cur.execute(''' + select + item, + rank() over (order by id), + count(*) over (rows between unbounded preceding + and unbounded following) as total + from + mcmaniacs + order by + {order} + limit + 1 + {offset} + '''.format(order=order, offset=offset), [index]) + result = self.cur.fetchone() + + if result: + return '[{rank}/{total}] {item}'.format(**result) + + @irc3.event(r'^:(?P\S+) PRIVMSG \S+ :.*(?PMc\S+iaC).*') def save(self, mask: str, item: str): if IrcString(mask).nick != self.bot.nick: - self.cur.execute('insert into mcmaniacs (item) values (?)', [item]) - self.con.commit() + try: + # Insert into database + self.cur.execute(''' + insert into + mcmaniacs (item) + values + (%s) + ''', [item]) + self.con.commit() + except Error: + # Rollback transaction on error + self.con.rollback() diff --git a/nxy/plugins/quotes.py b/nxy/plugins/quotes.py index 92601b1..10a896c 100644 --- a/nxy/plugins/quotes.py +++ b/nxy/plugins/quotes.py @@ -4,18 +4,85 @@ import irc3 from docopt import Dict as DocOptDict from irc3.plugins.command import command from irc3.utils import IrcString +from psycopg2 import Error from . import DatabasePlugin from ..utils import NICK_REGEX, parse_int +""" + delete from + quotes + where + id = ( + select + id + from + quotes a + where + nick like %s and %s = ( + select + count(id) + from + quotes b where a.id {op} b.id + ) + order by + id {order} + ) +""" + + @irc3.plugin class Quotes(DatabasePlugin): requires = ['irc3.plugins.command', - 'nxy.plugins.database'] + 'nxy.plugins.storage'] + + def add_quote(self, mask, nick, quote): + # Parse nick from "<@foobar>" like strings + nick = NICK_REGEX.match(nick).group(1) + + if not nick: + self.bot.notice(mask.nick, '[Quotes] Error parsing nick') + else: + # Insert quote into database + self.cur.execute(''' + insert into + quotes (nick, item) + values + (%s, %s) + ''', [nick, quote]) + + def delete_quote(self, nick, quote): + index, order, _ = parse_int(quote, select=False) + + if index: + # Delete from database + self.cur.execute(''' + with ranked_quotes as ( + select + id, + rank() over (partition by nick order by id {order}) + from + quotes + where + nick = %s + ) + + delete from + quotes + where + id = ( + select + id + from + ranked_quotes + where + rank = %s + ) + '''.format(order=order), [nick, index]) @command(options_first=True) - def q(self, mask: IrcString, channel: IrcString, args: DocOptDict): + def q(self, mask: IrcString, target: IrcString, args: DocOptDict): """Get, add or delete quots for a user %%q ... @@ -23,48 +90,62 @@ class Quotes(DatabasePlugin): """ cmd = args.get('') nick = args[''] - item = args.get('') - if cmd and item: - if self.guard.has_permission(mask, 'admin'): + quote = ''.join(args.get('')) + + if cmd and quote: + try: + # Anybody can add if cmd == 'add': - nick = NICK_REGEX.match(nick).group(1) - if not nick: - return '[Quotes] Error parsing nick' - self.cur.execute('insert into quotes (nick, item) values ' - '(?, ?', [nick, ' '.join(item)]) - self.con.commit() - if cmd == 'del': - index, order, op = parse_int(''.join(item), select=False) - if not index: - return - self.cur.execute('''delete from quotes where id = - (select id from quotes a where nick like ? and ? = - (select count(id) from quotes b where a.id {op} b.id) - order by id {order}) - '''.format(op=op, order=order), [nick, index]) + self.add_quote(mask, nick, quote) + # But only admins can delete + elif cmd == 'del' and self.guard.has_permission(mask, 'admin'): + self.delete_quote(nick, quote) + self.con.commit() - else: - self.bot.notice(mask.nick, 'Permission denied') + except Error: + # Rollback transaction on error + self.con.rollback() else: index = args.get('') - binds = [nick, nick, nick] + values = [nick] + if index: index = parse_int(index) if not index: return index, order, _ = index - order = 'id {order}'.format(order=order) - extra = 'offset ?' - binds.append(index) + order = 'rank {order}'.format(order=order) + offset = 'offset %s' + values.append(index) else: order = 'random()' - extra = '' - self.cur.execute('''select nick, item, - (select count(id) from quotes b where b.nick like ? and - a.id >= b.id) as idx, - (select count(id) from quotes where nick like ?) as len - from quotes a where nick like ? order by {order} limit 1 {extra} - '''.format(order=order, extra=extra), binds) + offset = '' + + # Fetch quote from database + self.cur.execute(""" + with ranked_quotes as ( + select + nick, + item, + rank() over (partition by nick order by id), + count(*) over (partition by nick) as total + from + quotes + ) + + select + * + from + ranked_quotes + where + nick like %s + order by + {order} + limit + 1 + {offset} + """.format(order=order, offset=offset), values) result = self.cur.fetchone() + if result: - return '[{idx}/{len}] <{nick}> {item}'.format(**result) + return '[{rank}/{total}] <{nick}> {item}'.format(**result) diff --git a/nxy/plugins/rape.py b/nxy/plugins/rape.py index 800b6cb..10f56e0 100644 --- a/nxy/plugins/rape.py +++ b/nxy/plugins/rape.py @@ -5,6 +5,7 @@ import irc3 from docopt import Dict as DocOptDict from irc3.plugins.command import command from irc3.utils import IrcString +from psycopg2 import Error from . import DatabasePlugin @@ -56,27 +57,31 @@ class Rape(DatabasePlugin): else: fine = random.randint(1, 500) - # Insert or add fine to database and return total owe - self.cur.execute(''' - insert into - owes (nick, amount) - values - (%s, %s) - on conflict (nick) do update set - amount = owes.amount + excluded.amount - returning - amount - ''', [nick, fine]) - self.con.commit() + try: + # Insert or add fine to database and return total owe + self.cur.execute(''' + insert into + owes (nick, amount) + values + (%s, %s) + on conflict (nick) do update set + amount = owes.amount + excluded.amount + returning + amount + ''', [nick, fine]) + self.con.commit() - # Get reason based on rand value - reason = ('raping', 'being too lewd and getting raped')[rand] + # Get reason based on rand value + reason = ('raping', 'being too lewd and getting raped')[rand] - # Print fine and total owe - self.bot.action(target, - 'fines {nick} \x02${fine}\x02 for {reason}. ' - 'You owe: \x0304${total}\x03' - .format(nick=nick, - fine=fine, - reason=reason, - total=self.cur.fetchone()['amount'])) + # Print fine and total owe + self.bot.action(target, + 'fines {nick} \x02${fine}\x02 for {reason}. ' + 'You owe: \x0304${total}\x03' + .format(nick=nick, + fine=fine, + reason=reason, + total=self.cur.fetchone()['amount'])) + except Error: + # Rollback transaction on error + self.con.rollback() diff --git a/nxy/plugins/seen.py b/nxy/plugins/seen.py index e059c9a..3e471ef 100644 --- a/nxy/plugins/seen.py +++ b/nxy/plugins/seen.py @@ -6,6 +6,7 @@ import irc3 from docopt import Dict as DocOptDict from irc3.plugins.command import command from irc3.utils import IrcString +from psycopg2 import Error from . import DatabasePlugin @@ -52,15 +53,19 @@ class Seen(DatabasePlugin): @irc3.event(r'(?i)^:(?P\S+) PRIVMSG (?P\S+) :(?P.*)') def save(self, mask: str, target: str, msg: str): - # Insert or update if user writes a message - self.cur.execute(''' - insert into - seens (nick, channel, message, seen_at) - values - (%s, %s, %s, %s) - on conflict (nick) do update set - channel = excluded.channel, - seen_at = excluded.seen_at, - message = excluded.message - ''', [IrcString(mask).nick, target, msg, datetime.now()]) - self.con.commit() + try: + # Insert or update if user writes a message + self.cur.execute(''' + insert into + seens (nick, channel, message, seen_at) + values + (%s, %s, %s, %s) + on conflict (nick) do update set + channel = excluded.channel, + seen_at = excluded.seen_at, + message = excluded.message + ''', [IrcString(mask).nick, target, msg, datetime.now()]) + self.con.commit() + except Error: + # Rollback transaction on error + self.con.rollback() diff --git a/nxy/plugins/tell.py b/nxy/plugins/tell.py index b7f4b2d..742a03b 100644 --- a/nxy/plugins/tell.py +++ b/nxy/plugins/tell.py @@ -3,6 +3,7 @@ import irc3 from docopt import Dict as DocOptDict from irc3.plugins.command import command from irc3.utils import IrcString +from psycopg2 import Error from . import DatabasePlugin @@ -49,14 +50,18 @@ class Tell(DatabasePlugin): self.tell_queue[nick] = [] self.tell_queue[nick].append(tell) - # Insert tell into database - self.cur.execute(''' - insert into - tells (from_nick, to_nick, message) - values - (%s, %s, %s) - ''', tell) - self.con.commit() + try: + # Insert tell into database + self.cur.execute(''' + insert into + tells (from_nick, to_nick, message) + values + (%s, %s, %s) + ''', tell) + self.con.commit() + except Error: + # Rollback transaction on error + self.con.rollback() @irc3.event(r'(?i)^:(?P.*) PRIVMSG .* :.*') def check(self, mask: str): @@ -72,11 +77,15 @@ class Tell(DatabasePlugin): # Remove nick from queue del self.tell_queue[nick] - # Remove tells from database - self.cur.execute(''' - delete from - tells - where - to_nick = %s - ''', [nick]) - self.con.commit() + try: + # Remove tells from database + self.cur.execute(''' + delete from + tells + where + to_nick = %s + ''', [nick]) + self.con.commit() + except Error: + # Rollback transaction on error + self.con.rollback() diff --git a/nxy/plugins/timer.py b/nxy/plugins/timer.py index 7a6f375..52c0ca5 100644 --- a/nxy/plugins/timer.py +++ b/nxy/plugins/timer.py @@ -6,6 +6,7 @@ import irc3 from docopt import Dict as DocOptDict from irc3.plugins.command import command from irc3.utils import IrcString +from psycopg2 import Error from . import DatabasePlugin from ..utils import time_delta @@ -51,23 +52,27 @@ class Timer(DatabasePlugin): message = ' '.join(args['']) values = [mask, target, message, delay] - # Insert into database (add now + delta) - self.cur.execute(''' - insert into - timers (mask, target, message, delay, ends_at) - values - (%s, %s, %s, %s, %s) - returning id - ''', values + [datetime.now() + delta]) - self.con.commit() + try: + # Insert into database (add now + delta) + self.cur.execute(''' + insert into + timers (mask, target, message, delay, ends_at) + values + (%s, %s, %s, %s, %s) + returning id + ''', values + [datetime.now() + delta]) + self.con.commit() - # Add delta and id from inserted and start timer - values.extend([delta, self.cur.fetchone()['id']]) - self.start_timer(*values) + # Add delta and id from inserted and start timer + values.extend([delta, self.cur.fetchone()['id']]) + self.start_timer(*values) - # Send notice to user that timer has been set - self.bot.notice(mask.nick, 'Timer in {delay} set: {message}' - .format(delay=delay, message=message)) + # Send notice to user that timer has been set + self.bot.notice(mask.nick, 'Timer in {delay} set: {message}' + .format(delay=delay, message=message)) + except Error: + # Rollback transaction on error + self.con.rollback() def start_timer(self, mask: IrcString, target: IrcString, message: str, delay: str, delta: timedelta, row_id: int): @@ -85,13 +90,17 @@ class Timer(DatabasePlugin): nick=mask.nick, delay=delay)) - # Delete timer from database - self.cur.execute(''' - delete from - timers - where - id = %s - ''', [row_id]) - self.con.commit() + try: + # Delete timer from database + self.cur.execute(''' + delete from + timers + where + id = %s + ''', [row_id]) + self.con.commit() + except Error: + # Rollback transaction on error + self.con.rollback() asyncio.ensure_future(callback()) diff --git a/schema.sql b/schema.sql index acfbe25..39800d9 100644 --- a/schema.sql +++ b/schema.sql @@ -45,26 +45,3 @@ create table if not exists owes ( amount integer not null, unique (nick) ); - - -with ranked_quotes as ( - select - id, - rank() over (partition by nick order by id desc) - from - quotes - where - nick = 'mrhanky' - ) - - delete from - quotes - where - id = ( - select - id - from - ranked_quotes - where - rank = 1 - )