toolkit/mozapps/extensions/internal/AddonRepository_SQLiteMigrator.jsm

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

michael@0 1 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 2 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 4
michael@0 5 "use strict";
michael@0 6
michael@0 7 const Cc = Components.classes;
michael@0 8 const Ci = Components.interfaces;
michael@0 9 const Cu = Components.utils;
michael@0 10
michael@0 11 Cu.import("resource://gre/modules/Services.jsm");
michael@0 12 Cu.import("resource://gre/modules/AddonManager.jsm");
michael@0 13 Cu.import("resource://gre/modules/FileUtils.jsm");
michael@0 14
michael@0 15 const KEY_PROFILEDIR = "ProfD";
michael@0 16 const FILE_DATABASE = "addons.sqlite";
michael@0 17 const LAST_DB_SCHEMA = 4;
michael@0 18
michael@0 19 // Add-on properties present in the columns of the database
michael@0 20 const PROP_SINGLE = ["id", "type", "name", "version", "creator", "description",
michael@0 21 "fullDescription", "developerComments", "eula",
michael@0 22 "homepageURL", "supportURL", "contributionURL",
michael@0 23 "contributionAmount", "averageRating", "reviewCount",
michael@0 24 "reviewURL", "totalDownloads", "weeklyDownloads",
michael@0 25 "dailyUsers", "sourceURI", "repositoryStatus", "size",
michael@0 26 "updateDate"];
michael@0 27
michael@0 28 Cu.import("resource://gre/modules/Log.jsm");
michael@0 29 const LOGGER_ID = "addons.repository.sqlmigrator";
michael@0 30
michael@0 31 // Create a new logger for use by the Addons Repository SQL Migrator
michael@0 32 // (Requires AddonManager.jsm)
michael@0 33 let logger = Log.repository.getLogger(LOGGER_ID);
michael@0 34
michael@0 35 this.EXPORTED_SYMBOLS = ["AddonRepository_SQLiteMigrator"];
michael@0 36
michael@0 37
michael@0 38 this.AddonRepository_SQLiteMigrator = {
michael@0 39
michael@0 40 /**
michael@0 41 * Migrates data from a previous SQLite version of the
michael@0 42 * database to the JSON version.
michael@0 43 *
michael@0 44 * @param structFunctions an object that contains functions
michael@0 45 * to create the various objects used
michael@0 46 * in the new JSON format
michael@0 47 * @param aCallback A callback to be called when migration
michael@0 48 * finishes, with the results in an array
michael@0 49 * @returns bool True if a migration will happen (DB was
michael@0 50 * found and succesfully opened)
michael@0 51 */
michael@0 52 migrate: function(aCallback) {
michael@0 53 if (!this._openConnection()) {
michael@0 54 this._closeConnection();
michael@0 55 aCallback([]);
michael@0 56 return false;
michael@0 57 }
michael@0 58
michael@0 59 logger.debug("Importing addon repository from previous " + FILE_DATABASE + " storage.");
michael@0 60
michael@0 61 this._retrieveStoredData((results) => {
michael@0 62 this._closeConnection();
michael@0 63 let resultArray = [addon for ([,addon] of Iterator(results))];
michael@0 64 logger.debug(resultArray.length + " addons imported.")
michael@0 65 aCallback(resultArray);
michael@0 66 });
michael@0 67
michael@0 68 return true;
michael@0 69 },
michael@0 70
michael@0 71 /**
michael@0 72 * Synchronously opens a new connection to the database file.
michael@0 73 *
michael@0 74 * @return bool Whether the DB was opened successfully.
michael@0 75 */
michael@0 76 _openConnection: function AD_openConnection() {
michael@0 77 delete this.connection;
michael@0 78
michael@0 79 let dbfile = FileUtils.getFile(KEY_PROFILEDIR, [FILE_DATABASE], true);
michael@0 80 if (!dbfile.exists())
michael@0 81 return false;
michael@0 82
michael@0 83 try {
michael@0 84 this.connection = Services.storage.openUnsharedDatabase(dbfile);
michael@0 85 } catch (e) {
michael@0 86 return false;
michael@0 87 }
michael@0 88
michael@0 89 this.connection.executeSimpleSQL("PRAGMA locking_mode = EXCLUSIVE");
michael@0 90
michael@0 91 // Any errors in here should rollback
michael@0 92 try {
michael@0 93 this.connection.beginTransaction();
michael@0 94
michael@0 95 switch (this.connection.schemaVersion) {
michael@0 96 case 0:
michael@0 97 return false;
michael@0 98
michael@0 99 case 1:
michael@0 100 logger.debug("Upgrading database schema to version 2");
michael@0 101 this.connection.executeSimpleSQL("ALTER TABLE screenshot ADD COLUMN width INTEGER");
michael@0 102 this.connection.executeSimpleSQL("ALTER TABLE screenshot ADD COLUMN height INTEGER");
michael@0 103 this.connection.executeSimpleSQL("ALTER TABLE screenshot ADD COLUMN thumbnailWidth INTEGER");
michael@0 104 this.connection.executeSimpleSQL("ALTER TABLE screenshot ADD COLUMN thumbnailHeight INTEGER");
michael@0 105 case 2:
michael@0 106 logger.debug("Upgrading database schema to version 3");
michael@0 107 this.connection.createTable("compatibility_override",
michael@0 108 "addon_internal_id INTEGER, " +
michael@0 109 "num INTEGER, " +
michael@0 110 "type TEXT, " +
michael@0 111 "minVersion TEXT, " +
michael@0 112 "maxVersion TEXT, " +
michael@0 113 "appID TEXT, " +
michael@0 114 "appMinVersion TEXT, " +
michael@0 115 "appMaxVersion TEXT, " +
michael@0 116 "PRIMARY KEY (addon_internal_id, num)");
michael@0 117 case 3:
michael@0 118 logger.debug("Upgrading database schema to version 4");
michael@0 119 this.connection.createTable("icon",
michael@0 120 "addon_internal_id INTEGER, " +
michael@0 121 "size INTEGER, " +
michael@0 122 "url TEXT, " +
michael@0 123 "PRIMARY KEY (addon_internal_id, size)");
michael@0 124 this._createIndices();
michael@0 125 this._createTriggers();
michael@0 126 this.connection.schemaVersion = LAST_DB_SCHEMA;
michael@0 127 case LAST_DB_SCHEMA:
michael@0 128 break;
michael@0 129 default:
michael@0 130 return false;
michael@0 131 }
michael@0 132 this.connection.commitTransaction();
michael@0 133 } catch (e) {
michael@0 134 logger.error("Failed to open " + FILE_DATABASE + ". Data import will not happen.", e);
michael@0 135 this.logSQLError(this.connection.lastError, this.connection.lastErrorString);
michael@0 136 this.connection.rollbackTransaction();
michael@0 137 return false;
michael@0 138 }
michael@0 139
michael@0 140 return true;
michael@0 141 },
michael@0 142
michael@0 143 _closeConnection: function() {
michael@0 144 for each (let stmt in this.asyncStatementsCache)
michael@0 145 stmt.finalize();
michael@0 146 this.asyncStatementsCache = {};
michael@0 147
michael@0 148 if (this.connection)
michael@0 149 this.connection.asyncClose();
michael@0 150
michael@0 151 delete this.connection;
michael@0 152 },
michael@0 153
michael@0 154 /**
michael@0 155 * Asynchronously retrieve all add-ons from the database, and pass it
michael@0 156 * to the specified callback
michael@0 157 *
michael@0 158 * @param aCallback
michael@0 159 * The callback to pass the add-ons back to
michael@0 160 */
michael@0 161 _retrieveStoredData: function AD_retrieveStoredData(aCallback) {
michael@0 162 let self = this;
michael@0 163 let addons = {};
michael@0 164
michael@0 165 // Retrieve all data from the addon table
michael@0 166 function getAllAddons() {
michael@0 167 self.getAsyncStatement("getAllAddons").executeAsync({
michael@0 168 handleResult: function getAllAddons_handleResult(aResults) {
michael@0 169 let row = null;
michael@0 170 while ((row = aResults.getNextRow())) {
michael@0 171 let internal_id = row.getResultByName("internal_id");
michael@0 172 addons[internal_id] = self._makeAddonFromAsyncRow(row);
michael@0 173 }
michael@0 174 },
michael@0 175
michael@0 176 handleError: self.asyncErrorLogger,
michael@0 177
michael@0 178 handleCompletion: function getAllAddons_handleCompletion(aReason) {
michael@0 179 if (aReason != Ci.mozIStorageStatementCallback.REASON_FINISHED) {
michael@0 180 logger.error("Error retrieving add-ons from database. Returning empty results");
michael@0 181 aCallback({});
michael@0 182 return;
michael@0 183 }
michael@0 184
michael@0 185 getAllDevelopers();
michael@0 186 }
michael@0 187 });
michael@0 188 }
michael@0 189
michael@0 190 // Retrieve all data from the developer table
michael@0 191 function getAllDevelopers() {
michael@0 192 self.getAsyncStatement("getAllDevelopers").executeAsync({
michael@0 193 handleResult: function getAllDevelopers_handleResult(aResults) {
michael@0 194 let row = null;
michael@0 195 while ((row = aResults.getNextRow())) {
michael@0 196 let addon_internal_id = row.getResultByName("addon_internal_id");
michael@0 197 if (!(addon_internal_id in addons)) {
michael@0 198 logger.warn("Found a developer not linked to an add-on in database");
michael@0 199 continue;
michael@0 200 }
michael@0 201
michael@0 202 let addon = addons[addon_internal_id];
michael@0 203 if (!addon.developers)
michael@0 204 addon.developers = [];
michael@0 205
michael@0 206 addon.developers.push(self._makeDeveloperFromAsyncRow(row));
michael@0 207 }
michael@0 208 },
michael@0 209
michael@0 210 handleError: self.asyncErrorLogger,
michael@0 211
michael@0 212 handleCompletion: function getAllDevelopers_handleCompletion(aReason) {
michael@0 213 if (aReason != Ci.mozIStorageStatementCallback.REASON_FINISHED) {
michael@0 214 logger.error("Error retrieving developers from database. Returning empty results");
michael@0 215 aCallback({});
michael@0 216 return;
michael@0 217 }
michael@0 218
michael@0 219 getAllScreenshots();
michael@0 220 }
michael@0 221 });
michael@0 222 }
michael@0 223
michael@0 224 // Retrieve all data from the screenshot table
michael@0 225 function getAllScreenshots() {
michael@0 226 self.getAsyncStatement("getAllScreenshots").executeAsync({
michael@0 227 handleResult: function getAllScreenshots_handleResult(aResults) {
michael@0 228 let row = null;
michael@0 229 while ((row = aResults.getNextRow())) {
michael@0 230 let addon_internal_id = row.getResultByName("addon_internal_id");
michael@0 231 if (!(addon_internal_id in addons)) {
michael@0 232 logger.warn("Found a screenshot not linked to an add-on in database");
michael@0 233 continue;
michael@0 234 }
michael@0 235
michael@0 236 let addon = addons[addon_internal_id];
michael@0 237 if (!addon.screenshots)
michael@0 238 addon.screenshots = [];
michael@0 239 addon.screenshots.push(self._makeScreenshotFromAsyncRow(row));
michael@0 240 }
michael@0 241 },
michael@0 242
michael@0 243 handleError: self.asyncErrorLogger,
michael@0 244
michael@0 245 handleCompletion: function getAllScreenshots_handleCompletion(aReason) {
michael@0 246 if (aReason != Ci.mozIStorageStatementCallback.REASON_FINISHED) {
michael@0 247 logger.error("Error retrieving screenshots from database. Returning empty results");
michael@0 248 aCallback({});
michael@0 249 return;
michael@0 250 }
michael@0 251
michael@0 252 getAllCompatOverrides();
michael@0 253 }
michael@0 254 });
michael@0 255 }
michael@0 256
michael@0 257 function getAllCompatOverrides() {
michael@0 258 self.getAsyncStatement("getAllCompatOverrides").executeAsync({
michael@0 259 handleResult: function getAllCompatOverrides_handleResult(aResults) {
michael@0 260 let row = null;
michael@0 261 while ((row = aResults.getNextRow())) {
michael@0 262 let addon_internal_id = row.getResultByName("addon_internal_id");
michael@0 263 if (!(addon_internal_id in addons)) {
michael@0 264 logger.warn("Found a compatibility override not linked to an add-on in database");
michael@0 265 continue;
michael@0 266 }
michael@0 267
michael@0 268 let addon = addons[addon_internal_id];
michael@0 269 if (!addon.compatibilityOverrides)
michael@0 270 addon.compatibilityOverrides = [];
michael@0 271 addon.compatibilityOverrides.push(self._makeCompatOverrideFromAsyncRow(row));
michael@0 272 }
michael@0 273 },
michael@0 274
michael@0 275 handleError: self.asyncErrorLogger,
michael@0 276
michael@0 277 handleCompletion: function getAllCompatOverrides_handleCompletion(aReason) {
michael@0 278 if (aReason != Ci.mozIStorageStatementCallback.REASON_FINISHED) {
michael@0 279 logger.error("Error retrieving compatibility overrides from database. Returning empty results");
michael@0 280 aCallback({});
michael@0 281 return;
michael@0 282 }
michael@0 283
michael@0 284 getAllIcons();
michael@0 285 }
michael@0 286 });
michael@0 287 }
michael@0 288
michael@0 289 function getAllIcons() {
michael@0 290 self.getAsyncStatement("getAllIcons").executeAsync({
michael@0 291 handleResult: function getAllIcons_handleResult(aResults) {
michael@0 292 let row = null;
michael@0 293 while ((row = aResults.getNextRow())) {
michael@0 294 let addon_internal_id = row.getResultByName("addon_internal_id");
michael@0 295 if (!(addon_internal_id in addons)) {
michael@0 296 logger.warn("Found an icon not linked to an add-on in database");
michael@0 297 continue;
michael@0 298 }
michael@0 299
michael@0 300 let addon = addons[addon_internal_id];
michael@0 301 let { size, url } = self._makeIconFromAsyncRow(row);
michael@0 302 addon.icons[size] = url;
michael@0 303 if (size == 32)
michael@0 304 addon.iconURL = url;
michael@0 305 }
michael@0 306 },
michael@0 307
michael@0 308 handleError: self.asyncErrorLogger,
michael@0 309
michael@0 310 handleCompletion: function getAllIcons_handleCompletion(aReason) {
michael@0 311 if (aReason != Ci.mozIStorageStatementCallback.REASON_FINISHED) {
michael@0 312 logger.error("Error retrieving icons from database. Returning empty results");
michael@0 313 aCallback({});
michael@0 314 return;
michael@0 315 }
michael@0 316
michael@0 317 let returnedAddons = {};
michael@0 318 for each (let addon in addons)
michael@0 319 returnedAddons[addon.id] = addon;
michael@0 320 aCallback(returnedAddons);
michael@0 321 }
michael@0 322 });
michael@0 323 }
michael@0 324
michael@0 325 // Begin asynchronous process
michael@0 326 getAllAddons();
michael@0 327 },
michael@0 328
michael@0 329 // A cache of statements that are used and need to be finalized on shutdown
michael@0 330 asyncStatementsCache: {},
michael@0 331
michael@0 332 /**
michael@0 333 * Gets a cached async statement or creates a new statement if it doesn't
michael@0 334 * already exist.
michael@0 335 *
michael@0 336 * @param aKey
michael@0 337 * A unique key to reference the statement
michael@0 338 * @return a mozIStorageAsyncStatement for the SQL corresponding to the
michael@0 339 * unique key
michael@0 340 */
michael@0 341 getAsyncStatement: function AD_getAsyncStatement(aKey) {
michael@0 342 if (aKey in this.asyncStatementsCache)
michael@0 343 return this.asyncStatementsCache[aKey];
michael@0 344
michael@0 345 let sql = this.queries[aKey];
michael@0 346 try {
michael@0 347 return this.asyncStatementsCache[aKey] = this.connection.createAsyncStatement(sql);
michael@0 348 } catch (e) {
michael@0 349 logger.error("Error creating statement " + aKey + " (" + sql + ")");
michael@0 350 throw Components.Exception("Error creating statement " + aKey + " (" + sql + "): " + e,
michael@0 351 e.result);
michael@0 352 }
michael@0 353 },
michael@0 354
michael@0 355 // The queries used by the database
michael@0 356 queries: {
michael@0 357 getAllAddons: "SELECT internal_id, id, type, name, version, " +
michael@0 358 "creator, creatorURL, description, fullDescription, " +
michael@0 359 "developerComments, eula, homepageURL, supportURL, " +
michael@0 360 "contributionURL, contributionAmount, averageRating, " +
michael@0 361 "reviewCount, reviewURL, totalDownloads, weeklyDownloads, " +
michael@0 362 "dailyUsers, sourceURI, repositoryStatus, size, updateDate " +
michael@0 363 "FROM addon",
michael@0 364
michael@0 365 getAllDevelopers: "SELECT addon_internal_id, name, url FROM developer " +
michael@0 366 "ORDER BY addon_internal_id, num",
michael@0 367
michael@0 368 getAllScreenshots: "SELECT addon_internal_id, url, width, height, " +
michael@0 369 "thumbnailURL, thumbnailWidth, thumbnailHeight, caption " +
michael@0 370 "FROM screenshot ORDER BY addon_internal_id, num",
michael@0 371
michael@0 372 getAllCompatOverrides: "SELECT addon_internal_id, type, minVersion, " +
michael@0 373 "maxVersion, appID, appMinVersion, appMaxVersion " +
michael@0 374 "FROM compatibility_override " +
michael@0 375 "ORDER BY addon_internal_id, num",
michael@0 376
michael@0 377 getAllIcons: "SELECT addon_internal_id, size, url FROM icon " +
michael@0 378 "ORDER BY addon_internal_id, size",
michael@0 379 },
michael@0 380
michael@0 381 /**
michael@0 382 * Make add-on structure from an asynchronous row.
michael@0 383 *
michael@0 384 * @param aRow
michael@0 385 * The asynchronous row to use
michael@0 386 * @return The created add-on
michael@0 387 */
michael@0 388 _makeAddonFromAsyncRow: function AD__makeAddonFromAsyncRow(aRow) {
michael@0 389 // This is intentionally not an AddonSearchResult object in order
michael@0 390 // to allow AddonDatabase._parseAddon to parse it, same as if it
michael@0 391 // was read from the JSON database.
michael@0 392
michael@0 393 let addon = { icons: {} };
michael@0 394
michael@0 395 for (let prop of PROP_SINGLE) {
michael@0 396 addon[prop] = aRow.getResultByName(prop)
michael@0 397 };
michael@0 398
michael@0 399 return addon;
michael@0 400 },
michael@0 401
michael@0 402 /**
michael@0 403 * Make a developer from an asynchronous row
michael@0 404 *
michael@0 405 * @param aRow
michael@0 406 * The asynchronous row to use
michael@0 407 * @return The created developer
michael@0 408 */
michael@0 409 _makeDeveloperFromAsyncRow: function AD__makeDeveloperFromAsyncRow(aRow) {
michael@0 410 let name = aRow.getResultByName("name");
michael@0 411 let url = aRow.getResultByName("url")
michael@0 412 return new AddonManagerPrivate.AddonAuthor(name, url);
michael@0 413 },
michael@0 414
michael@0 415 /**
michael@0 416 * Make a screenshot from an asynchronous row
michael@0 417 *
michael@0 418 * @param aRow
michael@0 419 * The asynchronous row to use
michael@0 420 * @return The created screenshot
michael@0 421 */
michael@0 422 _makeScreenshotFromAsyncRow: function AD__makeScreenshotFromAsyncRow(aRow) {
michael@0 423 let url = aRow.getResultByName("url");
michael@0 424 let width = aRow.getResultByName("width");
michael@0 425 let height = aRow.getResultByName("height");
michael@0 426 let thumbnailURL = aRow.getResultByName("thumbnailURL");
michael@0 427 let thumbnailWidth = aRow.getResultByName("thumbnailWidth");
michael@0 428 let thumbnailHeight = aRow.getResultByName("thumbnailHeight");
michael@0 429 let caption = aRow.getResultByName("caption");
michael@0 430 return new AddonManagerPrivate.AddonScreenshot(url, width, height, thumbnailURL,
michael@0 431 thumbnailWidth, thumbnailHeight, caption);
michael@0 432 },
michael@0 433
michael@0 434 /**
michael@0 435 * Make a CompatibilityOverride from an asynchronous row
michael@0 436 *
michael@0 437 * @param aRow
michael@0 438 * The asynchronous row to use
michael@0 439 * @return The created CompatibilityOverride
michael@0 440 */
michael@0 441 _makeCompatOverrideFromAsyncRow: function AD_makeCompatOverrideFromAsyncRow(aRow) {
michael@0 442 let type = aRow.getResultByName("type");
michael@0 443 let minVersion = aRow.getResultByName("minVersion");
michael@0 444 let maxVersion = aRow.getResultByName("maxVersion");
michael@0 445 let appID = aRow.getResultByName("appID");
michael@0 446 let appMinVersion = aRow.getResultByName("appMinVersion");
michael@0 447 let appMaxVersion = aRow.getResultByName("appMaxVersion");
michael@0 448 return new AddonManagerPrivate.AddonCompatibilityOverride(type,
michael@0 449 minVersion,
michael@0 450 maxVersion,
michael@0 451 appID,
michael@0 452 appMinVersion,
michael@0 453 appMaxVersion);
michael@0 454 },
michael@0 455
michael@0 456 /**
michael@0 457 * Make an icon from an asynchronous row
michael@0 458 *
michael@0 459 * @param aRow
michael@0 460 * The asynchronous row to use
michael@0 461 * @return An object containing the size and URL of the icon
michael@0 462 */
michael@0 463 _makeIconFromAsyncRow: function AD_makeIconFromAsyncRow(aRow) {
michael@0 464 let size = aRow.getResultByName("size");
michael@0 465 let url = aRow.getResultByName("url");
michael@0 466 return { size: size, url: url };
michael@0 467 },
michael@0 468
michael@0 469 /**
michael@0 470 * A helper function to log an SQL error.
michael@0 471 *
michael@0 472 * @param aError
michael@0 473 * The storage error code associated with the error
michael@0 474 * @param aErrorString
michael@0 475 * An error message
michael@0 476 */
michael@0 477 logSQLError: function AD_logSQLError(aError, aErrorString) {
michael@0 478 logger.error("SQL error " + aError + ": " + aErrorString);
michael@0 479 },
michael@0 480
michael@0 481 /**
michael@0 482 * A helper function to log any errors that occur during async statements.
michael@0 483 *
michael@0 484 * @param aError
michael@0 485 * A mozIStorageError to log
michael@0 486 */
michael@0 487 asyncErrorLogger: function AD_asyncErrorLogger(aError) {
michael@0 488 logger.error("Async SQL error " + aError.result + ": " + aError.message);
michael@0 489 },
michael@0 490
michael@0 491 /**
michael@0 492 * Synchronously creates the triggers in the database.
michael@0 493 */
michael@0 494 _createTriggers: function AD__createTriggers() {
michael@0 495 this.connection.executeSimpleSQL("DROP TRIGGER IF EXISTS delete_addon");
michael@0 496 this.connection.executeSimpleSQL("CREATE TRIGGER delete_addon AFTER DELETE " +
michael@0 497 "ON addon BEGIN " +
michael@0 498 "DELETE FROM developer WHERE addon_internal_id=old.internal_id; " +
michael@0 499 "DELETE FROM screenshot WHERE addon_internal_id=old.internal_id; " +
michael@0 500 "DELETE FROM compatibility_override WHERE addon_internal_id=old.internal_id; " +
michael@0 501 "DELETE FROM icon WHERE addon_internal_id=old.internal_id; " +
michael@0 502 "END");
michael@0 503 },
michael@0 504
michael@0 505 /**
michael@0 506 * Synchronously creates the indices in the database.
michael@0 507 */
michael@0 508 _createIndices: function AD__createIndices() {
michael@0 509 this.connection.executeSimpleSQL("CREATE INDEX IF NOT EXISTS developer_idx " +
michael@0 510 "ON developer (addon_internal_id)");
michael@0 511 this.connection.executeSimpleSQL("CREATE INDEX IF NOT EXISTS screenshot_idx " +
michael@0 512 "ON screenshot (addon_internal_id)");
michael@0 513 this.connection.executeSimpleSQL("CREATE INDEX IF NOT EXISTS compatibility_override_idx " +
michael@0 514 "ON compatibility_override (addon_internal_id)");
michael@0 515 this.connection.executeSimpleSQL("CREATE INDEX IF NOT EXISTS icon_idx " +
michael@0 516 "ON icon (addon_internal_id)");
michael@0 517 }
michael@0 518 }

mercurial