%PDF- %PDF-
Direktori : /lib/calibre/calibre/db/ |
Current File : //lib/calibre/calibre/db/schema_upgrades.py |
#!/usr/bin/env python3 __license__ = 'GPL v3' __copyright__ = '2011, Kovid Goyal <kovid@kovidgoyal.net>' __docformat__ = 'restructuredtext en' import os from calibre import prints from calibre.utils.date import isoformat, DEFAULT_DATE from polyglot.builtins import itervalues class SchemaUpgrade: def __init__(self, db, library_path, field_metadata): db.execute('BEGIN EXCLUSIVE TRANSACTION') self.db = db self.library_path = library_path self.field_metadata = field_metadata # Upgrade database try: while True: uv = next(self.db.execute('pragma user_version'))[0] meth = getattr(self, 'upgrade_version_%d'%uv, None) if meth is None: break else: prints('Upgrading database to version %d...'%(uv+1)) meth() self.db.execute('pragma user_version=%d'%(uv+1)) except: self.db.execute('ROLLBACK') raise else: self.db.execute('COMMIT') finally: self.db = self.field_metadata = None def upgrade_version_1(self): ''' Normalize indices. ''' self.db.execute('''\ DROP INDEX IF EXISTS authors_idx; CREATE INDEX authors_idx ON books (author_sort COLLATE NOCASE, sort COLLATE NOCASE); DROP INDEX IF EXISTS series_idx; CREATE INDEX series_idx ON series (name COLLATE NOCASE); DROP INDEX IF EXISTS series_sort_idx; CREATE INDEX series_sort_idx ON books (series_index, id); ''') def upgrade_version_2(self): ''' Fix Foreign key constraints for deleting from link tables. ''' script = '''\ DROP TRIGGER IF EXISTS fkc_delete_books_%(ltable)s_link; CREATE TRIGGER fkc_delete_on_%(table)s BEFORE DELETE ON %(table)s BEGIN SELECT CASE WHEN (SELECT COUNT(id) FROM books_%(ltable)s_link WHERE %(ltable_col)s=OLD.id) > 0 THEN RAISE(ABORT, 'Foreign key violation: %(table)s is still referenced') END; END; DELETE FROM %(table)s WHERE (SELECT COUNT(id) FROM books_%(ltable)s_link WHERE %(ltable_col)s=%(table)s.id) < 1; ''' self.db.execute(script%dict(ltable='authors', table='authors', ltable_col='author')) self.db.execute(script%dict(ltable='publishers', table='publishers', ltable_col='publisher')) self.db.execute(script%dict(ltable='tags', table='tags', ltable_col='tag')) self.db.execute(script%dict(ltable='series', table='series', ltable_col='series')) def upgrade_version_3(self): ' Add path to result cache ' self.db.execute(''' DROP VIEW IF EXISTS meta; CREATE VIEW meta AS SELECT id, title, (SELECT concat(name) FROM authors WHERE authors.id IN (SELECT author from books_authors_link WHERE book=books.id)) authors, (SELECT name FROM publishers WHERE publishers.id IN (SELECT publisher from books_publishers_link WHERE book=books.id)) publisher, (SELECT rating FROM ratings WHERE ratings.id IN (SELECT rating from books_ratings_link WHERE book=books.id)) rating, timestamp, (SELECT MAX(uncompressed_size) FROM data WHERE book=books.id) size, (SELECT concat(name) FROM tags WHERE tags.id IN (SELECT tag from books_tags_link WHERE book=books.id)) tags, (SELECT text FROM comments WHERE book=books.id) comments, (SELECT name FROM series WHERE series.id IN (SELECT series FROM books_series_link WHERE book=books.id)) series, series_index, sort, author_sort, (SELECT concat(format) FROM data WHERE data.book=books.id) formats, isbn, path FROM books; ''') def upgrade_version_4(self): 'Rationalize books table' self.db.execute(''' CREATE TEMPORARY TABLE books_backup(id,title,sort,timestamp,series_index,author_sort,isbn,path); INSERT INTO books_backup SELECT id,title,sort,timestamp,series_index,author_sort,isbn,path FROM books; DROP TABLE books; CREATE TABLE books ( id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT NOT NULL DEFAULT 'Unknown' COLLATE NOCASE, sort TEXT COLLATE NOCASE, timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP, pubdate TIMESTAMP DEFAULT CURRENT_TIMESTAMP, series_index REAL NOT NULL DEFAULT 1.0, author_sort TEXT COLLATE NOCASE, isbn TEXT DEFAULT "" COLLATE NOCASE, lccn TEXT DEFAULT "" COLLATE NOCASE, path TEXT NOT NULL DEFAULT "", flags INTEGER NOT NULL DEFAULT 1 ); INSERT INTO books (id,title,sort,timestamp,pubdate,series_index,author_sort,isbn,path) SELECT id,title,sort,timestamp,timestamp,series_index,author_sort,isbn,path FROM books_backup; DROP TABLE books_backup; DROP VIEW IF EXISTS meta; CREATE VIEW meta AS SELECT id, title, (SELECT concat(name) FROM authors WHERE authors.id IN (SELECT author from books_authors_link WHERE book=books.id)) authors, (SELECT name FROM publishers WHERE publishers.id IN (SELECT publisher from books_publishers_link WHERE book=books.id)) publisher, (SELECT rating FROM ratings WHERE ratings.id IN (SELECT rating from books_ratings_link WHERE book=books.id)) rating, timestamp, (SELECT MAX(uncompressed_size) FROM data WHERE book=books.id) size, (SELECT concat(name) FROM tags WHERE tags.id IN (SELECT tag from books_tags_link WHERE book=books.id)) tags, (SELECT text FROM comments WHERE book=books.id) comments, (SELECT name FROM series WHERE series.id IN (SELECT series FROM books_series_link WHERE book=books.id)) series, series_index, sort, author_sort, (SELECT concat(format) FROM data WHERE data.book=books.id) formats, isbn, path, lccn, pubdate, flags FROM books; ''') def upgrade_version_5(self): 'Update indexes/triggers for new books table' self.db.execute(''' CREATE INDEX authors_idx ON books (author_sort COLLATE NOCASE); CREATE INDEX books_idx ON books (sort COLLATE NOCASE); CREATE TRIGGER books_delete_trg AFTER DELETE ON books BEGIN DELETE FROM books_authors_link WHERE book=OLD.id; DELETE FROM books_publishers_link WHERE book=OLD.id; DELETE FROM books_ratings_link WHERE book=OLD.id; DELETE FROM books_series_link WHERE book=OLD.id; DELETE FROM books_tags_link WHERE book=OLD.id; DELETE FROM data WHERE book=OLD.id; DELETE FROM comments WHERE book=OLD.id; DELETE FROM conversion_options WHERE book=OLD.id; END; CREATE TRIGGER books_insert_trg AFTER INSERT ON books BEGIN UPDATE books SET sort=title_sort(NEW.title) WHERE id=NEW.id; END; CREATE TRIGGER books_update_trg AFTER UPDATE ON books BEGIN UPDATE books SET sort=title_sort(NEW.title) WHERE id=NEW.id; END; UPDATE books SET sort=title_sort(title) WHERE sort IS NULL; ''' ) def upgrade_version_6(self): 'Show authors in order' self.db.execute(''' DROP VIEW IF EXISTS meta; CREATE VIEW meta AS SELECT id, title, (SELECT sortconcat(bal.id, name) FROM books_authors_link AS bal JOIN authors ON(author = authors.id) WHERE book = books.id) authors, (SELECT name FROM publishers WHERE publishers.id IN (SELECT publisher from books_publishers_link WHERE book=books.id)) publisher, (SELECT rating FROM ratings WHERE ratings.id IN (SELECT rating from books_ratings_link WHERE book=books.id)) rating, timestamp, (SELECT MAX(uncompressed_size) FROM data WHERE book=books.id) size, (SELECT concat(name) FROM tags WHERE tags.id IN (SELECT tag from books_tags_link WHERE book=books.id)) tags, (SELECT text FROM comments WHERE book=books.id) comments, (SELECT name FROM series WHERE series.id IN (SELECT series FROM books_series_link WHERE book=books.id)) series, series_index, sort, author_sort, (SELECT concat(format) FROM data WHERE data.book=books.id) formats, isbn, path, lccn, pubdate, flags FROM books; ''') def upgrade_version_7(self): 'Add uuid column' self.db.execute(''' ALTER TABLE books ADD COLUMN uuid TEXT; DROP TRIGGER IF EXISTS books_insert_trg; DROP TRIGGER IF EXISTS books_update_trg; UPDATE books SET uuid=uuid4(); CREATE TRIGGER books_insert_trg AFTER INSERT ON books BEGIN UPDATE books SET sort=title_sort(NEW.title),uuid=uuid4() WHERE id=NEW.id; END; CREATE TRIGGER books_update_trg AFTER UPDATE ON books BEGIN UPDATE books SET sort=title_sort(NEW.title) WHERE id=NEW.id; END; DROP VIEW IF EXISTS meta; CREATE VIEW meta AS SELECT id, title, (SELECT sortconcat(bal.id, name) FROM books_authors_link AS bal JOIN authors ON(author = authors.id) WHERE book = books.id) authors, (SELECT name FROM publishers WHERE publishers.id IN (SELECT publisher from books_publishers_link WHERE book=books.id)) publisher, (SELECT rating FROM ratings WHERE ratings.id IN (SELECT rating from books_ratings_link WHERE book=books.id)) rating, timestamp, (SELECT MAX(uncompressed_size) FROM data WHERE book=books.id) size, (SELECT concat(name) FROM tags WHERE tags.id IN (SELECT tag from books_tags_link WHERE book=books.id)) tags, (SELECT text FROM comments WHERE book=books.id) comments, (SELECT name FROM series WHERE series.id IN (SELECT series FROM books_series_link WHERE book=books.id)) series, series_index, sort, author_sort, (SELECT concat(format) FROM data WHERE data.book=books.id) formats, isbn, path, lccn, pubdate, flags, uuid FROM books; ''') def upgrade_version_8(self): 'Add Tag Browser views' def create_tag_browser_view(table_name, column_name): self.db.execute(''' DROP VIEW IF EXISTS tag_browser_{tn}; CREATE VIEW tag_browser_{tn} AS SELECT id, name, (SELECT COUNT(id) FROM books_{tn}_link WHERE {cn}={tn}.id) count FROM {tn}; '''.format(tn=table_name, cn=column_name)) for tn in ('authors', 'tags', 'publishers', 'series'): cn = tn[:-1] if tn == 'series': cn = tn create_tag_browser_view(tn, cn) def upgrade_version_9(self): 'Add custom columns' self.db.execute(''' CREATE TABLE custom_columns ( id INTEGER PRIMARY KEY AUTOINCREMENT, label TEXT NOT NULL, name TEXT NOT NULL, datatype TEXT NOT NULL, mark_for_delete BOOL DEFAULT 0 NOT NULL, editable BOOL DEFAULT 1 NOT NULL, display TEXT DEFAULT "{}" NOT NULL, is_multiple BOOL DEFAULT 0 NOT NULL, normalized BOOL NOT NULL, UNIQUE(label) ); CREATE INDEX IF NOT EXISTS custom_columns_idx ON custom_columns (label); CREATE INDEX IF NOT EXISTS formats_idx ON data (format); ''') def upgrade_version_10(self): 'Add restricted Tag Browser views' def create_tag_browser_view(table_name, column_name, view_column_name): script = (''' DROP VIEW IF EXISTS tag_browser_{tn}; CREATE VIEW tag_browser_{tn} AS SELECT id, {vcn}, (SELECT COUNT(id) FROM books_{tn}_link WHERE {cn}={tn}.id) count FROM {tn}; DROP VIEW IF EXISTS tag_browser_filtered_{tn}; CREATE VIEW tag_browser_filtered_{tn} AS SELECT id, {vcn}, (SELECT COUNT(books_{tn}_link.id) FROM books_{tn}_link WHERE {cn}={tn}.id AND books_list_filter(book)) count FROM {tn}; '''.format(tn=table_name, cn=column_name, vcn=view_column_name)) self.db.execute(script) for field in itervalues(self.field_metadata): if field['is_category'] and not field['is_custom'] and 'link_column' in field: table = self.db.get( 'SELECT name FROM sqlite_master WHERE type="table" AND name=?', ('books_%s_link'%field['table'],), all=False) if table is not None: create_tag_browser_view(field['table'], field['link_column'], field['column']) def upgrade_version_11(self): 'Add average rating to tag browser views' def create_std_tag_browser_view(table_name, column_name, view_column_name, sort_column_name): script = (''' DROP VIEW IF EXISTS tag_browser_{tn}; CREATE VIEW tag_browser_{tn} AS SELECT id, {vcn}, (SELECT COUNT(id) FROM books_{tn}_link WHERE {cn}={tn}.id) count, (SELECT AVG(ratings.rating) FROM books_{tn}_link AS tl, books_ratings_link AS bl, ratings WHERE tl.{cn}={tn}.id AND bl.book=tl.book AND ratings.id = bl.rating AND ratings.rating <> 0) avg_rating, {scn} AS sort FROM {tn}; DROP VIEW IF EXISTS tag_browser_filtered_{tn}; CREATE VIEW tag_browser_filtered_{tn} AS SELECT id, {vcn}, (SELECT COUNT(books_{tn}_link.id) FROM books_{tn}_link WHERE {cn}={tn}.id AND books_list_filter(book)) count, (SELECT AVG(ratings.rating) FROM books_{tn}_link AS tl, books_ratings_link AS bl, ratings WHERE tl.{cn}={tn}.id AND bl.book=tl.book AND ratings.id = bl.rating AND ratings.rating <> 0 AND books_list_filter(bl.book)) avg_rating, {scn} AS sort FROM {tn}; '''.format(tn=table_name, cn=column_name, vcn=view_column_name, scn=sort_column_name)) self.db.execute(script) def create_cust_tag_browser_view(table_name, link_table_name): script = ''' DROP VIEW IF EXISTS tag_browser_{table}; CREATE VIEW tag_browser_{table} AS SELECT id, value, (SELECT COUNT(id) FROM {lt} WHERE value={table}.id) count, (SELECT AVG(r.rating) FROM {lt}, books_ratings_link AS bl, ratings AS r WHERE {lt}.value={table}.id AND bl.book={lt}.book AND r.id = bl.rating AND r.rating <> 0) avg_rating, value AS sort FROM {table}; DROP VIEW IF EXISTS tag_browser_filtered_{table}; CREATE VIEW tag_browser_filtered_{table} AS SELECT id, value, (SELECT COUNT({lt}.id) FROM {lt} WHERE value={table}.id AND books_list_filter(book)) count, (SELECT AVG(r.rating) FROM {lt}, books_ratings_link AS bl, ratings AS r WHERE {lt}.value={table}.id AND bl.book={lt}.book AND r.id = bl.rating AND r.rating <> 0 AND books_list_filter(bl.book)) avg_rating, value AS sort FROM {table}; '''.format(lt=link_table_name, table=table_name) self.db.execute(script) for field in itervalues(self.field_metadata): if field['is_category'] and not field['is_custom'] and 'link_column' in field: table = self.db.get( 'SELECT name FROM sqlite_master WHERE type="table" AND name=?', ('books_%s_link'%field['table'],), all=False) if table is not None: create_std_tag_browser_view(field['table'], field['link_column'], field['column'], field['category_sort']) db_tables = self.db.get('''SELECT name FROM sqlite_master WHERE type='table' ORDER BY name''') tables = [] for (table,) in db_tables: tables.append(table) for table in tables: link_table = 'books_%s_link'%table if table.startswith('custom_column_') and link_table in tables: create_cust_tag_browser_view(table, link_table) self.db.execute('UPDATE authors SET sort=author_to_author_sort(name)') def upgrade_version_12(self): 'DB based preference store' script = ''' DROP TABLE IF EXISTS preferences; CREATE TABLE preferences(id INTEGER PRIMARY KEY, key TEXT NOT NULL, val TEXT NOT NULL, UNIQUE(key)); ''' self.db.execute(script) def upgrade_version_13(self): 'Dirtied table for OPF metadata backups' script = ''' DROP TABLE IF EXISTS metadata_dirtied; CREATE TABLE metadata_dirtied(id INTEGER PRIMARY KEY, book INTEGER NOT NULL, UNIQUE(book)); INSERT INTO metadata_dirtied (book) SELECT id FROM books; ''' self.db.execute(script) def upgrade_version_14(self): 'Cache has_cover' self.db.execute('ALTER TABLE books ADD COLUMN has_cover BOOL DEFAULT 0') data = self.db.get('SELECT id,path FROM books', all=True) def has_cover(path): if path: path = os.path.join(self.library_path, path.replace('/', os.sep), 'cover.jpg') return os.path.exists(path) return False ids = [(x[0],) for x in data if has_cover(x[1])] self.db.executemany('UPDATE books SET has_cover=1 WHERE id=?', ids) def upgrade_version_15(self): 'Remove commas from tags' self.db.execute("UPDATE OR IGNORE tags SET name=REPLACE(name, ',', ';')") self.db.execute("UPDATE OR IGNORE tags SET name=REPLACE(name, ',', ';;')") self.db.execute("UPDATE OR IGNORE tags SET name=REPLACE(name, ',', '')") def upgrade_version_16(self): self.db.execute(''' DROP TRIGGER IF EXISTS books_update_trg; CREATE TRIGGER books_update_trg AFTER UPDATE ON books BEGIN UPDATE books SET sort=title_sort(NEW.title) WHERE id=NEW.id AND OLD.title <> NEW.title; END; ''') def upgrade_version_17(self): 'custom book data table (for plugins)' script = ''' DROP TABLE IF EXISTS books_plugin_data; CREATE TABLE books_plugin_data(id INTEGER PRIMARY KEY, book INTEGER NOT NULL, name TEXT NOT NULL, val TEXT NOT NULL, UNIQUE(book,name)); DROP TRIGGER IF EXISTS books_delete_trg; CREATE TRIGGER books_delete_trg AFTER DELETE ON books BEGIN DELETE FROM books_authors_link WHERE book=OLD.id; DELETE FROM books_publishers_link WHERE book=OLD.id; DELETE FROM books_ratings_link WHERE book=OLD.id; DELETE FROM books_series_link WHERE book=OLD.id; DELETE FROM books_tags_link WHERE book=OLD.id; DELETE FROM data WHERE book=OLD.id; DELETE FROM comments WHERE book=OLD.id; DELETE FROM conversion_options WHERE book=OLD.id; DELETE FROM books_plugin_data WHERE book=OLD.id; END; ''' self.db.execute(script) def upgrade_version_18(self): ''' Add a library UUID. Add an identifiers table. Add a languages table. Add a last_modified column. NOTE: You cannot downgrade after this update, if you do any changes you make to book isbns will be lost. ''' script = ''' DROP TABLE IF EXISTS library_id; CREATE TABLE library_id ( id INTEGER PRIMARY KEY, uuid TEXT NOT NULL, UNIQUE(uuid) ); DROP TABLE IF EXISTS identifiers; CREATE TABLE identifiers ( id INTEGER PRIMARY KEY, book INTEGER NOT NULL, type TEXT NOT NULL DEFAULT "isbn" COLLATE NOCASE, val TEXT NOT NULL COLLATE NOCASE, UNIQUE(book, type) ); DROP TABLE IF EXISTS languages; CREATE TABLE languages ( id INTEGER PRIMARY KEY, lang_code TEXT NOT NULL COLLATE NOCASE, UNIQUE(lang_code) ); DROP TABLE IF EXISTS books_languages_link; CREATE TABLE books_languages_link ( id INTEGER PRIMARY KEY, book INTEGER NOT NULL, lang_code INTEGER NOT NULL, item_order INTEGER NOT NULL DEFAULT 0, UNIQUE(book, lang_code) ); DROP TRIGGER IF EXISTS fkc_delete_on_languages; CREATE TRIGGER fkc_delete_on_languages BEFORE DELETE ON languages BEGIN SELECT CASE WHEN (SELECT COUNT(id) FROM books_languages_link WHERE lang_code=OLD.id) > 0 THEN RAISE(ABORT, 'Foreign key violation: language is still referenced') END; END; DROP TRIGGER IF EXISTS fkc_delete_on_languages_link; CREATE TRIGGER fkc_delete_on_languages_link BEFORE INSERT ON books_languages_link BEGIN SELECT CASE WHEN (SELECT id from books WHERE id=NEW.book) IS NULL THEN RAISE(ABORT, 'Foreign key violation: book not in books') WHEN (SELECT id from languages WHERE id=NEW.lang_code) IS NULL THEN RAISE(ABORT, 'Foreign key violation: lang_code not in languages') END; END; DROP TRIGGER IF EXISTS fkc_update_books_languages_link_a; CREATE TRIGGER fkc_update_books_languages_link_a BEFORE UPDATE OF book ON books_languages_link BEGIN SELECT CASE WHEN (SELECT id from books WHERE id=NEW.book) IS NULL THEN RAISE(ABORT, 'Foreign key violation: book not in books') END; END; DROP TRIGGER IF EXISTS fkc_update_books_languages_link_b; CREATE TRIGGER fkc_update_books_languages_link_b BEFORE UPDATE OF lang_code ON books_languages_link BEGIN SELECT CASE WHEN (SELECT id from languages WHERE id=NEW.lang_code) IS NULL THEN RAISE(ABORT, 'Foreign key violation: lang_code not in languages') END; END; DROP INDEX IF EXISTS books_languages_link_aidx; CREATE INDEX books_languages_link_aidx ON books_languages_link (lang_code); DROP INDEX IF EXISTS books_languages_link_bidx; CREATE INDEX books_languages_link_bidx ON books_languages_link (book); DROP INDEX IF EXISTS languages_idx; CREATE INDEX languages_idx ON languages (lang_code COLLATE NOCASE); DROP TRIGGER IF EXISTS books_delete_trg; CREATE TRIGGER books_delete_trg AFTER DELETE ON books BEGIN DELETE FROM books_authors_link WHERE book=OLD.id; DELETE FROM books_publishers_link WHERE book=OLD.id; DELETE FROM books_ratings_link WHERE book=OLD.id; DELETE FROM books_series_link WHERE book=OLD.id; DELETE FROM books_tags_link WHERE book=OLD.id; DELETE FROM books_languages_link WHERE book=OLD.id; DELETE FROM data WHERE book=OLD.id; DELETE FROM comments WHERE book=OLD.id; DELETE FROM conversion_options WHERE book=OLD.id; DELETE FROM books_plugin_data WHERE book=OLD.id; DELETE FROM identifiers WHERE book=OLD.id; END; INSERT INTO identifiers (book, val) SELECT id,isbn FROM books WHERE isbn; ALTER TABLE books ADD COLUMN last_modified TIMESTAMP NOT NULL DEFAULT "%s"; '''%isoformat(DEFAULT_DATE, sep=' ') # Sqlite does not support non constant default values in alter # statements self.db.execute(script) def upgrade_version_19(self): recipes = self.db.get('SELECT id,title,script FROM feeds') if recipes: from calibre.web.feeds.recipes import (custom_recipes, custom_recipe_filename) bdir = os.path.dirname(custom_recipes.file_path) for id_, title, script in recipes: existing = frozenset(map(int, custom_recipes)) if id_ in existing: id_ = max(existing) + 1000 id_ = str(id_) fname = custom_recipe_filename(id_, title) custom_recipes[id_] = (title, fname) if isinstance(script, str): script = script.encode('utf-8') with open(os.path.join(bdir, fname), 'wb') as f: f.write(script) def upgrade_version_20(self): ''' Add a link column to the authors table. ''' script = ''' ALTER TABLE authors ADD COLUMN link TEXT NOT NULL DEFAULT ""; ''' self.db.execute(script) def upgrade_version_21(self): ''' Write the series sort into the existing sort column in the series table ''' script = ''' DROP TRIGGER IF EXISTS series_insert_trg; DROP TRIGGER IF EXISTS series_update_trg; UPDATE series SET sort=title_sort(name); CREATE TRIGGER series_insert_trg AFTER INSERT ON series BEGIN UPDATE series SET sort=title_sort(NEW.name) WHERE id=NEW.id; END; CREATE TRIGGER series_update_trg AFTER UPDATE ON series BEGIN UPDATE series SET sort=title_sort(NEW.name) WHERE id=NEW.id; END; ''' self.db.execute(script) def upgrade_version_22(self): ''' Create the last_read_positions table ''' self.db.execute(''' DROP TABLE IF EXISTS last_read_positions; CREATE TABLE last_read_positions ( id INTEGER PRIMARY KEY, book INTEGER NOT NULL, format TEXT NOT NULL COLLATE NOCASE, user TEXT NOT NULL, device TEXT NOT NULL, cfi TEXT NOT NULL, epoch REAL NOT NULL, pos_frac REAL NOT NULL DEFAULT 0, UNIQUE(user, device, book, format) ); DROP INDEX IF EXISTS lrp_idx; CREATE INDEX lrp_idx ON last_read_positions (book); DROP TRIGGER IF EXISTS books_delete_trg; CREATE TRIGGER books_delete_trg AFTER DELETE ON books BEGIN DELETE FROM books_authors_link WHERE book=OLD.id; DELETE FROM books_publishers_link WHERE book=OLD.id; DELETE FROM books_ratings_link WHERE book=OLD.id; DELETE FROM books_series_link WHERE book=OLD.id; DELETE FROM books_tags_link WHERE book=OLD.id; DELETE FROM books_languages_link WHERE book=OLD.id; DELETE FROM data WHERE book=OLD.id; DELETE FROM last_read_positions WHERE book=OLD.id; DELETE FROM comments WHERE book=OLD.id; DELETE FROM conversion_options WHERE book=OLD.id; DELETE FROM books_plugin_data WHERE book=OLD.id; DELETE FROM identifiers WHERE book=OLD.id; END; DROP TRIGGER IF EXISTS fkc_lrp_insert; DROP TRIGGER IF EXISTS fkc_lrp_update; CREATE TRIGGER fkc_lrp_insert BEFORE INSERT ON last_read_positions BEGIN SELECT CASE WHEN (SELECT id from books WHERE id=NEW.book) IS NULL THEN RAISE(ABORT, 'Foreign key violation: book not in books') END; END; CREATE TRIGGER fkc_lrp_update BEFORE UPDATE OF book ON last_read_positions BEGIN SELECT CASE WHEN (SELECT id from books WHERE id=NEW.book) IS NULL THEN RAISE(ABORT, 'Foreign key violation: book not in books') END; END; ''') def upgrade_version_23(self): ''' Create the annotations table ''' self.db.execute(''' DROP TABLE IF EXISTS annotations_dirtied; CREATE TABLE annotations_dirtied(id INTEGER PRIMARY KEY, book INTEGER NOT NULL, UNIQUE(book)); DROP TABLE IF EXISTS annotations; CREATE TABLE annotations ( id INTEGER PRIMARY KEY, book INTEGER NOT NULL, format TEXT NOT NULL COLLATE NOCASE, user_type TEXT NOT NULL, user TEXT NOT NULL, timestamp REAL NOT NULL, annot_id TEXT NOT NULL, annot_type TEXT NOT NULL, annot_data TEXT NOT NULL, searchable_text TEXT NOT NULL DEFAULT "", UNIQUE(book, user_type, user, format, annot_type, annot_id) ); DROP INDEX IF EXISTS annot_idx; CREATE INDEX annot_idx ON annotations (book); DROP TABLE IF EXISTS annotations_fts; DROP TABLE IF EXISTS annotations_fts_stemmed; CREATE VIRTUAL TABLE annotations_fts USING fts5(searchable_text, content = 'annotations', content_rowid = 'id', tokenize = 'unicode61 remove_diacritics 2'); CREATE VIRTUAL TABLE annotations_fts_stemmed USING fts5(searchable_text, content = 'annotations', content_rowid = 'id', tokenize = 'porter unicode61 remove_diacritics 2'); DROP TRIGGER IF EXISTS annotations_fts_insert_trg; CREATE TRIGGER annotations_fts_insert_trg AFTER INSERT ON annotations BEGIN INSERT INTO annotations_fts(rowid, searchable_text) VALUES (NEW.id, NEW.searchable_text); INSERT INTO annotations_fts_stemmed(rowid, searchable_text) VALUES (NEW.id, NEW.searchable_text); END; DROP TRIGGER IF EXISTS annotations_fts_delete_trg; CREATE TRIGGER annotations_fts_delete_trg AFTER DELETE ON annotations BEGIN INSERT INTO annotations_fts(annotations_fts, rowid, searchable_text) VALUES('delete', OLD.id, OLD.searchable_text); INSERT INTO annotations_fts_stemmed(annotations_fts_stemmed, rowid, searchable_text) VALUES('delete', OLD.id, OLD.searchable_text); END; DROP TRIGGER IF EXISTS annotations_fts_update_trg; CREATE TRIGGER annotations_fts_update_trg AFTER UPDATE ON annotations BEGIN INSERT INTO annotations_fts(annotations_fts, rowid, searchable_text) VALUES('delete', OLD.id, OLD.searchable_text); INSERT INTO annotations_fts(rowid, searchable_text) VALUES (NEW.id, NEW.searchable_text); INSERT INTO annotations_fts_stemmed(annotations_fts_stemmed, rowid, searchable_text) VALUES('delete', OLD.id, OLD.searchable_text); INSERT INTO annotations_fts_stemmed(rowid, searchable_text) VALUES (NEW.id, NEW.searchable_text); END; DROP TRIGGER IF EXISTS books_delete_trg; CREATE TRIGGER books_delete_trg AFTER DELETE ON books BEGIN DELETE FROM books_authors_link WHERE book=OLD.id; DELETE FROM books_publishers_link WHERE book=OLD.id; DELETE FROM books_ratings_link WHERE book=OLD.id; DELETE FROM books_series_link WHERE book=OLD.id; DELETE FROM books_tags_link WHERE book=OLD.id; DELETE FROM books_languages_link WHERE book=OLD.id; DELETE FROM data WHERE book=OLD.id; DELETE FROM last_read_positions WHERE book=OLD.id; DELETE FROM annotations WHERE book=OLD.id; DELETE FROM comments WHERE book=OLD.id; DELETE FROM conversion_options WHERE book=OLD.id; DELETE FROM books_plugin_data WHERE book=OLD.id; DELETE FROM identifiers WHERE book=OLD.id; END; DROP TRIGGER IF EXISTS fkc_annot_insert; DROP TRIGGER IF EXISTS fkc_annot_update; CREATE TRIGGER fkc_annot_insert BEFORE INSERT ON annotations BEGIN SELECT CASE WHEN (SELECT id from books WHERE id=NEW.book) IS NULL THEN RAISE(ABORT, 'Foreign key violation: book not in books') END; END; CREATE TRIGGER fkc_annot_update BEFORE UPDATE OF book ON annotations BEGIN SELECT CASE WHEN (SELECT id from books WHERE id=NEW.book) IS NULL THEN RAISE(ABORT, 'Foreign key violation: book not in books') END; END; ''') def upgrade_version_24(self): self.db.reindex_annotations()