2020-05-10 00:13:20 +00:00
|
|
|
import psycopg2
|
|
|
|
import re
|
|
|
|
import sys
|
|
|
|
from urllib.parse import urlparse
|
|
|
|
from os import listdir
|
|
|
|
from os.path import isfile, join
|
|
|
|
from psycopg2 import pool
|
|
|
|
from flask import current_app
|
|
|
|
from flask import g
|
|
|
|
|
2020-05-10 23:59:30 +00:00
|
|
|
from capsulflask.db_model import DBModel
|
2020-05-10 00:13:20 +00:00
|
|
|
|
|
|
|
def init_app(app):
|
|
|
|
databaseUrl = urlparse(app.config['DATABASE_URL'])
|
|
|
|
|
|
|
|
app.config['PSYCOPG2_CONNECTION_POOL'] = psycopg2.pool.SimpleConnectionPool(
|
|
|
|
1,
|
|
|
|
20,
|
|
|
|
user = databaseUrl.username,
|
|
|
|
password = databaseUrl.password,
|
|
|
|
host = databaseUrl.hostname,
|
|
|
|
port = databaseUrl.port,
|
|
|
|
database = databaseUrl.path[1:]
|
|
|
|
)
|
|
|
|
|
|
|
|
schemaMigrations = {}
|
|
|
|
schemaMigrationsPath = join(app.root_path, 'schema_migrations')
|
2020-05-16 04:19:01 +00:00
|
|
|
app.logger.info("loading schema migration scripts from {}".format(schemaMigrationsPath))
|
2020-05-10 00:13:20 +00:00
|
|
|
for filename in listdir(schemaMigrationsPath):
|
2020-05-13 05:28:53 +00:00
|
|
|
result = re.search(r"^\d+_(up|down)", filename)
|
|
|
|
if not result:
|
2020-05-16 04:19:01 +00:00
|
|
|
app.logger.error(f"schemaVersion {filename} must match ^\\d+_(up|down). exiting.")
|
2020-05-13 05:28:53 +00:00
|
|
|
exit(1)
|
|
|
|
key = result.group()
|
2020-05-10 00:13:20 +00:00
|
|
|
with open(join(schemaMigrationsPath, filename), 'rb') as file:
|
|
|
|
schemaMigrations[key] = file.read().decode("utf8")
|
|
|
|
|
2020-05-10 01:36:14 +00:00
|
|
|
connection = app.config['PSYCOPG2_CONNECTION_POOL'].getconn()
|
2020-05-10 00:13:20 +00:00
|
|
|
|
|
|
|
hasSchemaVersionTable = False
|
|
|
|
actionWasTaken = False
|
|
|
|
schemaVersion = 0
|
2020-05-20 03:48:59 +00:00
|
|
|
desiredSchemaVersion = 3
|
2020-05-10 00:13:20 +00:00
|
|
|
|
2020-05-10 01:36:14 +00:00
|
|
|
cursor = connection.cursor()
|
2020-05-10 00:13:20 +00:00
|
|
|
|
|
|
|
cursor.execute("""
|
|
|
|
SELECT table_name, table_schema FROM information_schema.tables WHERE table_schema = '{}'
|
|
|
|
""".format(app.config['DATABASE_SCHEMA']))
|
|
|
|
|
|
|
|
rows = cursor.fetchall()
|
|
|
|
for row in rows:
|
|
|
|
if row[0] == "schemaversion":
|
|
|
|
hasSchemaVersionTable = True
|
|
|
|
|
|
|
|
if hasSchemaVersionTable == False:
|
2020-05-16 04:19:01 +00:00
|
|
|
app.logger.info("no table named schemaversion found in the {} schema. running migration 01_up".format(app.config['DATABASE_SCHEMA']))
|
2020-05-10 00:13:20 +00:00
|
|
|
try:
|
|
|
|
cursor.execute(schemaMigrations["01_up"])
|
2020-05-10 01:36:14 +00:00
|
|
|
connection.commit()
|
2020-05-10 00:13:20 +00:00
|
|
|
except:
|
2020-05-16 04:19:01 +00:00
|
|
|
app.logger.error("unable to create the schemaversion table because: {}".format(my_exec_info_message(sys.exc_info())))
|
2020-05-10 00:13:20 +00:00
|
|
|
exit(1)
|
|
|
|
actionWasTaken = True
|
|
|
|
|
|
|
|
cursor.execute("SELECT Version FROM schemaversion")
|
|
|
|
schemaVersion = cursor.fetchall()[0][0]
|
2020-05-10 00:22:25 +00:00
|
|
|
|
|
|
|
if schemaVersion > desiredSchemaVersion:
|
2020-05-16 04:19:01 +00:00
|
|
|
app.logger.critical("schemaVersion ({}) > desiredSchemaVersion ({}). schema downgrades are not supported yet. exiting.".format(
|
2020-05-10 00:22:25 +00:00
|
|
|
schemaVersion, desiredSchemaVersion
|
|
|
|
))
|
|
|
|
exit(1)
|
|
|
|
|
2020-05-10 00:13:20 +00:00
|
|
|
while schemaVersion < desiredSchemaVersion:
|
|
|
|
migrationKey = "%02d_up" % (schemaVersion+1)
|
2020-05-16 04:19:01 +00:00
|
|
|
app.logger.info("schemaVersion ({}) < desiredSchemaVersion ({}). running migration {}".format(
|
2020-05-10 00:13:20 +00:00
|
|
|
schemaVersion, desiredSchemaVersion, migrationKey
|
|
|
|
))
|
|
|
|
try:
|
|
|
|
cursor.execute(schemaMigrations[migrationKey])
|
2020-05-10 01:36:14 +00:00
|
|
|
connection.commit()
|
2020-05-10 00:13:20 +00:00
|
|
|
except KeyError:
|
2020-05-16 04:19:01 +00:00
|
|
|
app.logger.critical("missing schema migration script: {}_xyz.sql".format(migrationKey))
|
2020-05-10 00:13:20 +00:00
|
|
|
exit(1)
|
|
|
|
except:
|
2020-05-16 04:19:01 +00:00
|
|
|
app.logger.critical("unable to execute the schema migration {} because: {}".format(migrationKey, my_exec_info_message(sys.exc_info())))
|
2020-05-10 00:13:20 +00:00
|
|
|
exit(1)
|
|
|
|
actionWasTaken = True
|
|
|
|
|
|
|
|
schemaVersion += 1
|
|
|
|
cursor.execute("SELECT Version FROM schemaversion")
|
|
|
|
versionFromDatabase = cursor.fetchall()[0][0]
|
|
|
|
|
|
|
|
if schemaVersion != versionFromDatabase:
|
2020-05-16 04:19:01 +00:00
|
|
|
app.logger.critical("incorrect schema version value \"{}\" after running migration {}, expected \"{}\". exiting.".format(
|
2020-05-10 00:13:20 +00:00
|
|
|
versionFromDatabase,
|
|
|
|
migrationKey,
|
|
|
|
schemaVersion
|
|
|
|
))
|
|
|
|
exit(1)
|
|
|
|
|
|
|
|
cursor.close()
|
|
|
|
|
2020-05-10 01:36:14 +00:00
|
|
|
app.config['PSYCOPG2_CONNECTION_POOL'].putconn(connection)
|
2020-05-10 00:13:20 +00:00
|
|
|
|
2020-05-16 04:19:01 +00:00
|
|
|
app.logger.info("{} current schemaVersion: \"{}\"".format(
|
2020-05-10 00:22:25 +00:00
|
|
|
("schema migration completed." if actionWasTaken else "schema is already up to date. "), schemaVersion
|
2020-05-10 00:13:20 +00:00
|
|
|
))
|
|
|
|
|
|
|
|
app.teardown_appcontext(close_db)
|
|
|
|
|
|
|
|
|
2020-05-10 01:36:14 +00:00
|
|
|
def get_model():
|
2020-05-12 01:34:12 +00:00
|
|
|
if 'db_model' not in g:
|
2020-05-10 01:36:14 +00:00
|
|
|
connection = current_app.config['PSYCOPG2_CONNECTION_POOL'].getconn()
|
|
|
|
cursor = connection.cursor()
|
2020-05-10 23:59:30 +00:00
|
|
|
g.db_model = DBModel(connection, cursor)
|
|
|
|
return g.db_model
|
2020-05-10 00:13:20 +00:00
|
|
|
|
|
|
|
|
|
|
|
def close_db(e=None):
|
2020-05-12 01:34:12 +00:00
|
|
|
db_model = g.pop("db_model", None)
|
2020-05-10 00:13:20 +00:00
|
|
|
|
2020-05-12 01:34:12 +00:00
|
|
|
if db_model is not None:
|
|
|
|
db_model.cursor.close()
|
|
|
|
current_app.config['PSYCOPG2_CONNECTION_POOL'].putconn(db_model.connection)
|
2020-05-10 00:13:20 +00:00
|
|
|
|
|
|
|
def my_exec_info_message(exec_info):
|
2020-05-20 03:48:59 +00:00
|
|
|
return "{}: {}".format(".".join([exec_info[0].__module__, exec_info[0].__name__]), exec_info[1])
|