Copy database_upgrader Migrator from Etiquette.
This commit is contained in:
		
							parent
							
								
									1e59d7be06
								
							
						
					
					
						commit
						885bc2b711
					
				
					 1 changed files with 167 additions and 97 deletions
				
			
		|  | @ -6,12 +6,75 @@ import sys | ||||||
| import bot | import bot | ||||||
| import ycdl | import ycdl | ||||||
| 
 | 
 | ||||||
|  | class Migrator: | ||||||
|  |     ''' | ||||||
|  |     Many of the upgraders involve adding columns. ALTER TABLE ADD COLUMN only | ||||||
|  |     allows adding at the end, which I usually don't prefer. In order to add a | ||||||
|  |     column in the middle, you must rename the table, create a new one, transfer | ||||||
|  |     the data, and drop the old one. But, foreign keys and indices will still | ||||||
|  |     point to the old table, which causes broken foreign keys and dropped | ||||||
|  |     indices. So, the only way to prevent all that is to regenerate all affected | ||||||
|  |     tables and indices. Rather than parsing relationships to determine the | ||||||
|  |     affected tables, this implementation just regenerates everything. | ||||||
|  | 
 | ||||||
|  |     It's kind of horrible but it allows me to have the columns in the order I | ||||||
|  |     want instead of just always appending. Besides, modifying collations cannot | ||||||
|  |     be done in-place either. | ||||||
|  | 
 | ||||||
|  |     If you want to truly remove a table or index and not have it get | ||||||
|  |     regenerated, just do that before instantiating the Migrator. | ||||||
|  |     ''' | ||||||
|  |     def __init__(self, ycdldb): | ||||||
|  |         self.ycdldb = ycdldb | ||||||
|  | 
 | ||||||
