Debugging db migration issues

This commit is contained in:
Collin R
2021-02-22 00:26:34 -08:00
parent 284fff7e36
commit 4305e3f619
4 changed files with 125 additions and 70 deletions

View File

@@ -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

View File

@@ -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 = "<string>"
# name = "<name inside quotes>"
# points = <integer>
# flair_template_id = "<string>"
# flair_template_id = "<id inside quotes>"
#
# These fields are case-sensitive, so enter the level name exactly as you want
# it to appear.

View File

@@ -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'):

View File

@@ -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:
@@ -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
@@ -265,8 +286,10 @@ class Database:
@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()