From ddf5ed01a21b5bdbf6efb3d5e629b9fe97f8d579 Mon Sep 17 00:00:00 2001 From: s3lph Date: Sun, 8 Dec 2024 00:05:28 +0100 Subject: [PATCH] refactor(db): greatly simplify database migrations --- matemat/db/migrations.py | 55 ++- matemat/db/schemas.py | 764 ----------------------------- matemat/db/test/test_migrations.py | 203 +------- matemat/db/test/test_wrapper.py | 2 +- matemat/db/wrapper.py | 55 +-- 5 files changed, 75 insertions(+), 1004 deletions(-) delete mode 100644 matemat/db/schemas.py diff --git a/matemat/db/migrations.py b/matemat/db/migrations.py index 286f805..fd9868e 100644 --- a/matemat/db/migrations.py +++ b/matemat/db/migrations.py @@ -4,7 +4,44 @@ from typing import Dict import sqlite3 -def migrate_schema_1_to_2(c: sqlite3.Cursor): +def migrate_schema_1(c: sqlite3.Cursor): + c.execute(''' + CREATE TABLE users ( + user_id INTEGER PRIMARY KEY, + username TEXT UNIQUE NOT NULL, + email TEXT DEFAULT NULL, + password TEXT NOT NULL, + touchkey TEXT DEFAULT NULL, + is_admin INTEGER(1) NOT NULL DEFAULT 0, + is_member INTEGER(1) NOT NULL DEFAULT 1, + balance INTEGER(8) NOT NULL DEFAULT 0, + lastchange INTEGER(8) NOT NULL DEFAULT 0 + ) + ''') + c.execute(''' + CREATE TABLE products ( + product_id INTEGER PRIMARY KEY, + name TEXT UNIQUE NOT NULL, + stock INTEGER(8) NOT NULL DEFAULT 0, + price_member INTEGER(8) NOT NULL, + price_non_member INTEGER(8) NOT NULL + ) + ''') + c.execute(''' + CREATE TABLE consumption ( + user_id INTEGER NOT NULL, + product_id INTEGER NOT NULL, + count INTEGER(8) NOT NULL DEFAULT 0, + PRIMARY KEY (user_id, product_id), + FOREIGN KEY (user_id) REFERENCES users(user_id) + ON DELETE CASCADE ON UPDATE CASCADE, + FOREIGN KEY (product_id) REFERENCES products(product_id) + ON DELETE CASCADE ON UPDATE CASCADE + ) + ''') + + +def migrate_schema_2(c: sqlite3.Cursor): # Create missing tables c.execute(''' CREATE TABLE transactions ( @@ -115,7 +152,7 @@ def migrate_schema_1_to_2(c: sqlite3.Cursor): c.execute('DROP TABLE consumption') -def migrate_schema_2_to_3(c: sqlite3.Cursor): +def migrate_schema_3(c: sqlite3.Cursor): # Add missing columns to users table c.execute('ALTER TABLE users ADD COLUMN receipt_pref INTEGER(1) NOT NULL DEFAULT 0') c.execute('''ALTER TABLE users ADD COLUMN created INTEGER(8) NOT NULL DEFAULT 0''') @@ -203,7 +240,7 @@ def migrate_schema_2_to_3(c: sqlite3.Cursor): ''') -def migrate_schema_3_to_4(c: sqlite3.Cursor): +def migrate_schema_4(c: sqlite3.Cursor): # Change receipts schema to allow null for transaction IDs c.execute(''' CREATE TEMPORARY TABLE receipts_temp ( @@ -241,7 +278,7 @@ def migrate_schema_3_to_4(c: sqlite3.Cursor): c.execute('DROP TABLE receipts_temp') -def migrate_schema_4_to_5(c: sqlite3.Cursor): +def migrate_schema_5(c: sqlite3.Cursor): # Change products schema to allow null for stock and add stockable column c.execute(''' CREATE TEMPORARY TABLE products_temp ( @@ -270,7 +307,7 @@ def migrate_schema_4_to_5(c: sqlite3.Cursor): c.execute('DROP TABLE products_temp') -def migrate_schema_5_to_6(c: sqlite3.Cursor): +def migrate_schema_6(c: sqlite3.Cursor): # Add custom_price column c.execute(''' ALTER TABLE products ADD COLUMN @@ -278,7 +315,7 @@ def migrate_schema_5_to_6(c: sqlite3.Cursor): ''') -def migrate_schema_6_to_7(c: sqlite3.Cursor): +def migrate_schema_7(c: sqlite3.Cursor): # Add custom_price column c.execute(''' ALTER TABLE users ADD COLUMN @@ -286,7 +323,7 @@ def migrate_schema_6_to_7(c: sqlite3.Cursor): ''') -def migrate_schema_7_to_8(c: sqlite3.Cursor): +def migrate_schema_8(c: sqlite3.Cursor): # Add ean column c.execute(''' ALTER TABLE products ADD COLUMN ean TEXT DEFAULT NULL @@ -297,7 +334,7 @@ def migrate_schema_7_to_8(c: sqlite3.Cursor): ''') -def migrate_schema_8_to_9(c: sqlite3.Cursor): +def migrate_schema_9(c: sqlite3.Cursor): c.execute(''' CREATE TABLE tokens ( token_id INTEGER PRIMARY KEY, @@ -311,7 +348,7 @@ def migrate_schema_8_to_9(c: sqlite3.Cursor): ''') -def migrate_schema_9_to_10(c: sqlite3.Cursor): +def migrate_schema_10(c: sqlite3.Cursor): c.execute(''' ALTER TABLE users RENAME TO users_old ''') diff --git a/matemat/db/schemas.py b/matemat/db/schemas.py deleted file mode 100644 index 5481fc8..0000000 --- a/matemat/db/schemas.py +++ /dev/null @@ -1,764 +0,0 @@ -from typing import Dict, List - -SCHEMAS: Dict[int, List[str]] = dict() - -SCHEMAS[1] = [ - ''' - CREATE TABLE users ( - user_id INTEGER PRIMARY KEY, - username TEXT UNIQUE NOT NULL, - email TEXT DEFAULT NULL, - password TEXT NOT NULL, - touchkey TEXT DEFAULT NULL, - is_admin INTEGER(1) NOT NULL DEFAULT 0, - is_member INTEGER(1) NOT NULL DEFAULT 1, - balance INTEGER(8) NOT NULL DEFAULT 0, - lastchange INTEGER(8) NOT NULL DEFAULT 0 - ); - ''', - ''' - CREATE TABLE products ( - product_id INTEGER PRIMARY KEY, - name TEXT UNIQUE NOT NULL, - stock INTEGER(8) NOT NULL DEFAULT 0, - price_member INTEGER(8) NOT NULL, - price_non_member INTEGER(8) NOT NULL - ); - ''', - ''' - CREATE TABLE consumption ( - user_id INTEGER NOT NULL, - product_id INTEGER NOT NULL, - count INTEGER(8) NOT NULL DEFAULT 0, - PRIMARY KEY (user_id, product_id), - FOREIGN KEY (user_id) REFERENCES users(user_id) - ON DELETE CASCADE ON UPDATE CASCADE, - FOREIGN KEY (product_id) REFERENCES products(product_id) - ON DELETE CASCADE ON UPDATE CASCADE - ); - '''] - -SCHEMAS[2] = [ - ''' - CREATE TABLE users ( - user_id INTEGER PRIMARY KEY, - username TEXT UNIQUE NOT NULL, - email TEXT DEFAULT NULL, - password TEXT NOT NULL, - touchkey TEXT DEFAULT NULL, - is_admin INTEGER(1) NOT NULL DEFAULT 0, - is_member INTEGER(1) NOT NULL DEFAULT 1, - balance INTEGER(8) NOT NULL DEFAULT 0, - lastchange INTEGER(8) NOT NULL DEFAULT 0 - ); - ''', - ''' - CREATE TABLE products ( - product_id INTEGER PRIMARY KEY, - name TEXT UNIQUE NOT NULL, - stock INTEGER(8) NOT NULL DEFAULT 0, - price_member INTEGER(8) NOT NULL, - price_non_member INTEGER(8) NOT NULL - ); - ''', - ''' - CREATE TABLE transactions ( -- "superclass" of the following 3 tables - ta_id INTEGER PRIMARY KEY, - user_id INTEGER NOT NULL, - value INTEGER(8) NOT NULL, - old_balance INTEGER(8) NOT NULL, - date INTEGER(8) DEFAULT (STRFTIME('%s', 'now')), - FOREIGN KEY (user_id) REFERENCES users(user_id) - ON DELETE CASCADE ON UPDATE CASCADE - ); - ''', - ''' - CREATE TABLE consumptions ( -- transactions involving buying a product - ta_id INTEGER PRIMARY KEY, - product_id INTEGER DEFAULT NULL, - FOREIGN KEY (ta_id) REFERENCES transactions(ta_id) - ON DELETE CASCADE ON UPDATE CASCADE, - FOREIGN KEY (product_id) REFERENCES products(product_id) - ON DELETE SET NULL ON UPDATE CASCADE - ); - ''', - ''' - CREATE TABLE deposits ( -- transactions involving depositing cash - ta_id INTEGER PRIMARY KEY, - FOREIGN KEY (ta_id) REFERENCES transactions(ta_id) - ON DELETE CASCADE ON UPDATE CASCADE - ); - ''', - ''' - CREATE TABLE modifications ( -- transactions involving balance modification by an admin - ta_id INTEGER NOT NULL, - agent_id INTEGER NOT NULL, - reason TEXT DEFAULT NULL, - PRIMARY KEY (ta_id), - FOREIGN KEY (ta_id) REFERENCES transactions(ta_id) - ON DELETE CASCADE ON UPDATE CASCADE, - FOREIGN KEY (agent_id) REFERENCES users(user_id) - ON DELETE CASCADE ON UPDATE CASCADE - ); - '''] - -SCHEMAS[3] = [ - ''' - CREATE TABLE users ( - user_id INTEGER PRIMARY KEY, - username TEXT UNIQUE NOT NULL, - email TEXT DEFAULT NULL, - password TEXT NOT NULL, - touchkey TEXT DEFAULT NULL, - is_admin INTEGER(1) NOT NULL DEFAULT 0, - is_member INTEGER(1) NOT NULL DEFAULT 1, - balance INTEGER(8) NOT NULL DEFAULT 0, - lastchange INTEGER(8) NOT NULL DEFAULT 0, - receipt_pref INTEGER(1) NOT NULL DEFAULT 0, - created INTEGER(8) NOT NULL DEFAULT 0 - ); - ''', - ''' - CREATE TABLE products ( - product_id INTEGER PRIMARY KEY, - name TEXT UNIQUE NOT NULL, - stock INTEGER(8) NOT NULL DEFAULT 0, - price_member INTEGER(8) NOT NULL, - price_non_member INTEGER(8) NOT NULL - ); - ''', - ''' - CREATE TABLE transactions ( -- "superclass" of the following 3 tables - ta_id INTEGER PRIMARY KEY, - user_id INTEGER DEFAULT NULL, - value INTEGER(8) NOT NULL, - old_balance INTEGER(8) NOT NULL, - date INTEGER(8) DEFAULT (STRFTIME('%s', 'now')), - FOREIGN KEY (user_id) REFERENCES users(user_id) - ON DELETE SET NULL ON UPDATE CASCADE - ); - ''', - ''' - CREATE TABLE consumptions ( -- transactions involving buying a product - ta_id INTEGER PRIMARY KEY, - product TEXT NOT NULL, - FOREIGN KEY (ta_id) REFERENCES transactions(ta_id) - ON DELETE CASCADE ON UPDATE CASCADE - ); - ''', - ''' - CREATE TABLE deposits ( -- transactions involving depositing cash - ta_id INTEGER PRIMARY KEY, - FOREIGN KEY (ta_id) REFERENCES transactions(ta_id) - ON DELETE CASCADE ON UPDATE CASCADE - ); - ''', - ''' - CREATE TABLE modifications ( -- transactions involving balance modification by an admin - ta_id INTEGER NOT NULL, - agent TEXT NOT NULL, - reason TEXT DEFAULT NULL, - PRIMARY KEY (ta_id), - FOREIGN KEY (ta_id) REFERENCES transactions(ta_id) - ON DELETE CASCADE ON UPDATE CASCADE - ); - ''', - ''' - CREATE TABLE receipts ( -- receipts sent to the users - receipt_id INTEGER PRIMARY KEY, - user_id INTEGER NOT NULL, - first_ta_id INTEGER NOT NULL, - last_ta_id INTEGER NOT NULL, - date INTEGER(8) DEFAULT (STRFTIME('%s', 'now')), - FOREIGN KEY (user_id) REFERENCES users(user_id) - ON DELETE CASCADE ON UPDATE CASCADE, - FOREIGN KEY (first_ta_id) REFERENCES transactions(ta_id) - ON DELETE SET NULL ON UPDATE CASCADE, - FOREIGN KEY (last_ta_id) REFERENCES transactions(ta_id) - ON DELETE SET NULL ON UPDATE CASCADE - ); - '''] - -SCHEMAS[4] = [ - ''' - CREATE TABLE users ( - user_id INTEGER PRIMARY KEY, - username TEXT UNIQUE NOT NULL, - email TEXT DEFAULT NULL, - password TEXT NOT NULL, - touchkey TEXT DEFAULT NULL, - is_admin INTEGER(1) NOT NULL DEFAULT 0, - is_member INTEGER(1) NOT NULL DEFAULT 1, - balance INTEGER(8) NOT NULL DEFAULT 0, - lastchange INTEGER(8) NOT NULL DEFAULT 0, - receipt_pref INTEGER(1) NOT NULL DEFAULT 0, - created INTEGER(8) NOT NULL DEFAULT 0 - ); - ''', - ''' - CREATE TABLE products ( - product_id INTEGER PRIMARY KEY, - name TEXT UNIQUE NOT NULL, - stock INTEGER(8) NOT NULL DEFAULT 0, - price_member INTEGER(8) NOT NULL, - price_non_member INTEGER(8) NOT NULL - ); - ''', - ''' - CREATE TABLE transactions ( -- "superclass" of the following 3 tables - ta_id INTEGER PRIMARY KEY, - user_id INTEGER DEFAULT NULL, - value INTEGER(8) NOT NULL, - old_balance INTEGER(8) NOT NULL, - date INTEGER(8) DEFAULT (STRFTIME('%s', 'now')), - FOREIGN KEY (user_id) REFERENCES users(user_id) - ON DELETE SET NULL ON UPDATE CASCADE - ); - ''', - ''' - CREATE TABLE consumptions ( -- transactions involving buying a product - ta_id INTEGER PRIMARY KEY, - product TEXT NOT NULL, - FOREIGN KEY (ta_id) REFERENCES transactions(ta_id) - ON DELETE CASCADE ON UPDATE CASCADE - ); - ''', - ''' - CREATE TABLE deposits ( -- transactions involving depositing cash - ta_id INTEGER PRIMARY KEY, - FOREIGN KEY (ta_id) REFERENCES transactions(ta_id) - ON DELETE CASCADE ON UPDATE CASCADE - ); - ''', - ''' - CREATE TABLE modifications ( -- transactions involving balance modification by an admin - ta_id INTEGER NOT NULL, - agent TEXT NOT NULL, - reason TEXT DEFAULT NULL, - PRIMARY KEY (ta_id), - FOREIGN KEY (ta_id) REFERENCES transactions(ta_id) - ON DELETE CASCADE ON UPDATE CASCADE - ); - ''', - ''' - CREATE TABLE receipts ( -- receipts sent to the users - receipt_id INTEGER PRIMARY KEY, - user_id INTEGER NOT NULL, - first_ta_id INTEGER DEFAULT NULL, - last_ta_id INTEGER DEFAULT NULL, - date INTEGER(8) DEFAULT (STRFTIME('%s', 'now')), - FOREIGN KEY (user_id) REFERENCES users(user_id) - ON DELETE CASCADE ON UPDATE CASCADE, - FOREIGN KEY (first_ta_id) REFERENCES transactions(ta_id) - ON DELETE SET NULL ON UPDATE CASCADE, - FOREIGN KEY (last_ta_id) REFERENCES transactions(ta_id) - ON DELETE SET NULL ON UPDATE CASCADE - ); - '''] - -SCHEMAS[5] = [ - ''' - CREATE TABLE users ( - user_id INTEGER PRIMARY KEY, - username TEXT UNIQUE NOT NULL, - email TEXT DEFAULT NULL, - password TEXT NOT NULL, - touchkey TEXT DEFAULT NULL, - is_admin INTEGER(1) NOT NULL DEFAULT 0, - is_member INTEGER(1) NOT NULL DEFAULT 1, - balance INTEGER(8) NOT NULL DEFAULT 0, - lastchange INTEGER(8) NOT NULL DEFAULT 0, - receipt_pref INTEGER(1) NOT NULL DEFAULT 0, - created INTEGER(8) NOT NULL DEFAULT 0 - ); - ''', - ''' - CREATE TABLE products ( - product_id INTEGER PRIMARY KEY, - name TEXT UNIQUE NOT NULL, - stock INTEGER(8) DEFAULT 0, - stockable INTEGER(1) DEFAULT 1, - price_member INTEGER(8) NOT NULL, - price_non_member INTEGER(8) NOT NULL - ); - ''', - ''' - CREATE TABLE transactions ( -- "superclass" of the following 3 tables - ta_id INTEGER PRIMARY KEY, - user_id INTEGER DEFAULT NULL, - value INTEGER(8) NOT NULL, - old_balance INTEGER(8) NOT NULL, - date INTEGER(8) DEFAULT (STRFTIME('%s', 'now')), - FOREIGN KEY (user_id) REFERENCES users(user_id) - ON DELETE SET NULL ON UPDATE CASCADE - ); - ''', - ''' - CREATE TABLE consumptions ( -- transactions involving buying a product - ta_id INTEGER PRIMARY KEY, - product TEXT NOT NULL, - FOREIGN KEY (ta_id) REFERENCES transactions(ta_id) - ON DELETE CASCADE ON UPDATE CASCADE - ); - ''', - ''' - CREATE TABLE deposits ( -- transactions involving depositing cash - ta_id INTEGER PRIMARY KEY, - FOREIGN KEY (ta_id) REFERENCES transactions(ta_id) - ON DELETE CASCADE ON UPDATE CASCADE - ); - ''', - ''' - CREATE TABLE modifications ( -- transactions involving balance modification by an admin - ta_id INTEGER NOT NULL, - agent TEXT NOT NULL, - reason TEXT DEFAULT NULL, - PRIMARY KEY (ta_id), - FOREIGN KEY (ta_id) REFERENCES transactions(ta_id) - ON DELETE CASCADE ON UPDATE CASCADE - ); - ''', - ''' - CREATE TABLE receipts ( -- receipts sent to the users - receipt_id INTEGER PRIMARY KEY, - user_id INTEGER NOT NULL, - first_ta_id INTEGER DEFAULT NULL, - last_ta_id INTEGER DEFAULT NULL, - date INTEGER(8) DEFAULT (STRFTIME('%s', 'now')), - FOREIGN KEY (user_id) REFERENCES users(user_id) - ON DELETE CASCADE ON UPDATE CASCADE, - FOREIGN KEY (first_ta_id) REFERENCES transactions(ta_id) - ON DELETE SET NULL ON UPDATE CASCADE, - FOREIGN KEY (last_ta_id) REFERENCES transactions(ta_id) - ON DELETE SET NULL ON UPDATE CASCADE - ); - '''] - - -SCHEMAS[6] = [ - ''' - CREATE TABLE users ( - user_id INTEGER PRIMARY KEY, - username TEXT UNIQUE NOT NULL, - email TEXT DEFAULT NULL, - password TEXT NOT NULL, - touchkey TEXT DEFAULT NULL, - is_admin INTEGER(1) NOT NULL DEFAULT 0, - is_member INTEGER(1) NOT NULL DEFAULT 1, - balance INTEGER(8) NOT NULL DEFAULT 0, - lastchange INTEGER(8) NOT NULL DEFAULT 0, - receipt_pref INTEGER(1) NOT NULL DEFAULT 0, - created INTEGER(8) NOT NULL DEFAULT 0 - ); - ''', - ''' - CREATE TABLE products ( - product_id INTEGER PRIMARY KEY, - name TEXT UNIQUE NOT NULL, - stock INTEGER(8) DEFAULT 0, - stockable INTEGER(1) DEFAULT 1, - price_member INTEGER(8) NOT NULL, - price_non_member INTEGER(8) NOT NULL, - custom_price INTEGER(1) DEFAULT 0 - ); - ''', - ''' - CREATE TABLE transactions ( -- "superclass" of the following 3 tables - ta_id INTEGER PRIMARY KEY, - user_id INTEGER DEFAULT NULL, - value INTEGER(8) NOT NULL, - old_balance INTEGER(8) NOT NULL, - date INTEGER(8) DEFAULT (STRFTIME('%s', 'now')), - FOREIGN KEY (user_id) REFERENCES users(user_id) - ON DELETE SET NULL ON UPDATE CASCADE - ); - ''', - ''' - CREATE TABLE consumptions ( -- transactions involving buying a product - ta_id INTEGER PRIMARY KEY, - product TEXT NOT NULL, - FOREIGN KEY (ta_id) REFERENCES transactions(ta_id) - ON DELETE CASCADE ON UPDATE CASCADE - ); - ''', - ''' - CREATE TABLE deposits ( -- transactions involving depositing cash - ta_id INTEGER PRIMARY KEY, - FOREIGN KEY (ta_id) REFERENCES transactions(ta_id) - ON DELETE CASCADE ON UPDATE CASCADE - ); - ''', - ''' - CREATE TABLE modifications ( -- transactions involving balance modification by an admin - ta_id INTEGER NOT NULL, - agent TEXT NOT NULL, - reason TEXT DEFAULT NULL, - PRIMARY KEY (ta_id), - FOREIGN KEY (ta_id) REFERENCES transactions(ta_id) - ON DELETE CASCADE ON UPDATE CASCADE - ); - ''', - ''' - CREATE TABLE receipts ( -- receipts sent to the users - receipt_id INTEGER PRIMARY KEY, - user_id INTEGER NOT NULL, - first_ta_id INTEGER DEFAULT NULL, - last_ta_id INTEGER DEFAULT NULL, - date INTEGER(8) DEFAULT (STRFTIME('%s', 'now')), - FOREIGN KEY (user_id) REFERENCES users(user_id) - ON DELETE CASCADE ON UPDATE CASCADE, - FOREIGN KEY (first_ta_id) REFERENCES transactions(ta_id) - ON DELETE SET NULL ON UPDATE CASCADE, - FOREIGN KEY (last_ta_id) REFERENCES transactions(ta_id) - ON DELETE SET NULL ON UPDATE CASCADE - ); - '''] - - -SCHEMAS[7] = [ - ''' - CREATE TABLE users ( - user_id INTEGER PRIMARY KEY, - username TEXT UNIQUE NOT NULL, - email TEXT DEFAULT NULL, - password TEXT NOT NULL, - touchkey TEXT DEFAULT NULL, - is_admin INTEGER(1) NOT NULL DEFAULT 0, - is_member INTEGER(1) NOT NULL DEFAULT 1, - balance INTEGER(8) NOT NULL DEFAULT 0, - lastchange INTEGER(8) NOT NULL DEFAULT 0, - receipt_pref INTEGER(1) NOT NULL DEFAULT 0, - created INTEGER(8) NOT NULL DEFAULT 0, - logout_after_purchase INTEGER(1) DEFAULT 0 - ); - ''', - ''' - CREATE TABLE products ( - product_id INTEGER PRIMARY KEY, - name TEXT UNIQUE NOT NULL, - stock INTEGER(8) DEFAULT 0, - stockable INTEGER(1) DEFAULT 1, - price_member INTEGER(8) NOT NULL, - price_non_member INTEGER(8) NOT NULL, - custom_price INTEGER(1) DEFAULT 0 - ); - ''', - ''' - CREATE TABLE transactions ( -- "superclass" of the following 3 tables - ta_id INTEGER PRIMARY KEY, - user_id INTEGER DEFAULT NULL, - value INTEGER(8) NOT NULL, - old_balance INTEGER(8) NOT NULL, - date INTEGER(8) DEFAULT (STRFTIME('%s', 'now')), - FOREIGN KEY (user_id) REFERENCES users(user_id) - ON DELETE SET NULL ON UPDATE CASCADE - ); - ''', - ''' - CREATE TABLE consumptions ( -- transactions involving buying a product - ta_id INTEGER PRIMARY KEY, - product TEXT NOT NULL, - FOREIGN KEY (ta_id) REFERENCES transactions(ta_id) - ON DELETE CASCADE ON UPDATE CASCADE - ); - ''', - ''' - CREATE TABLE deposits ( -- transactions involving depositing cash - ta_id INTEGER PRIMARY KEY, - FOREIGN KEY (ta_id) REFERENCES transactions(ta_id) - ON DELETE CASCADE ON UPDATE CASCADE - ); - ''', - ''' - CREATE TABLE modifications ( -- transactions involving balance modification by an admin - ta_id INTEGER NOT NULL, - agent TEXT NOT NULL, - reason TEXT DEFAULT NULL, - PRIMARY KEY (ta_id), - FOREIGN KEY (ta_id) REFERENCES transactions(ta_id) - ON DELETE CASCADE ON UPDATE CASCADE - ); - ''', - ''' - CREATE TABLE receipts ( -- receipts sent to the users - receipt_id INTEGER PRIMARY KEY, - user_id INTEGER NOT NULL, - first_ta_id INTEGER DEFAULT NULL, - last_ta_id INTEGER DEFAULT NULL, - date INTEGER(8) DEFAULT (STRFTIME('%s', 'now')), - FOREIGN KEY (user_id) REFERENCES users(user_id) - ON DELETE CASCADE ON UPDATE CASCADE, - FOREIGN KEY (first_ta_id) REFERENCES transactions(ta_id) - ON DELETE SET NULL ON UPDATE CASCADE, - FOREIGN KEY (last_ta_id) REFERENCES transactions(ta_id) - ON DELETE SET NULL ON UPDATE CASCADE - ); - '''] - - -SCHEMAS[8] = [ - ''' - CREATE TABLE users ( - user_id INTEGER PRIMARY KEY, - username TEXT UNIQUE NOT NULL, - email TEXT DEFAULT NULL, - password TEXT NOT NULL, - touchkey TEXT DEFAULT NULL, - is_admin INTEGER(1) NOT NULL DEFAULT 0, - is_member INTEGER(1) NOT NULL DEFAULT 1, - balance INTEGER(8) NOT NULL DEFAULT 0, - lastchange INTEGER(8) NOT NULL DEFAULT 0, - receipt_pref INTEGER(1) NOT NULL DEFAULT 0, - created INTEGER(8) NOT NULL DEFAULT 0, - logout_after_purchase INTEGER(1) DEFAULT 0 - ); - ''', - ''' - CREATE TABLE products ( - product_id INTEGER PRIMARY KEY, - name TEXT UNIQUE NOT NULL, - stock INTEGER(8) DEFAULT 0, - stockable INTEGER(1) DEFAULT 1, - price_member INTEGER(8) NOT NULL, - price_non_member INTEGER(8) NOT NULL, - custom_price INTEGER(1) DEFAULT 0, - ean TEXT UNIQUE DEFAULT NULL - ); - ''', - ''' - CREATE TABLE transactions ( -- "superclass" of the following 3 tables - ta_id INTEGER PRIMARY KEY, - user_id INTEGER DEFAULT NULL, - value INTEGER(8) NOT NULL, - old_balance INTEGER(8) NOT NULL, - date INTEGER(8) DEFAULT (STRFTIME('%s', 'now')), - FOREIGN KEY (user_id) REFERENCES users(user_id) - ON DELETE SET NULL ON UPDATE CASCADE - ); - ''', - ''' - CREATE TABLE consumptions ( -- transactions involving buying a product - ta_id INTEGER PRIMARY KEY, - product TEXT NOT NULL, - FOREIGN KEY (ta_id) REFERENCES transactions(ta_id) - ON DELETE CASCADE ON UPDATE CASCADE - ); - ''', - ''' - CREATE TABLE deposits ( -- transactions involving depositing cash - ta_id INTEGER PRIMARY KEY, - FOREIGN KEY (ta_id) REFERENCES transactions(ta_id) - ON DELETE CASCADE ON UPDATE CASCADE - ); - ''', - ''' - CREATE TABLE modifications ( -- transactions involving balance modification by an admin - ta_id INTEGER NOT NULL, - agent TEXT NOT NULL, - reason TEXT DEFAULT NULL, - PRIMARY KEY (ta_id), - FOREIGN KEY (ta_id) REFERENCES transactions(ta_id) - ON DELETE CASCADE ON UPDATE CASCADE - ); - ''', - ''' - CREATE TABLE receipts ( -- receipts sent to the users - receipt_id INTEGER PRIMARY KEY, - user_id INTEGER NOT NULL, - first_ta_id INTEGER DEFAULT NULL, - last_ta_id INTEGER DEFAULT NULL, - date INTEGER(8) DEFAULT (STRFTIME('%s', 'now')), - FOREIGN KEY (user_id) REFERENCES users(user_id) - ON DELETE CASCADE ON UPDATE CASCADE, - FOREIGN KEY (first_ta_id) REFERENCES transactions(ta_id) - ON DELETE SET NULL ON UPDATE CASCADE, - FOREIGN KEY (last_ta_id) REFERENCES transactions(ta_id) - ON DELETE SET NULL ON UPDATE CASCADE - ); - '''] - - -SCHEMAS[9] = [ - ''' - CREATE TABLE users ( - user_id INTEGER PRIMARY KEY, - username TEXT UNIQUE NOT NULL, - email TEXT DEFAULT NULL, - password TEXT NOT NULL, - touchkey TEXT DEFAULT NULL, - is_admin INTEGER(1) NOT NULL DEFAULT 0, - is_member INTEGER(1) NOT NULL DEFAULT 1, - balance INTEGER(8) NOT NULL DEFAULT 0, - lastchange INTEGER(8) NOT NULL DEFAULT 0, - receipt_pref INTEGER(1) NOT NULL DEFAULT 0, - created INTEGER(8) NOT NULL DEFAULT 0, - logout_after_purchase INTEGER(1) DEFAULT 0 - ); - ''', - ''' - CREATE TABLE products ( - product_id INTEGER PRIMARY KEY, - name TEXT UNIQUE NOT NULL, - stock INTEGER(8) DEFAULT 0, - stockable INTEGER(1) DEFAULT 1, - price_member INTEGER(8) NOT NULL, - price_non_member INTEGER(8) NOT NULL, - custom_price INTEGER(1) DEFAULT 0, - ean TEXT UNIQUE DEFAULT NULL - ); - ''', - ''' - CREATE TABLE transactions ( -- "superclass" of the following 3 tables - ta_id INTEGER PRIMARY KEY, - user_id INTEGER DEFAULT NULL, - value INTEGER(8) NOT NULL, - old_balance INTEGER(8) NOT NULL, - date INTEGER(8) DEFAULT (STRFTIME('%s', 'now')), - FOREIGN KEY (user_id) REFERENCES users(user_id) - ON DELETE SET NULL ON UPDATE CASCADE - ); - ''', - ''' - CREATE TABLE consumptions ( -- transactions involving buying a product - ta_id INTEGER PRIMARY KEY, - product TEXT NOT NULL, - FOREIGN KEY (ta_id) REFERENCES transactions(ta_id) - ON DELETE CASCADE ON UPDATE CASCADE - ); - ''', - ''' - CREATE TABLE deposits ( -- transactions involving depositing cash - ta_id INTEGER PRIMARY KEY, - FOREIGN KEY (ta_id) REFERENCES transactions(ta_id) - ON DELETE CASCADE ON UPDATE CASCADE - ); - ''', - ''' - CREATE TABLE modifications ( -- transactions involving balance modification by an admin - ta_id INTEGER NOT NULL, - agent TEXT NOT NULL, - reason TEXT DEFAULT NULL, - PRIMARY KEY (ta_id), - FOREIGN KEY (ta_id) REFERENCES transactions(ta_id) - ON DELETE CASCADE ON UPDATE CASCADE - ); - ''', - ''' - CREATE TABLE receipts ( -- receipts sent to the users - receipt_id INTEGER PRIMARY KEY, - user_id INTEGER NOT NULL, - first_ta_id INTEGER DEFAULT NULL, - last_ta_id INTEGER DEFAULT NULL, - date INTEGER(8) DEFAULT (STRFTIME('%s', 'now')), - FOREIGN KEY (user_id) REFERENCES users(user_id) - ON DELETE CASCADE ON UPDATE CASCADE, - FOREIGN KEY (first_ta_id) REFERENCES transactions(ta_id) - ON DELETE SET NULL ON UPDATE CASCADE, - FOREIGN KEY (last_ta_id) REFERENCES transactions(ta_id) - ON DELETE SET NULL ON UPDATE CASCADE - ); - ''', - ''' - CREATE TABLE tokens ( -- authentication tokens such as barcodes - token_id INTEGER PRIMARY KEY, - user_id INTEGER NOT NULL, - token TEXT UNIQUE NOT NULL, - name TEXT UNIQUE NOT NULL, - date INTEGER(8) DEFAULT (STRFTIME('%s', 'now')), - FOREIGN KEY (user_id) REFERENCES users(user_id) - ON DELETE CASCADE ON UPDATE CASCADE - ); - '''] - - -SCHEMAS[10] = [ - ''' - CREATE TABLE users ( - user_id INTEGER PRIMARY KEY, - username TEXT UNIQUE NOT NULL COLLATE NOCASE, - email TEXT DEFAULT NULL, - password TEXT NOT NULL, - touchkey TEXT DEFAULT NULL, - is_admin INTEGER(1) NOT NULL DEFAULT 0, - is_member INTEGER(1) NOT NULL DEFAULT 1, - balance INTEGER(8) NOT NULL DEFAULT 0, - lastchange INTEGER(8) NOT NULL DEFAULT 0, - receipt_pref INTEGER(1) NOT NULL DEFAULT 0, - created INTEGER(8) NOT NULL DEFAULT 0, - logout_after_purchase INTEGER(1) DEFAULT 0 - ); - ''', - ''' - CREATE TABLE products ( - product_id INTEGER PRIMARY KEY, - name TEXT UNIQUE NOT NULL, - stock INTEGER(8) DEFAULT 0, - stockable INTEGER(1) DEFAULT 1, - price_member INTEGER(8) NOT NULL, - price_non_member INTEGER(8) NOT NULL, - custom_price INTEGER(1) DEFAULT 0, - ean TEXT UNIQUE DEFAULT NULL - ); - ''', - ''' - CREATE TABLE transactions ( -- "superclass" of the following 3 tables - ta_id INTEGER PRIMARY KEY, - user_id INTEGER DEFAULT NULL, - value INTEGER(8) NOT NULL, - old_balance INTEGER(8) NOT NULL, - date INTEGER(8) DEFAULT (STRFTIME('%s', 'now')), - FOREIGN KEY (user_id) REFERENCES users(user_id) - ON DELETE SET NULL ON UPDATE CASCADE - ); - ''', - ''' - CREATE TABLE consumptions ( -- transactions involving buying a product - ta_id INTEGER PRIMARY KEY, - product TEXT NOT NULL, - FOREIGN KEY (ta_id) REFERENCES transactions(ta_id) - ON DELETE CASCADE ON UPDATE CASCADE - ); - ''', - ''' - CREATE TABLE deposits ( -- transactions involving depositing cash - ta_id INTEGER PRIMARY KEY, - FOREIGN KEY (ta_id) REFERENCES transactions(ta_id) - ON DELETE CASCADE ON UPDATE CASCADE - ); - ''', - ''' - CREATE TABLE modifications ( -- transactions involving balance modification by an admin - ta_id INTEGER NOT NULL, - agent TEXT NOT NULL, - reason TEXT DEFAULT NULL, - PRIMARY KEY (ta_id), - FOREIGN KEY (ta_id) REFERENCES transactions(ta_id) - ON DELETE CASCADE ON UPDATE CASCADE - ); - ''', - ''' - CREATE TABLE receipts ( -- receipts sent to the users - receipt_id INTEGER PRIMARY KEY, - user_id INTEGER NOT NULL, - first_ta_id INTEGER DEFAULT NULL, - last_ta_id INTEGER DEFAULT NULL, - date INTEGER(8) DEFAULT (STRFTIME('%s', 'now')), - FOREIGN KEY (user_id) REFERENCES users(user_id) - ON DELETE CASCADE ON UPDATE CASCADE, - FOREIGN KEY (first_ta_id) REFERENCES transactions(ta_id) - ON DELETE SET NULL ON UPDATE CASCADE, - FOREIGN KEY (last_ta_id) REFERENCES transactions(ta_id) - ON DELETE SET NULL ON UPDATE CASCADE - ); - ''', - ''' - CREATE TABLE tokens ( -- authentication tokens such as barcodes - token_id INTEGER PRIMARY KEY, - user_id INTEGER NOT NULL, - token TEXT UNIQUE NOT NULL, - name TEXT UNIQUE NOT NULL, - date INTEGER(8) DEFAULT (STRFTIME('%s', 'now')), - FOREIGN KEY (user_id) REFERENCES users(user_id) - ON DELETE CASCADE ON UPDATE CASCADE - ); - '''] diff --git a/matemat/db/test/test_migrations.py b/matemat/db/test/test_migrations.py index 796f863..4c25e5f 100644 --- a/matemat/db/test/test_migrations.py +++ b/matemat/db/test/test_migrations.py @@ -1,10 +1,10 @@ import unittest +from unittest.mock import PropertyMock, patch import sqlite3 from matemat.db import DatabaseWrapper -from matemat.db.schemas import SCHEMAS class TestMigrations(unittest.TestCase): @@ -15,204 +15,13 @@ class TestMigrations(unittest.TestCase): def _initialize_db(self, schema_version: int): self.db._sqlite_db = sqlite3.connect(':memory:') - cursor: sqlite3.Cursor = self.db._sqlite_db.cursor() - cursor.execute('BEGIN EXCLUSIVE') - for cmd in SCHEMAS[schema_version]: - cursor.execute(cmd) - cursor.execute('COMMIT') + self.db._setup() def test_downgrade_fail(self): # Test that downgrades are forbidden - self.db.SCHEMA_VERSION = 1 self.db._sqlite_db = sqlite3.connect(':memory:') self.db._sqlite_db.execute('PRAGMA user_version = 2') - with self.assertRaises(RuntimeError): - with self.db: - pass - - def test_upgrade_1_to_2(self): - # Setup test db with example entries covering - hopefully - all cases - self._initialize_db(1) - cursor: sqlite3.Cursor = self.db._sqlite_db.cursor() - cursor.execute(''' - INSERT INTO users VALUES - (1, 'testadmin', 'a@b.c', '$2a$10$herebehashes', NULL, 1, 1, 1337, 0), - (2, 'testuser', NULL, '$2a$10$herebehashes', '$2a$10$herebehashes', 0, 1, 4242, 0), - (3, 'alien', NULL, '$2a$10$herebehashes', '$2a$10$herebehashes', 0, 0, 1234, 0) - ''') - cursor.execute(''' - INSERT INTO products VALUES - (1, 'Club Mate', 42, 200, 250), - (2, 'Flora Power Mate (1/4l)', 10, 100, 150) - ''') - cursor.execute(''' - INSERT INTO consumption VALUES - (1, 1, 5), (1, 2, 3), (2, 2, 10), (3, 1, 3), (3, 2, 4) - ''') - cursor.execute('PRAGMA user_version = 1') - - # Kick off the migration - schema_version = self.db.SCHEMA_VERSION - self.db.SCHEMA_VERSION = 2 - self.db._setup() - self.db.SCHEMA_VERSION = schema_version - - # Test whether the new tables were created - cursor.execute('PRAGMA table_info(transactions)') - self.assertNotEqual(0, len(cursor.fetchall())) - cursor.execute('PRAGMA table_info(consumptions)') - self.assertNotEqual(0, len(cursor.fetchall())) - cursor.execute('PRAGMA table_info(deposits)') - self.assertNotEqual(0, len(cursor.fetchall())) - cursor.execute('PRAGMA table_info(modifications)') - self.assertNotEqual(0, len(cursor.fetchall())) - # Test whether the old consumption table was dropped - cursor.execute('PRAGMA table_info(consumption)') - self.assertEqual(0, len(cursor.fetchall())) - - # Test number of entries in the new tables - cursor.execute('SELECT COUNT(ta_id) FROM transactions') - self.assertEqual(25, cursor.fetchone()[0]) - cursor.execute('SELECT COUNT(ta_id) FROM consumptions') - self.assertEqual(25, cursor.fetchone()[0]) - cursor.execute('SELECT COUNT(ta_id) FROM deposits') - self.assertEqual(0, cursor.fetchone()[0]) - cursor.execute('SELECT COUNT(ta_id) FROM modifications') - self.assertEqual(0, cursor.fetchone()[0]) - - # The (user_id=2 x product_id=1) combination should never appear - cursor.execute(''' - SELECT COUNT(t.ta_id) - FROM transactions AS t - LEFT JOIN consumptions AS c - ON t.ta_id = c.ta_id - WHERE t.user_id = 2 AND c.product_id = 1''') - self.assertEqual(0, cursor.fetchone()[0]) - - # Test that one entry per consumption was created, and their values match the negative price - cursor.execute(''' - SELECT COUNT(t.ta_id) - FROM transactions AS t - LEFT JOIN consumptions AS c - ON t.ta_id = c.ta_id - WHERE t.user_id = 1 AND c.product_id = 1 AND t.value = -200''') - self.assertEqual(5, cursor.fetchone()[0]) - cursor.execute(''' - SELECT COUNT(t.ta_id) - FROM transactions AS t - LEFT JOIN consumptions AS c - ON t.ta_id = c.ta_id - WHERE t.user_id = 1 AND c.product_id = 2 AND t.value = -100''') - self.assertEqual(3, cursor.fetchone()[0]) - cursor.execute(''' - SELECT COUNT(t.ta_id) - FROM transactions AS t - LEFT JOIN consumptions AS c - ON t.ta_id = c.ta_id - WHERE t.user_id = 2 AND c.product_id = 2 AND t.value = -100''') - self.assertEqual(10, cursor.fetchone()[0]) - cursor.execute(''' - SELECT COUNT(t.ta_id) - FROM transactions AS t - LEFT JOIN consumptions AS c - ON t.ta_id = c.ta_id - WHERE t.user_id = 3 AND c.product_id = 1 AND t.value = -250''') - self.assertEqual(3, cursor.fetchone()[0]) - cursor.execute(''' - SELECT COUNT(t.ta_id) - FROM transactions AS t - LEFT JOIN consumptions AS c - ON t.ta_id = c.ta_id - WHERE t.user_id = 3 AND c.product_id = 2 AND t.value = -150''') - self.assertEqual(4, cursor.fetchone()[0]) - - def test_upgrade_2_to_3(self): - # Setup test db with example entries covering - hopefully - all cases - self._initialize_db(2) - cursor: sqlite3.Cursor = self.db._sqlite_db.cursor() - cursor.execute(''' - INSERT INTO users VALUES - (1, 'testadmin', 'a@b.c', '$2a$10$herebehashes', NULL, 1, 1, 1337, 0), - (2, 'testuser', NULL, '$2a$10$herebehashes', '$2a$10$herebehashes', 0, 1, 4242, 0), - (3, 'alien', NULL, '$2a$10$herebehashes', '$2a$10$herebehashes', 0, 0, 1234, 0), - (4, 'neverused', NULL, '$2a$10$herebehashes', '$2a$10$herebehashes', 0, 0, 1234, 1234) - ''') - cursor.execute(''' - INSERT INTO products VALUES - (1, 'Club Mate', 42, 200, 250), - (2, 'Flora Power Mate', 10, 100, 150) - ''') - cursor.execute(''' - INSERT INTO transactions VALUES - (1, 1, 4200, 0, 1000), -- deposit - (2, 2, 1337, 0, 1001), -- modification - (3, 3, 1337, 0, 1002), -- modification with deleted agent - (4, 2, -200, 1337, 1003), -- consumption - (5, 3, -200, 1337, 1004) -- consumption with deleted product - ''') - cursor.execute('''INSERT INTO deposits VALUES (1)''') - cursor.execute(''' - INSERT INTO modifications VALUES - (2, 1, 'Account migration'), - (3, 42, 'You can''t find out who i am... MUAHAHAHA!!!')''') - cursor.execute('''INSERT INTO consumptions VALUES (4, 2), (5, 42)''') - cursor.execute('''PRAGMA user_version = 2''') - - # Kick off the migration - schema_version = self.db.SCHEMA_VERSION - self.db.SCHEMA_VERSION = 3 - self.db._setup() - self.db.SCHEMA_VERSION = schema_version - - # Make sure the receipts table was created - cursor.execute('''SELECT COUNT(receipt_id) FROM receipts''') - self.assertEqual(0, cursor.fetchone()[0]) - - # Make sure users.created was populated with the expected values - cursor.execute('''SELECT u.created FROM users AS u ORDER BY u.user_id ASC''') - self.assertEqual([(940,), (941,), (942,), (1174,)], cursor.fetchall()) - - # Make sure the modifications table was changed to contain the username, or a fallback - cursor.execute('''SELECT agent FROM modifications WHERE ta_id = 2''') - self.assertEqual('testadmin', cursor.fetchone()[0]) - cursor.execute('''SELECT agent FROM modifications WHERE ta_id = 3''') - self.assertEqual('', cursor.fetchone()[0]) - - # Make sure the consumptions table was changed to contain the product name, or a fallback - cursor.execute('''SELECT product FROM consumptions WHERE ta_id = 4''') - self.assertEqual('Flora Power Mate', cursor.fetchone()[0]) - cursor.execute('''SELECT product FROM consumptions WHERE ta_id = 5''') - self.assertEqual('', cursor.fetchone()[0]) - - def test_upgrade_3_to_4(self): - # Setup test db with example entries to test schema change - self._initialize_db(3) - cursor: sqlite3.Cursor = self.db._sqlite_db.cursor() - cursor.execute(''' - INSERT INTO users VALUES - (1, 'testadmin', 'a@b.c', '$2a$10$herebehashes', NULL, 1, 1, 1337, 0, 0, 0) - ''') - cursor.execute(''' - INSERT INTO products VALUES - (1, 'Club Mate', 42, 200, 250) - ''') - cursor.execute(''' - INSERT INTO transactions VALUES (1, 1, 4200, 0, 1000) - ''') - cursor.execute(''' - INSERT INTO receipts VALUES (1, 1, 1, 1, 1337) - ''') - cursor.execute('PRAGMA user_version = 3') - - # Kick off the migration - schema_version = self.db.SCHEMA_VERSION - self.db.SCHEMA_VERSION = 4 - self.db._setup() - self.db.SCHEMA_VERSION = schema_version - - # Make sure entries from the receipts table are preserved - cursor.execute('''SELECT COUNT(receipt_id) FROM receipts''') - self.assertEqual(1, cursor.fetchone()[0]) - - # Make sure transaction IDs can be set to NULL - cursor.execute('UPDATE receipts SET first_ta_id = NULL, last_ta_id = NULL') + with patch('matemat.db.DatabaseWrapper.schema_version', new_callable=PropertyMock(return_value=1)): + with self.assertRaises(RuntimeError): + with self.db: + pass diff --git a/matemat/db/test/test_wrapper.py b/matemat/db/test/test_wrapper.py index 27fc9e7..062dc2a 100644 --- a/matemat/db/test/test_wrapper.py +++ b/matemat/db/test/test_wrapper.py @@ -17,7 +17,7 @@ class DatabaseTest(unittest.TestCase): Test creation of database schema in an empty database """ with self.db as db: - self.assertEqual(DatabaseWrapper.SCHEMA_VERSION, db._user_version) + self.assertEqual(db.schema_version, db._user_version) def test_in_transaction(self) -> None: """ diff --git a/matemat/db/wrapper.py b/matemat/db/wrapper.py index c493f2b..a616543 100644 --- a/matemat/db/wrapper.py +++ b/matemat/db/wrapper.py @@ -1,9 +1,10 @@ from typing import Any, Optional +import sqlite3 + from matemat.exceptions import DatabaseConsistencyError -from matemat.db.schemas import SCHEMAS -from matemat.db.migrations import * +import matemat.db.migrations class DatabaseTransaction(object): @@ -40,8 +41,6 @@ class DatabaseTransaction(object): class DatabaseWrapper(object): - SCHEMA_VERSION = 10 - def __init__(self, filename: str) -> None: self._filename: str = filename self._sqlite_db: Optional[sqlite3.Connection] = None @@ -61,16 +60,11 @@ class DatabaseWrapper(object): def _setup(self) -> None: # Create or update schemas if necessary version: int = self._user_version - if version < 1: - # Don't use executescript, as it issues a COMMIT first - with self.transaction() as c: - for command in SCHEMAS[self.SCHEMA_VERSION]: - c.execute(command) - elif version < self.SCHEMA_VERSION: - self._upgrade(from_version=version, to_version=self.SCHEMA_VERSION) - elif version > self.SCHEMA_VERSION: + if version < self.schema_version: + self._upgrade(from_version=version, to_version=self.schema_version) + self._user_version = self.schema_version + elif version > self.schema_version: raise RuntimeError('Database schema is newer than supported by this version of Matemat.') - self._user_version = self.SCHEMA_VERSION # Enable foreign key enforcement cursor = self._sqlite_db.cursor() @@ -79,35 +73,20 @@ class DatabaseWrapper(object): def _upgrade(self, from_version: int, to_version: int) -> None: if from_version >= to_version: return + # Create backup before migration if self._filename != ':memory:': bakfile = f'{self._filename}_{from_version}_{to_version}.bak' bak = sqlite3.connect(bakfile) with bak: self._sqlite_db.backup(bak, pages=1) bak.close() + # Iterate through migrations, executing them one by one with self.transaction() as c: c.execute('PRAGMA foreign_keys=OFF') c.execute('PRAGMA legacy_alter_table=ON') - if from_version <= 1 and to_version >= 2: - migrate_schema_1_to_2(c) - if from_version <= 2 and to_version >= 3: - migrate_schema_2_to_3(c) - if from_version <= 3 and to_version >= 4: - migrate_schema_3_to_4(c) - if from_version <= 4 and to_version >= 5: - migrate_schema_4_to_5(c) - if from_version <= 5 and to_version >= 6: - migrate_schema_5_to_6(c) - if from_version <= 6 and to_version >= 7: - migrate_schema_6_to_7(c) - if from_version <= 7 and to_version >= 8: - migrate_schema_7_to_8(c) - if from_version <= 7 and to_version >= 8: - migrate_schema_7_to_8(c) - if from_version <= 8 and to_version >= 9: - migrate_schema_8_to_9(c) - if from_version <= 9 and to_version >= 10: - migrate_schema_9_to_10(c) + for i in range(from_version+1, to_version+1): + migration = getattr(matemat.db.migrations, f'migrate_schema_{i}') + migration(c) c.execute('PRAGMA foreign_key_check') c.execute('PRAGMA foreign_keys=ON') @@ -146,3 +125,13 @@ class DatabaseWrapper(object): raise RuntimeError(f'Database connection to {self._filename} is not established.') cursor = self._sqlite_db.cursor() cursor.execute(f'PRAGMA user_version = {version}') + + @property + def schema_version(self) -> int: + max_migration = 0 + for name in dir(matemat.db.migrations): + if not name.startswith('migrate_schema_'): + continue + migration = int(name.split('_')[2]) + max_migration = max(max_migration, migration) + return max_migration