Пример #1
0
        static Updater()
        {
            // Update from version 0 to 1: Remove empty Other tags
            AddUpdate(new Version("1"), delegate() {
                string other_id = SelectSingleString("SELECT id FROM tags WHERE name = 'Other'");

                if (other_id == null)
                {
                    return;
                }

                // Don't do anything if there are subtags
                string tag_count = SelectSingleString(
                    String.Format("SELECT COUNT(*) FROM tags WHERE category_id = {0}", other_id));

                if (tag_count == null || System.Int32.Parse(tag_count) != 0)
                {
                    return;
                }

                // Don't do anything if there are photos tagged with this
                string photo_count = SelectSingleString(
                    String.Format("SELECT COUNT(*) FROM photo_tags WHERE tag_id = {0}", other_id));

                if (photo_count == null || System.Int32.Parse(photo_count) != 0)
                {
                    return;
                }

                // Finally, we know that the Other tag exists and has no children, so remove it
                Execute("DELETE FROM tags WHERE name = 'Other'");
            });

            // Update from version 1 to 2: Restore Other tags that were removed leaving dangling child tags
            AddUpdate(new Version("2"), delegate() {
                string tag_count = SelectSingleString("SELECT COUNT(*) FROM tags WHERE category_id != 0 AND category_id NOT IN (SELECT id FROM tags)");

                // If there are no dangling tags, then don't do anything
                if (tag_count == null || System.Int32.Parse(tag_count) == 0)
                {
                    return;
                }

                int id = ExecuteScalar("INSERT INTO tags (name, category_id, is_category, icon) VALUES ('Other', 0, 1, 'stock_icon:f-spot-other.png')");

                Execute(String.Format(
                            "UPDATE tags SET category_id = {0} WHERE id IN " +
                            "(SELECT id FROM tags WHERE category_id != 0 AND category_id " +
                            "NOT IN (SELECT id FROM tags))",
                            id));

                Log.Debug("Other tag restored.  Sorry about that!");
            });

            // Update from version 2 to 3: ensure that Hidden is the only tag left which is a real tag (not category)
            AddUpdate(new Version("3"), delegate() {
                Execute("UPDATE tags SET is_category = 1 WHERE name != 'Hidden'");
            });

            //Version 3.1, clean old (and unused) items in Export
            AddUpdate(new Version(3, 1), delegate() {
                if (TableExists("exports"))
                {
                    ExecuteScalar("DELETE FROM exports WHERE export_type='fspot:Folder'");
                }
            });

            //Version 4.0, bump the version number to a integer, for backward compatibility
            AddUpdate(new Version(4, 0), delegate() {});


            //Version 5.0, add a roll_id field to photos, rename table 'imports' to 'rolls'
            //and fix bgo 324425.
            AddUpdate(new Version(5, 0), delegate() {
                Log.Debug("Will add a roll_id field to photos!");
                string tmp_photos = MoveTableToTemp("photos");
                Execute(
                    "CREATE TABLE photos (                                     " +
                    "	id                 INTEGER PRIMARY KEY NOT NULL,   "+
                    "       time               INTEGER NOT NULL,	   	   "+
                    "       directory_path     STRING NOT NULL,		   "+
                    "       name               STRING NOT NULL,		   "+
                    "       description        TEXT NOT NULL,	           "+
                    "       roll_id            INTEGER NOT NULL,		   "+
                    "       default_version_id INTEGER NOT NULL		   "+
                    ")");
                ExecuteScalar(String.Format("INSERT INTO photos SELECT id, time, directory_path, name, description, 0, default_version_id FROM {0}", tmp_photos));

                Log.Debug("Will rename imports to rolls!");
                string tmp_rolls = MoveTableToTemp("imports");
                Execute(
                    "CREATE TABLE rolls (                                     " +
                    "	id                 INTEGER PRIMARY KEY NOT NULL,   "+
                    "       time               INTEGER NOT NULL	   	   " +
                    ")");
                ExecuteScalar(String.Format("INSERT INTO rolls SELECT id, time FROM {0}", tmp_rolls));

                Log.Debug("Cleaning weird descriptions, fixes bug #324425.");
                Execute("UPDATE photos SET description = \"\" WHERE description LIKE \"Invalid size of entry%\"");
            });


            //Version 6.0, change tag icon f-spot-tag-other to emblem-generic
            AddUpdate(new Version(6, 0), delegate() {
                ExecuteScalar("UPDATE tags SET icon = \"stock_icon:emblem-generic\" " +
                              " WHERE icon LIKE \"stock_icon:f-spot-other.png\"");
            });

            //Update to version 7.0, keep photo uri instead of path
            AddUpdate(new Version(7, 0), delegate() {
                string tmp_photos = MoveTableToTemp("photos");
                Execute(
                    "CREATE TABLE photos (" +
                    "	id                 INTEGER PRIMARY KEY NOT NULL,"+
                    "       time               INTEGER NOT NULL," +
                    "       uri                STRING NOT NULL," +
                    "       description        TEXT NOT NULL," +
                    "       roll_id            INTEGER NOT NULL," +
                    "       default_version_id INTEGER NOT NULL" +
                    ")");
                Execute(String.Format(
                            "INSERT INTO photos (id, time, uri, description, roll_id, default_version_id)	"+
                            "SELECT id, time, 'file://' || directory_path || '/' || name, 		"+
                            "description, roll_id, default_version_id FROM {0}", tmp_photos));
            }, true);

            // Update to version 8.0, store full version uri
            AddUpdate(new Version(8, 0), delegate() {
                string tmp_versions = MoveTableToTemp("photo_versions");
                Execute(
                    "CREATE TABLE photo_versions (          " +
                    "       photo_id        INTEGER,        " +
                    "       version_id      INTEGER,        " +
                    "       name            STRING,         " +
                    "       uri             STRING NOT NULL " +
                    ")");

                IDataReader reader = ExecuteReader(String.Format(
                                                       "SELECT photo_id, version_id, name, uri " +
                                                       "FROM {0}, photos " +
                                                       "WHERE photo_id = id ", tmp_versions));

                while (reader.Read())
                {
                    System.Uri photo_uri          = new System.Uri(reader [3] as string);
                    string name_without_extension = System.IO.Path.GetFileNameWithoutExtension(photo_uri.AbsolutePath);
                    string extension = System.IO.Path.GetExtension(photo_uri.AbsolutePath);

                    string uri = photo_uri.Scheme + "://" +
                                 photo_uri.Host +
                                 System.IO.Path.GetDirectoryName(photo_uri.AbsolutePath) + "/" +
                                 name_without_extension + " (" + (reader [2]).ToString() + ")" + extension;

                    Execute(new HyenaSqliteCommand(
                                "INSERT INTO photo_versions (photo_id, version_id, name, uri) " +
                                "VALUES (?, ?, ?, ?)",
                                Convert.ToUInt32(reader [0]),
                                Convert.ToUInt32(reader [1]),
                                (reader [2]).ToString(),
                                uri));
                }
            }, true);

            // Update to version 9.0
            AddUpdate(new Version(9, 0), delegate() {
                string tmp_versions = MoveTableToTemp("photo_versions");
                Execute(
                    "CREATE TABLE photo_versions (          " +
                    "       photo_id        INTEGER,        " +
                    "       version_id      INTEGER,        " +
                    "       name            STRING,         " +
                    "       uri             STRING NOT NULL," +
                    "	protected	BOOLEAN		"+
                    ")");
                Execute(String.Format(
                            "INSERT INTO photo_versions (photo_id, version_id, name, uri, protected) " +
                            "SELECT photo_id, version_id, name, uri, 0 " +
                            "FROM {0} ", tmp_versions));
            });

            // Update to version 10.0, make id autoincrement
            AddUpdate(new Version(10, 0), delegate() {
                string tmp_photos = MoveTableToTemp("photos");
                Execute(
                    "CREATE TABLE photos (                                     " +
                    "	id                 INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "+
                    "	time               INTEGER NOT NULL,	   	   "+
                    "	uri		   STRING NOT NULL,		   "+
                    "	description        TEXT NOT NULL,	           "+
                    "	roll_id            INTEGER NOT NULL,		   "+
                    "	default_version_id INTEGER NOT NULL		   "+
                    ")");

                Execute(String.Format(
                            "INSERT INTO photos (id, time, uri, description, roll_id, default_version_id) " +
                            "SELECT id, time, uri, description, roll_id, default_version_id  " +
                            "FROM  {0} ", tmp_photos));
            }, false);

            // Update to version 11.0, rating
            AddUpdate(new Version(11, 0), delegate() {
                string tmp_photos = MoveTableToTemp("photos");
                Execute(
                    "CREATE TABLE photos (                                     " +
                    "	id                 INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "+
                    "	time               INTEGER NOT NULL,	   	   "+
                    "	uri		   STRING NOT NULL,		   "+
                    "	description        TEXT NOT NULL,	           "+
                    "	roll_id            INTEGER NOT NULL,		   "+
                    "	default_version_id INTEGER NOT NULL,		   "+
                    "       rating             INTEGER NULL			   "+
                    ")");

                Execute(String.Format(
                            "INSERT INTO photos (id, time, uri, description, roll_id, default_version_id, rating) " +
                            "SELECT id, time, uri, description, roll_id, default_version_id, null  " +
                            "FROM  {0} ", tmp_photos));
            });

            //Update to version 12.0, remove dead associations, bgo #507950, #488545
            AddUpdate(new Version(12, 0), delegate() {
                Execute("DELETE FROM photo_tags WHERE tag_id NOT IN (SELECT id FROM tags)");
            });

            // Update to version 13.0
            AddUpdate(new Version(13, 0), delegate() {
                Execute("UPDATE photos SET rating = 0 WHERE rating IS NULL");
            });

            // Update to version 14.0
            AddUpdate(new Version(14, 0), delegate() {
                Execute("UPDATE photos SET rating = 0 WHERE rating IS NULL");
            });

            // Update to version 15.0
            AddUpdate(new Version(15, 0), delegate() {
                string tmp_photo_tags = MoveTableToTemp("photo_tags");
                Execute(
                    "CREATE TABLE photo_tags (        " +
                    "	photo_id      INTEGER,    "+
                    "       tag_id        INTEGER,    " +
                    "       UNIQUE (photo_id, tag_id) " +
                    ")");
                Execute(String.Format(
                            "INSERT OR IGNORE INTO photo_tags (photo_id, tag_id) " +
                            "SELECT photo_id, tag_id FROM {0}", tmp_photo_tags));
                string tmp_photo_versions = MoveTableToTemp("photo_versions");
                Execute(
                    "CREATE TABLE photo_versions (		"+
                    "	photo_id	INTEGER,	"+
                    "	version_id	INTEGER,	"+
                    "	name		STRING,		"+
                    "	uri		STRING NOT NULL,"+
                    "	protected	BOOLEAN, 	"+
                    "	UNIQUE (photo_id, version_id)	"+
                    ")");
                Execute(String.Format(
                            "INSERT OR IGNORE INTO photo_versions 		"+
                            "(photo_id, version_id, name, uri, protected)	"+
                            "SELECT photo_id, version_id, name, uri, protected FROM {0}", tmp_photo_versions));
            });

            // Update to version 16.0
            AddUpdate(new Version(16, 0), delegate() {
                string temp_table = MoveTableToTemp("photos");

                Execute("CREATE TABLE photos ( " +
                        "	id                 INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,   "+
                        "	time               INTEGER NOT NULL,	   	   "+
                        "	uri		   STRING NOT NULL,		   "+
                        "	description        TEXT NOT NULL,	           "+
                        "	roll_id            INTEGER NOT NULL,		   "+
                        "	default_version_id INTEGER NOT NULL,		   "+
                        "	rating		   INTEGER NULL,		   "+
                        "	md5_sum		   TEXT NULL  			   "+
                        ")"
                        );

                Execute(string.Format("INSERT INTO photos (id, time, uri, description, roll_id, " +
                                      "default_version_id, rating, md5_sum) " +
                                      "SELECT id, time, uri, description, roll_id, " +
                                      "       default_version_id, rating, '' " +
                                      "FROM   {0} ",
                                      temp_table
                                      )
                        );


                string temp_versions_table = MoveTableToTemp("photo_versions");

                Execute("CREATE TABLE photo_versions (    	"+
                        "      photo_id        INTEGER,  	"+
                        "      version_id      INTEGER,  	"+
                        "      name            STRING,    	"+
                        "	uri		STRING NOT NULL,"+
                        "	md5_sum		STRING NOT NULL,"+
                        "	protected	BOOLEAN		"+
                        ")");

                Execute(String.Format("INSERT INTO photo_versions (photo_id, version_id, name, uri, md5_sum, protected) " +
                                      "SELECT photo_id, version_id, name, uri, '', protected " +
                                      "FROM   {0} ",
                                      temp_versions_table
                                      )
                        );

                JobStore.CreateTable(db);

                // This is kind of hacky but should be a lot faster on
                // large photo databases
                Execute(string.Format("INSERT INTO jobs (job_type, job_options, run_at, job_priority) " +
                                      "SELECT '{0}', id, {1}, {2} " +
                                      "FROM   photos ",
                                      typeof(Jobs.CalculateHashJob).ToString(),
                                      DateTimeUtil.FromDateTime(DateTime.Now),
                                      0
                                      )
                        );
            }, true);

            // Update to version 16.1
            AddUpdate(new Version(16, 1), delegate() {
                Execute("CREATE INDEX idx_photo_versions_id ON photo_versions(photo_id)");
            }, false);

            // Update to version 16.2
            AddUpdate(new Version(16, 2), delegate() {
                Execute("CREATE INDEX idx_photos_roll_id ON photos(roll_id)");
            }, false);

            // Update to version 16.3
            AddUpdate(new Version(16, 3), delegate() {
                Execute(String.Format("DELETE FROM jobs WHERE job_type = '{0}'", typeof(Jobs.CalculateHashJob).ToString()));
            }, false);

            // Update to version 16.4
            AddUpdate(new Version(16, 4), delegate() {                //fix the tables schema EOL
                string temp_table = MoveTableToTemp("exports");
                Execute(
                    "CREATE TABLE exports (\n" +
                    "	id			INTEGER PRIMARY KEY NOT NULL, \n"+
                    "	image_id		INTEGER NOT NULL, \n"+
                    "	image_version_id	INTEGER NOT NULL, \n"+
                    "	export_type		TEXT NOT NULL, \n"+
                    "	export_token		TEXT NOT NULL\n"+
                    ")");
                Execute(String.Format(
                            "INSERT INTO exports (id, image_id, image_version_id, export_type, export_token) " +
                            "SELECT id, image_id, image_version_id, export_type, export_token " +
                            "FROM {0}", temp_table));

                temp_table = MoveTableToTemp("jobs");
                Execute(
                    "CREATE TABLE jobs (\n" +
                    "	id		INTEGER PRIMARY KEY NOT NULL, \n"+
                    "	job_type	TEXT NOT NULL, \n"+
                    "	job_options	TEXT NOT NULL, \n"+
                    "	run_at		INTEGER, \n"+
                    "	job_priority	INTEGER NOT NULL\n"+
                    ")");
                Execute(String.Format(
                            "INSERT INTO jobs (id, job_type, job_options, run_at, job_priority) " +
                            "SELECT id, job_type, job_options, run_at, job_priority " +
                            "FROM {0}", temp_table));

                temp_table = MoveTableToTemp("meta");
                Execute(
                    "CREATE TABLE meta (\n" +
                    "	id	INTEGER PRIMARY KEY NOT NULL, \n"+
                    "	name	TEXT UNIQUE NOT NULL, \n"+
                    "	data	TEXT\n"+
                    ")");
                Execute(String.Format(
                            "INSERT INTO meta (id, name, data) " +
                            "SELECT id, name, data " +
                            "FROM {0}", temp_table));

                temp_table = MoveTableToTemp("photos");
                Execute(
                    "CREATE TABLE photos (\n" +
                    "	id			INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, \n"+
                    "	time			INTEGER NOT NULL, \n"+
                    "	uri			STRING NOT NULL, \n"+
                    "	description		TEXT NOT NULL, \n"+
                    "	roll_id			INTEGER NOT NULL, \n"+
                    "	default_version_id	INTEGER NOT NULL, \n"+
                    "	rating			INTEGER NULL, \n"+
                    "	md5_sum			TEXT NULL\n"+
                    ")");
                Execute(String.Format(
                            "INSERT INTO photos (id, time, uri, description, roll_id, default_version_id, rating, md5_sum) " +
                            "SELECT id, time, uri, description, roll_id, default_version_id, rating, md5_sum " +
                            "FROM {0}", temp_table));

                temp_table = MoveTableToTemp("photo_tags");
                Execute(
                    "CREATE TABLE photo_tags (\n" +
                    "	photo_id	INTEGER, \n"+
                    "       tag_id		INTEGER, \n"+
                    "       UNIQUE (photo_id, tag_id)\n" +
                    ")");
                Execute(String.Format(
                            "INSERT OR IGNORE INTO photo_tags (photo_id, tag_id) " +
                            "SELECT photo_id, tag_id " +
                            "FROM {0}", temp_table));

                temp_table = MoveTableToTemp("photo_versions");
                Execute(
                    "CREATE TABLE photo_versions (\n" +
                    "	photo_id	INTEGER, \n"+
                    "	version_id	INTEGER, \n"+
                    "	name		STRING, \n"+
                    "	uri		STRING NOT NULL, \n"+
                    "	md5_sum		STRING NOT NULL, \n"+
                    "	protected	BOOLEAN, \n"+
                    "	UNIQUE (photo_id, version_id)\n"+
                    ")");
                Execute(String.Format(
                            "INSERT OR IGNORE INTO photo_versions (photo_id, version_id, name, uri, md5_sum, protected) " +
                            "SELECT photo_id, version_id, name, uri, md5_sum, protected " +
                            "FROM {0}", temp_table));

                Execute("CREATE INDEX idx_photo_versions_id ON photo_versions(photo_id)");
                Execute("CREATE INDEX idx_photos_roll_id ON photos(roll_id)");

                temp_table = MoveTableToTemp("rolls");
                Execute(
                    "CREATE TABLE rolls (\n" +
                    "	id	INTEGER PRIMARY KEY NOT NULL, \n"+
                    "       time	INTEGER NOT NULL\n"+
                    ")");
                Execute(String.Format(
                            "INSERT INTO rolls (id, time) " +
                            "SELECT id, time " +
                            "FROM {0}", temp_table));

                temp_table = MoveTableToTemp("tags");
                Execute(
                    "CREATE TABLE tags (\n" +
                    "	id		INTEGER PRIMARY KEY NOT NULL, \n"+
                    "	name		TEXT UNIQUE, \n"+
                    "	category_id	INTEGER, \n"+
                    "	is_category	BOOLEAN, \n"+
                    "	sort_priority	INTEGER, \n"+
                    "	icon		TEXT\n"+
                    ")");
                Execute(String.Format(
                            "INSERT INTO tags (id, name, category_id, is_category, sort_priority, icon) " +
                            "SELECT id, name, category_id, is_category, sort_priority, icon " +
                            "FROM {0}", temp_table));
            });

            // Update to version 16.5
            AddUpdate(new Version(16, 5), delegate() {                //fix md5 null in photos and photo_versions table
                string temp_table = MoveTableToTemp("photo_versions");
                Execute(
                    "CREATE TABLE photo_versions (\n" +
                    "	photo_id	INTEGER, \n"+
                    "	version_id	INTEGER, \n"+
                    "	name		STRING, \n"+
                    "	uri		STRING NOT NULL, \n"+
                    "	md5_sum		TEXT NULL, \n"+
                    "	protected	BOOLEAN, \n"+
                    "	UNIQUE (photo_id, version_id)\n"+
                    ")");
                Execute(String.Format(
                            "INSERT OR IGNORE INTO photo_versions (photo_id, version_id, name, uri, md5_sum, protected) " +
                            "SELECT photo_id, version_id, name, uri, md5_sum, protected " +
                            "FROM {0}", temp_table));

                Execute("CREATE INDEX idx_photo_versions_id ON photo_versions(photo_id)");

                Execute("UPDATE photos SET md5_sum = NULL WHERE md5_sum = ''");
                Execute("UPDATE photo_versions SET md5_sum = NULL WHERE md5_sum = ''");
            });

            // Update to version 17.0, split uri and filename
            AddUpdate(new Version(17, 0), delegate() {
                string tmp_photos   = MoveTableToTemp("photos");
                string tmp_versions = MoveTableToTemp("photo_versions");

                Execute(
                    "CREATE TABLE photos (\n" +
                    "	id			INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, \n"+
                    "	time			INTEGER NOT NULL, \n"+
                    "	base_uri		STRING NOT NULL, \n"+
                    "	filename		STRING NOT NULL, \n"+
                    "	description		TEXT NOT NULL, \n"+
                    "	roll_id			INTEGER NOT NULL, \n"+
                    "	default_version_id	INTEGER NOT NULL, \n"+
                    "	rating			INTEGER NULL, \n"+
                    "	md5_sum			TEXT NULL\n"+
                    ")");

                Execute(
                    "CREATE TABLE photo_versions (\n" +
                    "	photo_id	INTEGER, \n"+
                    "	version_id	INTEGER, \n"+
                    "	name		STRING, \n"+
                    "	base_uri		STRING NOT NULL, \n"+
                    "	filename		STRING NOT NULL, \n"+
                    "	md5_sum		TEXT NULL, \n"+
                    "	protected	BOOLEAN, \n"+
                    "	UNIQUE (photo_id, version_id)\n"+
                    ")");

                IDataReader reader = ExecuteReader(String.Format(
                                                       "SELECT id, time, uri, description, roll_id, default_version_id, rating, md5_sum " +
                                                       "FROM {0} ", tmp_photos));

                while (reader.Read())
                {
                    System.Uri photo_uri = new System.Uri(reader ["uri"] as string);

                    string filename = photo_uri.GetFilename();
                    Uri base_uri    = photo_uri.GetDirectoryUri();

                    string md5 = reader ["md5_sum"] != null ? reader ["md5_sum"].ToString() : null;

                    Execute(new HyenaSqliteCommand(
                                "INSERT INTO photos (id, time, base_uri, filename, description, roll_id, default_version_id, rating, md5_sum) " +
                                "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
                                Convert.ToUInt32(reader ["id"]),
                                reader ["time"],
                                base_uri.ToString(),
                                filename,
                                reader ["description"].ToString(),
                                Convert.ToUInt32(reader ["roll_id"]),
                                Convert.ToUInt32(reader ["default_version_id"]),
                                Convert.ToUInt32(reader ["rating"]),
                                String.IsNullOrEmpty(md5) ? null : md5));
                }

                reader.Dispose();

                reader = ExecuteReader(String.Format(
                                           "SELECT photo_id, version_id, name, uri, md5_sum, protected " +
                                           "FROM {0} ", tmp_versions));

                while (reader.Read())
                {
                    System.Uri photo_uri = new System.Uri(reader ["uri"] as string);

                    string filename = photo_uri.GetFilename();
                    Uri base_uri    = photo_uri.GetDirectoryUri();

                    string md5 = reader ["md5_sum"] != null ? reader ["md5_sum"].ToString() : null;

                    Execute(new HyenaSqliteCommand(
                                "INSERT INTO photo_versions (photo_id, version_id, name, base_uri, filename, protected, md5_sum) " +
                                "VALUES (?, ?, ?, ?, ?, ?, ?)",
                                Convert.ToUInt32(reader ["photo_id"]),
                                Convert.ToUInt32(reader ["version_id"]),
                                reader ["name"].ToString(),
                                base_uri.ToString(),
                                filename,
                                Convert.ToBoolean(reader ["protected"]),
                                String.IsNullOrEmpty(md5) ? null : md5));
                }

                Execute("CREATE INDEX idx_photos_roll_id ON photos(roll_id)");
                Execute("CREATE INDEX idx_photo_versions_id ON photo_versions(photo_id)");
            }, true);

            // Update to version 17.1, Rename 'Import Tags' to 'Imported Tags'
            AddUpdate(new Version(17, 1), delegate() {
                Execute("UPDATE tags SET name = 'Imported Tags' WHERE name = 'Import Tags'");
            });

            // Update to version 17.2, Make sure every photo has an Original version in photo_versions
            AddUpdate(new Version(17, 2), delegate() {
                // Find photos that have no original version;
                var have_original_query = "SELECT id FROM photos LEFT JOIN photo_versions AS pv ON pv.photo_id = id WHERE pv.version_id = 1";
                var no_original_query   = String.Format("SELECT id, base_uri, filename FROM photos WHERE id NOT IN ({0})", have_original_query);

                var reader = ExecuteReader(no_original_query);

                while (reader.Read())
                {
                    Execute(new HyenaSqliteCommand(
                                "INSERT INTO photo_versions (photo_id, version_id, name, base_uri, filename, protected, md5_sum) " +
                                "VALUES (?, ?, ?, ?, ?, ?, ?)",
                                Convert.ToUInt32(reader ["id"]),
                                1,
                                "Original",
                                reader ["base_uri"].ToString(),
                                reader ["filename"].ToString(),
                                1,
                                ""));
                }
            }, true);

            // Update to version 18.0, Import MD5 hashes
            AddUpdate(new Version(18, 0), delegate() {
                string tmp_photos   = MoveTableToTemp("photos");
                string tmp_versions = MoveTableToTemp("photo_versions");

                Execute(
                    "CREATE TABLE photos (\n" +
                    "	id			INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, \n"+
                    "	time			INTEGER NOT NULL, \n"+
                    "	base_uri		STRING NOT NULL, \n"+
                    "	filename		STRING NOT NULL, \n"+
                    "	description		TEXT NOT NULL, \n"+
                    "	roll_id			INTEGER NOT NULL, \n"+
                    "	default_version_id	INTEGER NOT NULL, \n"+
                    "	rating			INTEGER NULL \n"+
                    ")");

                Execute(
                    "CREATE TABLE photo_versions (\n" +
                    "	photo_id	INTEGER, \n"+
                    "	version_id	INTEGER, \n"+
                    "	name		STRING, \n"+
                    "	base_uri		STRING NOT NULL, \n"+
                    "	filename		STRING NOT NULL, \n"+
                    "	import_md5		TEXT NULL, \n"+
                    "	protected	BOOLEAN, \n"+
                    "	UNIQUE (photo_id, version_id)\n"+
                    ")");

                var reader = ExecuteReader(String.Format(
                                               "SELECT id, time, base_uri, filename, description, roll_id, default_version_id, rating " +
                                               "FROM {0} ", tmp_photos));

                while (reader.Read())
                {
                    Execute(new HyenaSqliteCommand(
                                "INSERT INTO photos (id, time, base_uri, filename, description, roll_id, default_version_id, rating) " +
                                "VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
                                Convert.ToUInt32(reader ["id"]),
                                reader ["time"],
                                reader ["base_uri"].ToString(),
                                reader ["filename"].ToString(),
                                reader ["description"].ToString(),
                                Convert.ToUInt32(reader ["roll_id"]),
                                Convert.ToUInt32(reader ["default_version_id"]),
                                Convert.ToUInt32(reader ["rating"])));
                }

                reader.Dispose();

                reader = ExecuteReader(String.Format(
                                           "SELECT photo_id, version_id, name, base_uri, filename, protected " +
                                           "FROM {0} ", tmp_versions));

                while (reader.Read())
                {
                    Execute(new HyenaSqliteCommand(
                                "INSERT INTO photo_versions (photo_id, version_id, name, base_uri, filename, protected, import_md5) " +
                                "VALUES (?, ?, ?, ?, ?, ?, ?)",
                                Convert.ToUInt32(reader ["photo_id"]),
                                Convert.ToUInt32(reader ["version_id"]),
                                reader ["name"].ToString(),
                                reader ["base_uri"].ToString(),
                                reader ["filename"].ToString(),
                                Convert.ToBoolean(reader ["protected"]),
                                ""));
                }

                Execute("CREATE INDEX idx_photo_versions_import_md5 ON photo_versions(import_md5)");
            }, true);
        }