From 4305e3f6195bc9ae00fb74632b68dddc6f53974e Mon Sep 17 00:00:00 2001 From: Collin R Date: Mon, 22 Feb 2021 00:26:34 -0800 Subject: [PATCH] Debugging db migration issues --- docs/TODO.md | 4 +- pointsbot.sample.toml | 51 ++++++++++++------- pointsbot/config.py | 26 ++++------ pointsbot/database.py | 114 +++++++++++++++++++++++++++++------------- 4 files changed, 125 insertions(+), 70 deletions(-) diff --git a/docs/TODO.md b/docs/TODO.md index 34654cd..54975a2 100644 --- a/docs/TODO.md +++ b/docs/TODO.md @@ -4,7 +4,9 @@ File-specific lists are in loose descending order of priority. ## Current -n/a +* [ ] Change commands from !solved and /solved to !helped and /helped +* [ ] Allow multiple users to be awarded points on a single post + * So just check whether each user is already awarded a point for a given post ## Bugs diff --git a/pointsbot.sample.toml b/pointsbot.sample.toml index 02dad3a..56b47af 100644 --- a/pointsbot.sample.toml +++ b/pointsbot.sample.toml @@ -1,6 +1,6 @@ ################################################################################ # Core -# +# # These are the primary fields needed to run the bot. ################################################################################ @@ -10,26 +10,13 @@ subreddit = "" valid_tags = "Java,Bedrock,Dungeons,Earth,Education,Legacy" -################################################################################ -# Filepaths -# -# Any filepaths needed by the bot. Can be relative or absolute paths, or just -# filenames. -################################################################################ - -[filepaths] -# The name of the SQLite database file to use. This value is optional; if not -# specified, a default filepath will be used. -database = "" - - ################################################################################ # Reddit Credentials -# +# # See the bot documentation for more information about these fields. # Some of these fields are sensitive, so once these fields are provided, take -# care not to upload or distribute this file through unsecure channels, or to +# care not to upload or distribute this file through insecure channels, or to # public sites. ################################################################################ @@ -40,6 +27,34 @@ username = "REDACTED" password = "REDACTED" +################################################################################ +# Filepaths +# +# Any filepaths needed by the bot. Can be relative or absolute paths, or just +# filenames. +################################################################################ + +[filepaths] +# The name/path of the SQLite database file to use. This value is optional; if not +# specified, a default filepath will be used. +database = "" +# The name/path of the log file to use. This value is optional; if not specified, +# a default filepath will be used. +log = "" + + +################################################################################ +# Links +# +# Any links used in the bot's reply. If you do not need to use these links, +# leave these fields blank. +################################################################################ + +[links] +feedback = "" +scoreboard = "" + + ################################################################################ # User Levels # @@ -47,9 +62,9 @@ password = "REDACTED" # attain. To add a level, add another section with the format: # # [[levels]] -# name = "" +# name = "" # points = -# flair_template_id = "" +# flair_template_id = "" # # These fields are case-sensitive, so enter the level name exactly as you want # it to appear. diff --git a/pointsbot/config.py b/pointsbot/config.py index 21a8479..f38aac8 100644 --- a/pointsbot/config.py +++ b/pointsbot/config.py @@ -119,7 +119,7 @@ class Config: obj['levels'].append({ 'name': level.name, 'points': level.points, - 'flair_template_id': level.flair_template_id, + 'flair_template_id': level.flair_template_id }) obj['tags'] = ','.join(obj['tags']) @@ -142,10 +142,8 @@ def interactive_config(dest): print('\n' + ('#' * 80) + '\nCONFIGURING THE BOT\n' + ('#' * 80) + '\n') print('Type a value for each field, then press enter.') - print('\nIf a field is specified as optional, then you can skip it by just ' - 'pressing enter.') - print("\nIt is recommended that you skip any fields that you aren't sure " - 'about') + print('\nIf a field is specified as optional, then you can skip it by just pressing enter.') + print("\nIt is recommended that you skip any fields that you aren't sure about") print('\n*** Core Configuration ***\n') configvals['core']['subreddit'] = input('name of subreddit to monitor? ') @@ -154,8 +152,8 @@ def interactive_config(dest): configvals['filepaths']['log'] = input('log filepath? (optional) ') print('\n*** Website Links ***\n') - print('These values should only be provided if you have valid URLs for ' - 'websites that provide these services for your subreddit.\n') + print('These values should only be provided if you have valid URLs for websites that provide ' + 'these services for your subreddit.\n') configvals['links']['feedback'] = input('feedback webpage URL? (optional) ') configvals['links']['scoreboard'] = input('scoreboard webpage URL? (optional) ') @@ -166,8 +164,8 @@ def interactive_config(dest): configvals['credentials']['password'] = input('bot password? ') print('\n*** Flair Levels ***\n') - print('These fields will determine the different levels that your ' - 'subreddit users can achieve by earning points.') + print('These fields will determine the different levels that your subreddit users can achieve ' + 'by earning points.') print('\nFor each level, you should provide...') print("\t- Level name: the text that appears in the user's flair") print('\t- Level points: the number of points needed to reach the level') @@ -175,12 +173,10 @@ def interactive_config(dest): print('\t subreddit to be used for this level flair') print('\nThese may be provided in any order; the bot will sort them later.') print('\nDo not provide more than one level with the same number of points.') - print('\nNote that at the moment, providing a level points value of zero ' - 'will not set a default flair, because users must solve at least one ' - 'issue before the bot will keep track of their points and set their ' - 'flair for the first time.') - print('\nFor any more questions, please refer to the README on the Github ' - 'page.') + print('\nNote that at the moment, providing a level points value of zero will not set a ' + 'default flair, because users must solve at least one issue before the bot will keep ' + 'track of their points and set their flair for the first time.') + print('\nFor any more questions, please refer to the README on the Github page.') response = 'y' while response.lower().startswith('y'): diff --git a/pointsbot/database.py b/pointsbot/database.py index 92870db..0a66818 100644 --- a/pointsbot/database.py +++ b/pointsbot/database.py @@ -22,16 +22,17 @@ def transaction(func): self.cursor = self.conn.cursor() created_conn = True - try: - return_value = func(self, *args, **kwargs) - except Exception as e: - if self.conn.in_transaction: - self.conn.rollback() - if created_conn: - self.cursor.close() - self.conn.close() - self.cursor = self.conn = None - raise e + return_value = func(self, *args, **kwargs) + # try: + # return_value = func(self, *args, **kwargs) + # except Exception as e: + # if self.conn.in_transaction: + # self.conn.rollback() + # if created_conn: + # self.cursor.close() + # self.conn.close() + # self.cursor = self.conn = None + # raise e if self.conn.in_transaction: self.conn.commit() @@ -51,10 +52,10 @@ def transaction(func): class DatabaseVersion: PRE_RELEASE_NAME_ORDER_NUMBER = { - None: None, - 'alpha': 0, - 'beta': 1, - 'rc': 2 + None: 0, + 'alpha': 1, + 'beta': 2, + 'rc': 3 } def __init__(self, major, minor, patch, pre_release_name=None, pre_release_number=None): @@ -88,9 +89,10 @@ class DatabaseVersion: class Database: - # LATEST_VERSION = DatabaseVersion(0, 2, 0, None, None) LATEST_VERSION = DatabaseVersion(0, 2, 0) + # TODO now that I'm separating these statements by version, I could probably make these + # scripts instead of lists of individual statements... SCHEMA_VERSION_STATEMENTS = { DatabaseVersion(0, 1, 0): [ ''' @@ -112,9 +114,6 @@ class Database: ) ''', ''' - INSERT OR IGNORE INTO bot_version (major, minor, patch) VALUES (0, 2, 0) - ''', - ''' ALTER TABLE redditor_points RENAME TO redditor ''', ''' @@ -160,6 +159,7 @@ class Database: self._run_migrations() logging.info('Successfully created database') else: + logging.info(f'Using existing database: {self.path}') current_version = self._get_current_version() if current_version != self.LATEST_VERSION: logging.info('Newer database version exists; migrating...') @@ -171,15 +171,34 @@ class Database: if not current_version: current_version = DatabaseVersion(0, 0, 0) logging.info(f'Current database version: {current_version}') + versions = sorted(v for v in self.SCHEMA_VERSION_STATEMENTS if current_version < v) for v in versions: logging.info(f'Beginning migration to version: {v}...') for sql_stmt in self.SCHEMA_VERSION_STATEMENTS[v]: self.cursor.execute(sql_stmt) + if DatabaseVersion(0, 1, 0) < v: + # Only update bot_version table starting at version 0.2.0 + self.cursor.execute('DELETE FROM bot_version') + params = { + 'major': v.major, + 'minor': v.minor, + 'patch': v.patch, + 'pre_release_name': v.pre_release_name, + 'pre_release_number': v.pre_release_number + } + insert_stmt = ''' + INSERT INTO bot_version (major, minor, patch, pre_release_name, pre_release_number) + VALUES (:major, :minor, :patch, :pre_release_name, :pre_release_number) + ''' + self.cursor.execute(insert_stmt, params) logging.info(f'Successfully completed migration') @transaction def _get_current_version(self): + # self.cursor.execute('select * from sqlite_master') + # for row in self.cursor.fetchmany(): + # logging.info(tuple(row)) self.cursor.execute("SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'bot_version'") has_version_table = (self.cursor.rowcount == 1) if not has_version_table: @@ -226,7 +245,7 @@ class Database: self.cursor.execute(select_stmt, {'submission_id': submission.id, 'author_id': solver.id}) row = self.cursor.fetchone() return row and row['num_solutions'] > 0 - + def add_point_for_solution(self, submission, solver, solution_comment, chooser, chosen_by_comment): self._add_submission(submission) self._add_comment(solution_comment, solver) @@ -251,8 +270,10 @@ class Database: @transaction def add_back_point_for_solution(self, submission, solver): self._update_points(solver, 1) - submission_rowid = self._get_rowid_from_reddit_id('submission', submission) - author_rowid = self._get_rowid_from_reddit_id('redditor', solver) + # submission_rowid = self._get_rowid_from_reddit_id('submission', submission) + # author_rowid = self._get_rowid_from_reddit_id('redditor', solver) + submission_rowid = self._get_submission_rowid(submission) + author_rowid = self._get_redditor_rowid(solver) params = {'submission_rowid': submission_rowid, 'author_rowid': author_rowid} update_stmt = ''' UPDATE solution @@ -261,12 +282,14 @@ class Database: AND author_rowid = :author_rowid ''' return self.cursor.execute(update_stmt, params) - + @transaction def remove_point_and_delete_solution(self, submission, solver): params = { - 'submission_rowid': self._get_rowid_from_reddit_id('submission', submission), - 'author_rowid': self._get_rowid_from_reddit_id('redditor', solver) + # 'submission_rowid': self._get_rowid_from_reddit_id('submission', submission), + # 'author_rowid': self._get_rowid_from_reddit_id('redditor', solver) + 'submission_rowid': self._get_submission_rowid(submission), + 'author_rowid': self._get_redditor_rowid(solver) } delete_stmt = ''' DELETE FROM solution @@ -296,10 +319,29 @@ class Database: ### Private Methods ### + # @transaction + # def _get_rowid_from_reddit_id(self, table_name, reddit_object): + # params = {'table_name': table_name, 'reddit_id': reddit_object.id} + # self.cursor.execute('SELECT rowid FROM :table_name WHERE id = :reddit_id', params) + # row = self.cursor.fetchone() + # return row['rowid'] if row else None + + # @transaction + def _get_submission_rowid(self, submission): + return self._get_rowid_from_reddit_id('SELECT rowid FROM submission WHERE id = :reddit_id', {'reddit_id': submission.id}) + # self.cursor.execute('SELECT rowid FROM submission WHERE id = :reddit_id', {'reddit_id': submission.id}) + # row = self.cursor.fetchone() + # return row['rowid'] if row else None + + def _get_comment_rowid(self, comment): + return self._get_rowid_from_reddit_id('SELECT rowid FROM comment WHERE id = :reddit_id', {'reddit_id': comment.id}) + + def _get_redditor_rowid(self, redditor): + return self._get_rowid_from_reddit_id('SELECT rowid FROM redditor WHERE id = :reddit_id', {'reddit_id': redditor.id}) + @transaction - def _get_rowid_from_reddit_id(self, table_name, reddit_object): - params = {'table_name': table_name, 'reddit_id': reddit_object.id} - self.cursor.execute('SELECT rowid FROM :table_name WHERE id = :reddit_id', params) + def _get_rowid_from_reddit_id(self, stmt, params): + self.cursor.execute(stmt, params) row = self.cursor.fetchone() return row['rowid'] if row else None @@ -328,10 +370,10 @@ class Database: @transaction def _add_solution(self, submission, solver, comment, chosen_by_comment): - submission_rowid = self._get_rowid_from_reddit_id('submission', submission) - author_rowid = self._get_rowid_from_reddit_id('redditor', solver) - comment_rowid = self._get_rowid_from_reddit_id('comment', comment) - chosen_by_comment_rowid = self._get_rowid_from_reddit_id('comment', chosen_by_comment) + submission_rowid = self._get_submission_rowid(submission) + author_rowid = self._get_redditor_rowid(solver) + comment_rowid = self._get_comment_rowid(comment) + chosen_by_comment_rowid = self._get_comment_rowid(chosen_by_comment) params = { 'submission_rowid': submission_rowid, 'author_rowid': author_rowid, @@ -347,9 +389,9 @@ class Database: @transaction def _soft_remove_solution(self, submission, solver, removed_by_comment): - submission_rowid = self._get_rowid_from_reddit_id('submission', submission) - author_rowid = self._get_rowid_from_reddit_id('redditor', solver) - removed_by_comment_rowid = self._get_rowid_from_reddit_id('comment', removed_by_comment) + submission_rowid = self._get_submission_rowid(submission) + author_rowid = self._get_redditor_rowid(solver) + removed_by_comment_rowid = self._get_comment_rowid(removed_by_comment) params = { 'submission_rowid': submission_rowid, 'author_rowid': author_rowid, @@ -387,6 +429,6 @@ class Database: ### Utility ### -def reddit_datetime_to_iso(datetime): - return datetime.datetime.utcfromtimestamp(datetime).isoformat() +def reddit_datetime_to_iso(timestamp): + return datetime.datetime.utcfromtimestamp(timestamp).isoformat()