-CREATE TABLE yt_queries (
+CREATE TABLE "files" (
+ rel_path TEXT PRIMARY KEY,
+ yt_id TEXT NOT NULL DEFAULT "",
+ flags INTEGER NOT NULL DEFAULT 0,
+ FOREIGN KEY (yt_id) REFERENCES "yt_videos"(id)
+);
+CREATE TABLE "quota_costs" (
+ id TEXT PRIMARY KEY,
+ timestamp TEXT NOT NULL,
+ cost INT NOT NULL
+);
+CREATE TABLE "yt_queries" (
id TEXT PRIMARY KEY,
text TEXT NOT NULL,
retrieved_at TEXT NOT NULL
);
-CREATE TABLE yt_videos (
+CREATE TABLE "yt_query_results" (
+ query_id TEXT NOT NULL,
+ video_id TEXT NOT NULL,
+ PRIMARY KEY (query_id, video_id),
+ FOREIGN KEY (query_id) REFERENCES "yt_queries"(id),
+ FOREIGN KEY (video_id) REFERENCES "yt_videos"(id)
+);
+CREATE TABLE "yt_videos" (
id TEXT PRIMARY KEY,
title TEXT NOT NULL,
description TEXT NOT NULL,
duration TEXT NOT NULL,
definition TEXT NOT NULL
);
-CREATE TABLE yt_query_results (
- query_id TEXT NOT NULL,
- video_id TEXT NOT NULL,
- PRIMARY KEY (query_id, video_id),
- FOREIGN KEY (query_id) REFERENCES yt_queries(id),
- FOREIGN KEY (video_id) REFERENCES yt_videos(id)
-);
-CREATE TABLE quota_costs (
- id TEXT PRIMARY KEY,
- timestamp TEXT NOT NULL,
- cost INT NOT NULL
-);
-CREATE TABLE files (
- rel_path TEXT PRIMARY KEY,
- yt_id TEXT NOT NULL DEFAULT "",
- flags INTEGER NOT NULL DEFAULT 0,
- FOREIGN KEY (yt_id) REFERENCES yt_videos(id)
-);
-
-ALTER TABLE files ADD COLUMN last_update TEXT NOT NULL DEFAULT "2000-01-01 12:00:00.123456";
+ALTER TABLE "files" ADD COLUMN last_update TEXT NOT NULL DEFAULT "2000-01-01 12:00:00.123456";
-ALTER TABLE files ADD COLUMN sha512_digest TEXT NOT NULL DEFAULT "";
+ALTER TABLE "files" ADD COLUMN sha512_digest TEXT NOT NULL DEFAULT "";
-CREATE TEMPORARY TABLE files_backup (
+CREATE TEMPORARY TABLE "files_backup" (
rel_path TEXT PRIMARY KEY,
yt_id TEXT NOT NULL DEFAULT "",
flags INTEGER NOT NULL DEFAULT 0,
last_update TEXT NOT NULL DEFAULT "2000-01-01 12:00:00.123456",
sha512_digest TEXT NOT NULL,
- FOREIGN KEY (yt_id) REFERENCES yt_videos(id)
+ FOREIGN KEY (yt_id) REFERENCES "yt_videos"(id)
);
-INSERT INTO files_backup SELECT
+INSERT INTO "files_backup" SELECT
rel_path,
yt_id,
flags,
last_update,
sha512_digest
-FROM files;
-DROP TABLE files;
-CREATE TABLE files (
+FROM "files";
+DROP TABLE "files";
+CREATE TABLE "files" (
sha512_digest TEXT PRIMARY KEY,
rel_path TEXT NOT NULL,
flags INTEGER NOT NULL DEFAULT 0,
yt_id TEXT,
last_update TEXT NOT NULL,
- FOREIGN KEY (yt_id) REFERENCES yt_videos(id)
+ FOREIGN KEY (yt_id) REFERENCES "yt_videos"(id)
);
-INSERT INTO files SELECT
+INSERT INTO "files" SELECT
sha512_digest,
rel_path,
flags,
yt_id,
last_update
-FROM files_backup;
-DROP TABLE files_backup;
+FROM "files_backup";
+DROP TABLE "files_backup";
-ALTER TABLE files ADD COLUMN sha512_blob BLOB;
+ALTER TABLE "files" ADD COLUMN sha512_blob BLOB;
-CREATE TABLE files_new (
+ALTER TABLE "files" RENAME TO "files_old";
+CREATE TABLE "files" (
digest BLOB PRIMARY KEY,
rel_path TEXT NOT NULL,
flags INTEGER NOT NULL DEFAULT 0,
yt_id TEXT,
last_update TEXT NOT NULL,
- FOREIGN KEY (yt_id) REFERENCES yt_videos(id)
+ FOREIGN KEY (yt_id) REFERENCES "yt_videos"(id)
);
-INSERT INTO files_new SELECT
+INSERT INTO "files" SELECT
sha512_blob,
rel_path,
flags,
yt_id,
last_update
-FROM files;
-DROP TABLE files;
-ALTER TABLE files_new RENAME TO files;
+FROM "files_old";
+DROP TABLE "files_old";
-ALTER TABLE files ADD COLUMN tags TEXT NOT NULL DEFAULT "";
+ALTER TABLE "files" ADD COLUMN tags TEXT NOT NULL DEFAULT "";
-CREATE TABLE yt_queries (
+CREATE TABLE "files" (
+ digest BLOB PRIMARY KEY,
+ rel_path TEXT NOT NULL,
+ flags INTEGER NOT NULL DEFAULT 0,
+ yt_id TEXT,
+ last_update TEXT NOT NULL,
+ tags TEXT NOT NULL DEFAULT "",
+ FOREIGN KEY (yt_id) REFERENCES "yt_videos"(id)
+);
+CREATE TABLE "quota_costs" (
+ id TEXT PRIMARY KEY,
+ timestamp TEXT NOT NULL,
+ cost INT NOT NULL
+);
+CREATE TABLE "yt_queries" (
id TEXT PRIMARY KEY,
text TEXT NOT NULL,
retrieved_at TEXT NOT NULL
);
-CREATE TABLE yt_videos (
+CREATE TABLE "yt_query_results" (
+ query_id TEXT NOT NULL,
+ video_id TEXT NOT NULL,
+ PRIMARY KEY (query_id, video_id),
+ FOREIGN KEY (query_id) REFERENCES "yt_queries"(id),
+ FOREIGN KEY (video_id) REFERENCES "yt_videos"(id)
+);
+CREATE TABLE "yt_videos" (
id TEXT PRIMARY KEY,
title TEXT NOT NULL,
description TEXT NOT NULL,
duration TEXT NOT NULL,
definition TEXT NOT NULL
);
-CREATE TABLE yt_query_results (
- query_id TEXT NOT NULL,
- video_id TEXT NOT NULL,
- PRIMARY KEY (query_id, video_id),
- FOREIGN KEY (query_id) REFERENCES yt_queries(id),
- FOREIGN KEY (video_id) REFERENCES yt_videos(id)
-);
-CREATE TABLE quota_costs (
- id TEXT PRIMARY KEY,
- timestamp TEXT NOT NULL,
- cost INT NOT NULL
-);
-CREATE TABLE files (
- digest BLOB PRIMARY KEY,
- rel_path TEXT NOT NULL,
- flags INTEGER NOT NULL DEFAULT 0,
- yt_id TEXT,
- last_update TEXT NOT NULL,
- tags TEXT NOT NULL DEFAULT "",
- FOREIGN KEY (yt_id) REFERENCES yt_videos(id)
-);
# included libs
from base64 import urlsafe_b64decode, urlsafe_b64encode
+from difflib import Differ
from hashlib import file_digest
from pathlib import Path
from sqlite3 import (connect as sql_connect, Connection as SqlConnection,
def __init__(self,
path: Path = PATH_DB,
- expected_version: int = EXPECTED_DB_VERSION
+ version_to_validate: int = EXPECTED_DB_VERSION
) -> None:
self._path = path
- if not path.is_file():
- raise HandledException(f'no DB file at {path}')
- if expected_version >= 0:
- user_version = self._get_user_version()
- if user_version != expected_version:
- raise HandledException(
- f'wrong DB version {user_version} (!= {expected_version})')
+ if not self._path.is_file():
+ raise HandledException(f'no DB file at {self._path}')
+
+ if version_to_validate < 0:
+ return
+ # ensure version
+ if (user_version := self._get_user_version()) != version_to_validate:
+ raise HandledException(
+ f'wrong DB version {user_version} (!= {version_to_validate})')
+
+ # ensure schema
+ with sql_connect(self._path) as conn:
+ schema_rows = [
+ r[0] for r in
+ conn.execute('SELECT sql FROM sqlite_master ORDER BY sql')
+ if r[0]]
+ schema_rows_normed = []
+ indent = ' '
+ for row in schema_rows:
+ row_normed = []
+ for subrow in row.split('\n'):
+ subrow = subrow.rstrip()
+ in_parentheses = 0
+ split_at = []
+ for i, c in enumerate(subrow):
+ if '(' == c:
+ in_parentheses += 1
+ elif ')' == c:
+ in_parentheses -= 1
+ elif ',' == c and 0 == in_parentheses:
+ split_at += [i + 1]
+ prev_split = 0
+ for i in split_at:
+ segment = subrow[prev_split:i].strip()
+ if len(segment) > 0:
+ row_normed += [f'{indent}{segment}']
+ prev_split = i
+ segment = subrow[prev_split:].strip()
+ if len(segment) > 0:
+ row_normed += [f'{indent}{segment}']
+ row_normed[0] = row_normed[0].lstrip()
+ row_normed[-1] = row_normed[-1].lstrip()
+ if row_normed[-1] != ')' and row_normed[-3][-1] != ',':
+ row_normed[-3] = row_normed[-3] + ','
+ row_normed[-2:] = [indent + row_normed[-1][:-1]] + [')']
+ schema_rows_normed += ['\n'.join(row_normed)]
+ retrieved_schema = ';\n'.join(schema_rows_normed) + ';'
+ with open(_PATH_DB_SCHEMA, 'r', encoding='utf-8') as f:
+ stored_schema = f.read().rstrip()
+ if stored_schema != retrieved_schema:
+ diff_msg = Differ().compare(retrieved_schema.splitlines(),
+ stored_schema.splitlines())
+ raise HandledException('DB has wrong tables schema. Diff:\n'
+ + '\n'.join(diff_msg))
def _get_user_version(self) -> int:
with sql_connect(self._path) as conn:
def migrate():
"""Migrate DB file at expected default path to most recent version."""
- DbFile(expected_version=-1).migrate(_MIGRATIONS)
+ DbFile(version_to_validate=-1).migrate(_MIGRATIONS)