Debugging db migration issues
This commit is contained in:
@@ -4,7 +4,9 @@ File-specific lists are in loose descending order of priority.
|
|||||||
|
|
||||||
## Current
|
## 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
|
## Bugs
|
||||||
|
|
||||||
|
|||||||
@@ -10,26 +10,13 @@ subreddit = ""
|
|||||||
valid_tags = "Java,Bedrock,Dungeons,Earth,Education,Legacy"
|
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
|
# Reddit Credentials
|
||||||
#
|
#
|
||||||
# See the bot documentation for more information about these fields.
|
# See the bot documentation for more information about these fields.
|
||||||
|
|
||||||
# Some of these fields are sensitive, so once these fields are provided, take
|
# 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.
|
# public sites.
|
||||||
################################################################################
|
################################################################################
|
||||||
|
|
||||||
@@ -40,6 +27,34 @@ username = "REDACTED"
|
|||||||
password = "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
|
# User Levels
|
||||||
#
|
#
|
||||||
@@ -47,9 +62,9 @@ password = "REDACTED"
|
|||||||
# attain. To add a level, add another section with the format:
|
# attain. To add a level, add another section with the format:
|
||||||
#
|
#
|
||||||
# [[levels]]
|
# [[levels]]
|
||||||
# name = "<string>"
|
# name = "<name inside quotes>"
|
||||||
# points = <integer>
|
# 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
|
# These fields are case-sensitive, so enter the level name exactly as you want
|
||||||
# it to appear.
|
# it to appear.
|
||||||
|
|||||||
@@ -119,7 +119,7 @@ class Config:
|
|||||||
obj['levels'].append({
|
obj['levels'].append({
|
||||||
'name': level.name,
|
'name': level.name,
|
||||||
'points': level.points,
|
'points': level.points,
|
||||||
'flair_template_id': level.flair_template_id,
|
'flair_template_id': level.flair_template_id
|
||||||
})
|
})
|
||||||
|
|
||||||
obj['tags'] = ','.join(obj['tags'])
|
obj['tags'] = ','.join(obj['tags'])
|
||||||
@@ -142,10 +142,8 @@ def interactive_config(dest):
|
|||||||
|
|
||||||
print('\n' + ('#' * 80) + '\nCONFIGURING THE BOT\n' + ('#' * 80) + '\n')
|
print('\n' + ('#' * 80) + '\nCONFIGURING THE BOT\n' + ('#' * 80) + '\n')
|
||||||
print('Type a value for each field, then press enter.')
|
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 '
|
print('\nIf a field is specified as optional, then you can skip it by just pressing enter.')
|
||||||
'pressing enter.')
|
print("\nIt is recommended that you skip any fields that you aren't sure about")
|
||||||
print("\nIt is recommended that you skip any fields that you aren't sure "
|
|
||||||
'about')
|
|
||||||
|
|
||||||
print('\n*** Core Configuration ***\n')
|
print('\n*** Core Configuration ***\n')
|
||||||
configvals['core']['subreddit'] = input('name of subreddit to monitor? ')
|
configvals['core']['subreddit'] = input('name of subreddit to monitor? ')
|
||||||
@@ -154,8 +152,8 @@ def interactive_config(dest):
|
|||||||
configvals['filepaths']['log'] = input('log filepath? (optional) ')
|
configvals['filepaths']['log'] = input('log filepath? (optional) ')
|
||||||
|
|
||||||
print('\n*** Website Links ***\n')
|
print('\n*** Website Links ***\n')
|
||||||
print('These values should only be provided if you have valid URLs for '
|
print('These values should only be provided if you have valid URLs for websites that provide '
|
||||||
'websites that provide these services for your subreddit.\n')
|
'these services for your subreddit.\n')
|
||||||
configvals['links']['feedback'] = input('feedback webpage URL? (optional) ')
|
configvals['links']['feedback'] = input('feedback webpage URL? (optional) ')
|
||||||
configvals['links']['scoreboard'] = input('scoreboard 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? ')
|
configvals['credentials']['password'] = input('bot password? ')
|
||||||
|
|
||||||
print('\n*** Flair Levels ***\n')
|
print('\n*** Flair Levels ***\n')
|
||||||
print('These fields will determine the different levels that your '
|
print('These fields will determine the different levels that your subreddit users can achieve '
|
||||||
'subreddit users can achieve by earning points.')
|
'by earning points.')
|
||||||
print('\nFor each level, you should provide...')
|
print('\nFor each level, you should provide...')
|
||||||
print("\t- Level name: the text that appears in the user's flair")
|
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')
|
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('\t subreddit to be used for this level flair')
|
||||||
print('\nThese may be provided in any order; the bot will sort them later.')
|
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('\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 '
|
print('\nNote that at the moment, providing a level points value of zero will not set a '
|
||||||
'will not set a default flair, because users must solve at least one '
|
'default flair, because users must solve at least one issue before the bot will keep '
|
||||||
'issue before the bot will keep track of their points and set their '
|
'track of their points and set their flair for the first time.')
|
||||||
'flair for the first time.')
|
print('\nFor any more questions, please refer to the README on the Github page.')
|
||||||
print('\nFor any more questions, please refer to the README on the Github '
|
|
||||||
'page.')
|
|
||||||
|
|
||||||
response = 'y'
|
response = 'y'
|
||||||
while response.lower().startswith('y'):
|
while response.lower().startswith('y'):
|
||||||
|
|||||||
@@ -22,16 +22,17 @@ def transaction(func):
|
|||||||
self.cursor = self.conn.cursor()
|
self.cursor = self.conn.cursor()
|
||||||
created_conn = True
|
created_conn = True
|
||||||
|
|
||||||
try:
|
|
||||||
return_value = func(self, *args, **kwargs)
|
return_value = func(self, *args, **kwargs)
|
||||||
except Exception as e:
|
# try:
|
||||||
if self.conn.in_transaction:
|
# return_value = func(self, *args, **kwargs)
|
||||||
self.conn.rollback()
|
# except Exception as e:
|
||||||
if created_conn:
|
# if self.conn.in_transaction:
|
||||||
self.cursor.close()
|
# self.conn.rollback()
|
||||||
self.conn.close()
|
# if created_conn:
|
||||||
self.cursor = self.conn = None
|
# self.cursor.close()
|
||||||
raise e
|
# self.conn.close()
|
||||||
|
# self.cursor = self.conn = None
|
||||||
|
# raise e
|
||||||
|
|
||||||
if self.conn.in_transaction:
|
if self.conn.in_transaction:
|
||||||
self.conn.commit()
|
self.conn.commit()
|
||||||
@@ -51,10 +52,10 @@ def transaction(func):
|
|||||||
class DatabaseVersion:
|
class DatabaseVersion:
|
||||||
|
|
||||||
PRE_RELEASE_NAME_ORDER_NUMBER = {
|
PRE_RELEASE_NAME_ORDER_NUMBER = {
|
||||||
None: None,
|
None: 0,
|
||||||
'alpha': 0,
|
'alpha': 1,
|
||||||
'beta': 1,
|
'beta': 2,
|
||||||
'rc': 2
|
'rc': 3
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self, major, minor, patch, pre_release_name=None, pre_release_number=None):
|
def __init__(self, major, minor, patch, pre_release_name=None, pre_release_number=None):
|
||||||
@@ -88,9 +89,10 @@ class DatabaseVersion:
|
|||||||
|
|
||||||
class Database:
|
class Database:
|
||||||
|
|
||||||
# LATEST_VERSION = DatabaseVersion(0, 2, 0, None, None)
|
|
||||||
LATEST_VERSION = DatabaseVersion(0, 2, 0)
|
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 = {
|
SCHEMA_VERSION_STATEMENTS = {
|
||||||
DatabaseVersion(0, 1, 0): [
|
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
|
ALTER TABLE redditor_points RENAME TO redditor
|
||||||
''',
|
''',
|
||||||
'''
|
'''
|
||||||
@@ -160,6 +159,7 @@ class Database:
|
|||||||
self._run_migrations()
|
self._run_migrations()
|
||||||
logging.info('Successfully created database')
|
logging.info('Successfully created database')
|
||||||
else:
|
else:
|
||||||
|
logging.info(f'Using existing database: {self.path}')
|
||||||
current_version = self._get_current_version()
|
current_version = self._get_current_version()
|
||||||
if current_version != self.LATEST_VERSION:
|
if current_version != self.LATEST_VERSION:
|
||||||
logging.info('Newer database version exists; migrating...')
|
logging.info('Newer database version exists; migrating...')
|
||||||
@@ -171,15 +171,34 @@ class Database:
|
|||||||
if not current_version:
|
if not current_version:
|
||||||
current_version = DatabaseVersion(0, 0, 0)
|
current_version = DatabaseVersion(0, 0, 0)
|
||||||
logging.info(f'Current database version: {current_version}')
|
logging.info(f'Current database version: {current_version}')
|
||||||
|
|
||||||
versions = sorted(v for v in self.SCHEMA_VERSION_STATEMENTS if current_version < v)
|
versions = sorted(v for v in self.SCHEMA_VERSION_STATEMENTS if current_version < v)
|
||||||
for v in versions:
|
for v in versions:
|
||||||
logging.info(f'Beginning migration to version: {v}...')
|
logging.info(f'Beginning migration to version: {v}...')
|
||||||
for sql_stmt in self.SCHEMA_VERSION_STATEMENTS[v]:
|
for sql_stmt in self.SCHEMA_VERSION_STATEMENTS[v]:
|
||||||
self.cursor.execute(sql_stmt)
|
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')
|
logging.info(f'Successfully completed migration')
|
||||||
|
|
||||||
@transaction
|
@transaction
|
||||||
def _get_current_version(self):
|
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'")
|
self.cursor.execute("SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'bot_version'")
|
||||||
has_version_table = (self.cursor.rowcount == 1)
|
has_version_table = (self.cursor.rowcount == 1)
|
||||||
if not has_version_table:
|
if not has_version_table:
|
||||||
@@ -251,8 +270,10 @@ class Database:
|
|||||||
@transaction
|
@transaction
|
||||||
def add_back_point_for_solution(self, submission, solver):
|
def add_back_point_for_solution(self, submission, solver):
|
||||||
self._update_points(solver, 1)
|
self._update_points(solver, 1)
|
||||||
submission_rowid = self._get_rowid_from_reddit_id('submission', submission)
|
# submission_rowid = self._get_rowid_from_reddit_id('submission', submission)
|
||||||
author_rowid = self._get_rowid_from_reddit_id('redditor', solver)
|
# 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}
|
params = {'submission_rowid': submission_rowid, 'author_rowid': author_rowid}
|
||||||
update_stmt = '''
|
update_stmt = '''
|
||||||
UPDATE solution
|
UPDATE solution
|
||||||
@@ -265,8 +286,10 @@ class Database:
|
|||||||
@transaction
|
@transaction
|
||||||
def remove_point_and_delete_solution(self, submission, solver):
|
def remove_point_and_delete_solution(self, submission, solver):
|
||||||
params = {
|
params = {
|
||||||
'submission_rowid': self._get_rowid_from_reddit_id('submission', submission),
|
# 'submission_rowid': self._get_rowid_from_reddit_id('submission', submission),
|
||||||
'author_rowid': self._get_rowid_from_reddit_id('redditor', solver)
|
# '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_stmt = '''
|
||||||
DELETE FROM solution
|
DELETE FROM solution
|
||||||
@@ -296,10 +319,29 @@ class Database:
|
|||||||
|
|
||||||
### Private Methods ###
|
### 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
|
@transaction
|
||||||
def _get_rowid_from_reddit_id(self, table_name, reddit_object):
|
def _get_rowid_from_reddit_id(self, stmt, params):
|
||||||
params = {'table_name': table_name, 'reddit_id': reddit_object.id}
|
self.cursor.execute(stmt, params)
|
||||||
self.cursor.execute('SELECT rowid FROM :table_name WHERE id = :reddit_id', params)
|
|
||||||
row = self.cursor.fetchone()
|
row = self.cursor.fetchone()
|
||||||
return row['rowid'] if row else None
|
return row['rowid'] if row else None
|
||||||
|
|
||||||
@@ -328,10 +370,10 @@ class Database:
|
|||||||
|
|
||||||
@transaction
|
@transaction
|
||||||
def _add_solution(self, submission, solver, comment, chosen_by_comment):
|
def _add_solution(self, submission, solver, comment, chosen_by_comment):
|
||||||
submission_rowid = self._get_rowid_from_reddit_id('submission', submission)
|
submission_rowid = self._get_submission_rowid(submission)
|
||||||
author_rowid = self._get_rowid_from_reddit_id('redditor', solver)
|
author_rowid = self._get_redditor_rowid(solver)
|
||||||
comment_rowid = self._get_rowid_from_reddit_id('comment', comment)
|
comment_rowid = self._get_comment_rowid(comment)
|
||||||
chosen_by_comment_rowid = self._get_rowid_from_reddit_id('comment', chosen_by_comment)
|
chosen_by_comment_rowid = self._get_comment_rowid(chosen_by_comment)
|
||||||
params = {
|
params = {
|
||||||
'submission_rowid': submission_rowid,
|
'submission_rowid': submission_rowid,
|
||||||
'author_rowid': author_rowid,
|
'author_rowid': author_rowid,
|
||||||
@@ -347,9 +389,9 @@ class Database:
|
|||||||
|
|
||||||
@transaction
|
@transaction
|
||||||
def _soft_remove_solution(self, submission, solver, removed_by_comment):
|
def _soft_remove_solution(self, submission, solver, removed_by_comment):
|
||||||
submission_rowid = self._get_rowid_from_reddit_id('submission', submission)
|
submission_rowid = self._get_submission_rowid(submission)
|
||||||
author_rowid = self._get_rowid_from_reddit_id('redditor', solver)
|
author_rowid = self._get_redditor_rowid(solver)
|
||||||
removed_by_comment_rowid = self._get_rowid_from_reddit_id('comment', removed_by_comment)
|
removed_by_comment_rowid = self._get_comment_rowid(removed_by_comment)
|
||||||
params = {
|
params = {
|
||||||
'submission_rowid': submission_rowid,
|
'submission_rowid': submission_rowid,
|
||||||
'author_rowid': author_rowid,
|
'author_rowid': author_rowid,
|
||||||
@@ -387,6 +429,6 @@ class Database:
|
|||||||
### Utility ###
|
### Utility ###
|
||||||
|
|
||||||
|
|
||||||
def reddit_datetime_to_iso(datetime):
|
def reddit_datetime_to_iso(timestamp):
|
||||||
return datetime.datetime.utcfromtimestamp(datetime).isoformat()
|
return datetime.datetime.utcfromtimestamp(timestamp).isoformat()
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user