|  |         query = 'SELECT name, sql FROM sqlite_master WHERE type == "table"' | ||||||
|  |         self.tables = { | ||||||
|  |             name: {'create': sql, 'transfer': f'INSERT INTO {name} SELECT * FROM {name}_old'} | ||||||
|  |             for (name, sql) in self.ycdldb.sql_select(query) | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         # The user may be adding entirely new tables derived from the data of | ||||||
|  |         # old ones. We'll need to skip new tables for the rename and drop_old | ||||||
|  |         # steps. So we track which tables already existed at the beginning. | ||||||
|  |         self.existing_tables = set(self.tables) | ||||||
|  | 
 | ||||||
|  |         query = 'SELECT name, sql FROM sqlite_master WHERE type == "index" AND name NOT LIKE "sqlite_%"' | ||||||
|  |         self.indices = list(self.ycdldb.sql_select(query)) | ||||||
|  | 
 | ||||||
|  |     def go(self): | ||||||
|  |         # This loop is split in many parts, because otherwise if table A | ||||||
|  |         # references table B and table A is completely reconstructed, it will | ||||||
|  |         # be pointing to the version of B which has not been reconstructed yet, | ||||||
|  |         # which is about to get renamed to B_old and then A's reference will be | ||||||
|  |         # broken. | ||||||
|  |         self.ycdldb.sql_execute('PRAGMA foreign_keys = OFF') | ||||||
|  |         self.ycdldb.sql_execute('BEGIN') | ||||||
|  |         for (name, table) in self.tables.items(): | ||||||
|  |             if name not in self.existing_tables: | ||||||
|  |                 continue | ||||||
|  |             self.ycdldb.sql_execute(f'ALTER TABLE {name} RENAME TO {name}_old') | ||||||
|  | 
 | ||||||
|  |         for (name, table) in self.tables.items(): | ||||||
|  |             self.ycdldb.sql_execute(table['create']) | ||||||
|  | 
 | ||||||
|  |         for (name, table) in self.tables.items(): | ||||||
|  |             self.ycdldb.sql_execute(table['transfer']) | ||||||
|  | 
 | ||||||
|  |         for (name, query) in self.tables.items(): | ||||||
|  |             if name not in self.existing_tables: | ||||||
|  |                 continue | ||||||
|  |             self.ycdldb.sql_execute(f'DROP TABLE {name}_old') | ||||||
|  | 
 | ||||||
|  |         for (name, query) in self.indices: | ||||||
|  |             self.ycdldb.sql_execute(query) | ||||||
|  | 
 | ||||||
| def upgrade_1_to_2(ycdldb): | def upgrade_1_to_2(ycdldb): | ||||||
|     ''' |     ''' | ||||||
|     In this version, the `duration` column was added to the videos table. |     In this version, the `duration` column was added to the videos table. | ||||||
|     ''' |     ''' | ||||||
|     ycdldb.sql.execute('ALTER TABLE videos RENAME TO videos_old') |     m = Migrator(ycdldb) | ||||||
|     ycdldb.sql.execute(''' | 
 | ||||||
|  |     m.tables['videos']['create'] = ''' | ||||||
|     CREATE TABLE videos( |     CREATE TABLE videos( | ||||||
|         id TEXT, |         id TEXT, | ||||||
|         published INT, |         published INT, | ||||||
|  | @ -21,9 +84,9 @@ def upgrade_1_to_2(ycdldb): | ||||||
|         duration INT, |         duration INT, | ||||||
|         thumbnail TEXT, |         thumbnail TEXT, | ||||||
|         download TEXT |         download TEXT | ||||||
|         ) |     ); | ||||||
|     ''') |     ''' | ||||||
|     ycdldb.sql.execute(''' |     m.tables['videos']['transfer'] = ''' | ||||||
|     INSERT INTO videos SELECT |     INSERT INTO videos SELECT | ||||||
|         id, |         id, | ||||||
|         published, |         published, | ||||||
|  | @ -33,9 +96,10 @@ def upgrade_1_to_2(ycdldb): | ||||||
|         NULL, |         NULL, | ||||||
|         thumbnail, |         thumbnail, | ||||||
|         download |         download | ||||||
|         FROM videos_old |     FROM videos_old; | ||||||
|     ''') |     ''' | ||||||
|     ycdldb.sql.execute('DROP TABLE videos_old') | 
 | ||||||
|  |     m.go() | ||||||
| 
 | 
 | ||||||
| def upgrade_2_to_3(ycdldb): | def upgrade_2_to_3(ycdldb): | ||||||
|     ''' |     ''' | ||||||
|  | @ -48,8 +112,9 @@ def upgrade_3_to_4(ycdldb): | ||||||
|     ''' |     ''' | ||||||
|     In this version, the `views` column was added to the videos table. |     In this version, the `views` column was added to the videos table. | ||||||
|     ''' |     ''' | ||||||
|     ycdldb.sql.execute('ALTER TABLE videos RENAME TO videos_old') |     m = Migrator(ycdldb) | ||||||
|     ycdldb.sql.execute(''' | 
 | ||||||
|  |     m.tables['videos']['create'] = ''' | ||||||
|     CREATE TABLE videos( |     CREATE TABLE videos( | ||||||
|         id TEXT, |         id TEXT, | ||||||
|         published INT, |         published INT, | ||||||
|  | @ -60,9 +125,9 @@ def upgrade_3_to_4(ycdldb): | ||||||
|         views INT, |         views INT, | ||||||
|         thumbnail TEXT, |         thumbnail TEXT, | ||||||
|         download TEXT |         download TEXT | ||||||
|         ) |     ); | ||||||
|     ''') |     ''' | ||||||
|     ycdldb.sql.execute(''' |     m.tables['videos']['transfer'] = ''' | ||||||
|     INSERT INTO videos SELECT |     INSERT INTO videos SELECT | ||||||
|         id, |         id, | ||||||
|         published, |         published, | ||||||
|  | @ -73,34 +138,37 @@ def upgrade_3_to_4(ycdldb): | ||||||
|         NULL, |         NULL, | ||||||
|         thumbnail, |         thumbnail, | ||||||
|         download |         download | ||||||
|         FROM videos_old |     FROM videos_old; | ||||||
|     ''') |     ''' | ||||||
|     ycdldb.sql.execute('DROP TABLE videos_old') | 
 | ||||||
|  |     m.go() | ||||||
| 
 | 
 | ||||||
| def upgrade_4_to_5(ycdldb): | def upgrade_4_to_5(ycdldb): | ||||||
|     ''' |     ''' | ||||||
|     In this version, the `uploads_playlist` column was added to the channels table. |     In this version, the `uploads_playlist` column was added to the channels table. | ||||||
|     ''' |     ''' | ||||||
|     ycdldb.sql.execute('ALTER TABLE channels RENAME TO channels_old') |     m = Migrator(ycdldb) | ||||||
|     ycdldb.sql.execute(''' | 
 | ||||||
|  |     m.tables['channels']['create'] = ''' | ||||||
|     CREATE TABLE channels( |     CREATE TABLE channels( | ||||||
|         id TEXT, |         id TEXT, | ||||||
|         name TEXT, |         name TEXT, | ||||||
|         uploads_playlist TEXT, |         uploads_playlist TEXT, | ||||||
|         directory TEXT COLLATE NOCASE, |         directory TEXT COLLATE NOCASE, | ||||||
|         automark TEXT |         automark TEXT | ||||||
|         ) |     ); | ||||||
|     ''') |     ''' | ||||||
|     ycdldb.sql.execute(''' |     m.tables['channels']['transfer'] = ''' | ||||||
|     INSERT INTO channels SELECT |     INSERT INTO channels SELECT | ||||||
|         id, |         id, | ||||||
|         name, |         name, | ||||||
|         NULL, |         NULL, | ||||||
|         directory, |         directory, | ||||||
|         automark |         automark | ||||||
|         FROM channels_old |     FROM channels_old; | ||||||
|     ''') |     ''' | ||||||
|     ycdldb.sql.execute('DROP TABLE channels_old') | 
 | ||||||
|  |     m.go() | ||||||
| 
 | 
 | ||||||
|     rows = ycdldb.sql.execute('SELECT id FROM channels').fetchall() |     rows = ycdldb.sql.execute('SELECT id FROM channels').fetchall() | ||||||
|     channels = [row[0] for row in rows] |     channels = [row[0] for row in rows] | ||||||
|  | @ -121,8 +189,9 @@ def upgrade_5_to_6(ycdldb): | ||||||
|     to `download_directory` to be in line with the default config's name for |     to `download_directory` to be in line with the default config's name for | ||||||
|     the same value, and the `queuefile_extension` column was added. |     the same value, and the `queuefile_extension` column was added. | ||||||
|     ''' |     ''' | ||||||
|     ycdldb.sql.execute('ALTER TABLE channels RENAME TO channels_old') |     m = Migrator(ycdldb) | ||||||
|     ycdldb.sql.execute(''' | 
 | ||||||
|  |     m.tables['channels']['create'] = ''' | ||||||
|     CREATE TABLE channels( |     CREATE TABLE channels( | ||||||
|         id TEXT, |         id TEXT, | ||||||
|         name TEXT, |         name TEXT, | ||||||
|  | @ -130,9 +199,9 @@ def upgrade_5_to_6(ycdldb): | ||||||
|         download_directory TEXT COLLATE NOCASE, |         download_directory TEXT COLLATE NOCASE, | ||||||
|         queuefile_extension TEXT COLLATE NOCASE, |         queuefile_extension TEXT COLLATE NOCASE, | ||||||
|         automark TEXT |         automark TEXT | ||||||
|         ) |     ); | ||||||
|     ''') |     ''' | ||||||
|     ycdldb.sql.execute(''' |     m.tables['channels']['transfer'] = ''' | ||||||
|     INSERT INTO channels SELECT |     INSERT INTO channels SELECT | ||||||
|         id, |         id, | ||||||
|         name, |         name, | ||||||
|  | @ -140,9 +209,10 @@ def upgrade_5_to_6(ycdldb): | ||||||
|         directory, |         directory, | ||||||
|         NULL, |         NULL, | ||||||
|         automark |         automark | ||||||
|         FROM channels_old |     FROM channels_old; | ||||||
|     ''') |     ''' | ||||||
|     ycdldb.sql.execute('DROP TABLE channels_old') | 
 | ||||||
|  |     m.go() | ||||||
| 
 | 
 | ||||||
| def upgrade_6_to_7(ycdldb): | def upgrade_6_to_7(ycdldb): | ||||||
|     ''' |     ''' | ||||||
|  | @ -203,7 +273,7 @@ def upgrade_all(data_directory): | ||||||
|         upgrade_function = eval(upgrade_function) |         upgrade_function = eval(upgrade_function) | ||||||
| 
 | 
 | ||||||
|         try: |         try: | ||||||
|             ycdldb.sql.execute('BEGIN') |             ycdldb.sql.execute('PRAGMA foreign_keys = ON') | ||||||
|             upgrade_function(ycdldb) |             upgrade_function(ycdldb) | ||||||
|         except Exception as exc: |         except Exception as exc: | ||||||
|             ycdldb.rollback() |             ycdldb.rollback() | ||||||
|  |  | ||||||
		Loading…
	
		Reference in a new issue