522 lines
16 KiB
JavaScript
522 lines
16 KiB
JavaScript
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS-IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
/**
|
|
* @fileoverview Definition of goog.gears.BaseStore which
|
|
* is a base class for the various database stores. It provides
|
|
* the basic structure for creating, updating and removing the store, as well
|
|
* as versioning. It also provides ways to interconnect stores.
|
|
*
|
|
*/
|
|
|
|
goog.provide('goog.gears.BaseStore');
|
|
goog.provide('goog.gears.BaseStore.SchemaType');
|
|
|
|
goog.require('goog.Disposable');
|
|
|
|
|
|
|
|
/**
|
|
* This class implements the common store functionality
|
|
*
|
|
* @param {goog.gears.Database} database The data base to store the data in.
|
|
* @constructor
|
|
* @extends {goog.Disposable}
|
|
*/
|
|
goog.gears.BaseStore = function(database) {
|
|
goog.Disposable.call(this);
|
|
|
|
/**
|
|
* The underlying database that holds the message store.
|
|
* @private
|
|
* @type {goog.gears.Database}
|
|
*/
|
|
this.database_ = database;
|
|
};
|
|
goog.inherits(goog.gears.BaseStore, goog.Disposable);
|
|
|
|
|
|
/**
|
|
* Schema definition types
|
|
* @enum {number}
|
|
*/
|
|
goog.gears.BaseStore.SchemaType = {
|
|
TABLE: 1,
|
|
VIRTUAL_TABLE: 2,
|
|
INDEX: 3,
|
|
BEFORE_INSERT_TRIGGER: 4,
|
|
AFTER_INSERT_TRIGGER: 5,
|
|
BEFORE_UPDATE_TRIGGER: 6,
|
|
AFTER_UPDATE_TRIGGER: 7,
|
|
BEFORE_DELETE_TRIGGER: 8,
|
|
AFTER_DELETE_TRIGGER: 9
|
|
};
|
|
|
|
|
|
/**
|
|
* The name of the store. Subclasses should override and choose their own
|
|
* name. That name is used for the maintaining the version string
|
|
* @protected
|
|
* @type {string}
|
|
*/
|
|
goog.gears.BaseStore.prototype.name = 'Base';
|
|
|
|
|
|
/**
|
|
* The version number of the database schema. It is used to determine whether
|
|
* the store's portion of the database needs to be updated. Subclassses should
|
|
* override this value.
|
|
* @protected
|
|
* @type {number}
|
|
*/
|
|
goog.gears.BaseStore.prototype.version = 1;
|
|
|
|
|
|
/**
|
|
* The database schema for the store. This is an array of objects, where each
|
|
* object describes a database object (table, index, trigger). Documentation
|
|
* about the object's fields can be found in the #createSchema documentation.
|
|
* This is in the prototype so that it can be overriden by the subclass. This
|
|
* field is read only.
|
|
* @protected
|
|
* @type {Array.<Object>}
|
|
*/
|
|
goog.gears.BaseStore.prototype.schema = [];
|
|
|
|
|
|
/**
|
|
* Gets the underlying database.
|
|
* @return {goog.gears.Database}
|
|
* @protected
|
|
*/
|
|
goog.gears.BaseStore.prototype.getDatabaseInternal = function() {
|
|
return this.database_;
|
|
};
|
|
|
|
|
|
/**
|
|
* Updates the tables for the message store in the case where
|
|
* they are out of date.
|
|
*
|
|
* @protected
|
|
* @param {number} persistedVersion the current version of the tables in the
|
|
* database.
|
|
*/
|
|
goog.gears.BaseStore.prototype.updateStore = function(persistedVersion) {
|
|
// TODO(user): Need to figure out how to handle updates
|
|
// where to store the version number and is it globale or per unit.
|
|
};
|
|
|
|
|
|
/**
|
|
* Preloads any applicable data into the tables.
|
|
*
|
|
* @protected
|
|
*/
|
|
goog.gears.BaseStore.prototype.loadData = function() {
|
|
};
|
|
|
|
|
|
/**
|
|
* Creates in memory cache of data that is stored in the tables.
|
|
*
|
|
* @protected
|
|
*/
|
|
goog.gears.BaseStore.prototype.getCachedData = function() {
|
|
};
|
|
|
|
|
|
/**
|
|
* Informs other stores that this store exists .
|
|
*
|
|
* @protected
|
|
*/
|
|
goog.gears.BaseStore.prototype.informOtherStores = function() {
|
|
};
|
|
|
|
|
|
/**
|
|
* Makes sure that tables needed for the store exist and are up to date.
|
|
*/
|
|
goog.gears.BaseStore.prototype.ensureStoreExists = function() {
|
|
var persistedVersion = this.getStoreVersion();
|
|
|
|
if (persistedVersion) {
|
|
if (persistedVersion != this.version) {
|
|
// update
|
|
this.database_.begin();
|
|
try {
|
|
this.updateStore(persistedVersion);
|
|
this.setStoreVersion_(this.version);
|
|
this.database_.commit();
|
|
} catch (ex) {
|
|
this.database_.rollback(ex);
|
|
throw Error('Could not update the ' + this.name + ' schema ' +
|
|
' from version ' + persistedVersion + ' to ' + this.version +
|
|
': ' + (ex.message || 'unknown exception'));
|
|
}
|
|
}
|
|
} else {
|
|
// create
|
|
this.database_.begin();
|
|
try {
|
|
// This is rarely necessary, but it's possible if we rolled back a
|
|
// release and dropped the schema on version n-1 before installing
|
|
// again on version n.
|
|
this.dropSchema(this.schema);
|
|
|
|
this.createSchema(this.schema);
|
|
|
|
// Ensure that the version info schema exists.
|
|
this.createSchema([{
|
|
type: goog.gears.BaseStore.SchemaType.TABLE,
|
|
name: 'StoreVersionInfo',
|
|
columns: [
|
|
'StoreName TEXT NOT NULL PRIMARY KEY',
|
|
'Version INTEGER NOT NULL'
|
|
]}], true);
|
|
this.loadData();
|
|
this.setStoreVersion_(this.version);
|
|
this.database_.commit();
|
|
} catch (ex) {
|
|
this.database_.rollback(ex);
|
|
throw Error('Could not create the ' + this.name + ' schema' +
|
|
': ' + (ex.message || 'unknown exception'));
|
|
}
|
|
}
|
|
this.getCachedData();
|
|
this.informOtherStores();
|
|
};
|
|
|
|
|
|
/**
|
|
* Removes the tables for the MessageStore
|
|
*/
|
|
goog.gears.BaseStore.prototype.removeStore = function() {
|
|
this.database_.begin();
|
|
try {
|
|
this.removeStoreVersion();
|
|
this.dropSchema(this.schema);
|
|
this.database_.commit();
|
|
} catch (ex) {
|
|
this.database_.rollback(ex);
|
|
throw Error('Could not remove the ' + this.name + ' schema' +
|
|
': ' + (ex.message || 'unknown exception'));
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
* Returns the name of the store.
|
|
*
|
|
* @return {string} The name of the store.
|
|
*/
|
|
goog.gears.BaseStore.prototype.getName = function() {
|
|
return this.name;
|
|
};
|
|
|
|
|
|
/**
|
|
* Returns the version number for the specified store
|
|
*
|
|
* @return {number} The version number of the store. Returns 0 if the
|
|
* store does not exist.
|
|
*/
|
|
goog.gears.BaseStore.prototype.getStoreVersion = function() {
|
|
try {
|
|
return /** @type {number} */ (this.database_.queryValue(
|
|
'SELECT Version FROM StoreVersionInfo WHERE StoreName=?',
|
|
this.name)) || 0;
|
|
} catch (ex) {
|
|
return 0;
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
* Sets the version number for the specified store
|
|
*
|
|
* @param {number} version The version number for the store.
|
|
* @private
|
|
*/
|
|
goog.gears.BaseStore.prototype.setStoreVersion_ = function(version) {
|
|
// TODO(user): Need to determine if we should enforce the fact
|
|
// that store versions are monotonically increasing.
|
|
this.database_.execute(
|
|
'INSERT OR REPLACE INTO StoreVersionInfo ' +
|
|
'(StoreName, Version) VALUES(?,?)',
|
|
this.name,
|
|
version);
|
|
};
|
|
|
|
|
|
/**
|
|
* Removes the version number for the specified store
|
|
*/
|
|
goog.gears.BaseStore.prototype.removeStoreVersion = function() {
|
|
try {
|
|
this.database_.execute(
|
|
'DELETE FROM StoreVersionInfo WHERE StoreName=?',
|
|
this.name);
|
|
} catch (ex) {
|
|
// Ignore error - part of bootstrap process.
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
* Generates an SQLITE CREATE TRIGGER statement from a definition array.
|
|
* @param {string} onStr the type of trigger to create.
|
|
* @param {Object} def a schema statement definition.
|
|
* @param {string} notExistsStr string to be included in the create
|
|
* indicating what to do.
|
|
* @return {string} the statement.
|
|
* @private
|
|
*/
|
|
goog.gears.BaseStore.prototype.getCreateTriggerStatement_ =
|
|
function(onStr, def, notExistsStr) {
|
|
return 'CREATE TRIGGER ' + notExistsStr + def.name + ' ' +
|
|
onStr + ' ON ' + def.tableName +
|
|
(def.when ? (' WHEN ' + def.when) : '') +
|
|
' BEGIN ' + def.actions.join('; ') + '; END';
|
|
};
|
|
|
|
|
|
/**
|
|
* Generates an SQLITE CREATE statement from a definition object.
|
|
* @param {Object} def a schema statement definition.
|
|
* @param {boolean=} opt_ifNotExists true if the table or index should be
|
|
* created only if it does not exist. Otherwise trying to create a table
|
|
* or index that already exists will result in an exception being thrown.
|
|
* @return {string} the statement.
|
|
* @private
|
|
*/
|
|
goog.gears.BaseStore.prototype.getCreateStatement_ =
|
|
function(def, opt_ifNotExists) {
|
|
var notExists = opt_ifNotExists ? 'IF NOT EXISTS ' : '';
|
|
switch (def.type) {
|
|
case goog.gears.BaseStore.SchemaType.TABLE:
|
|
return 'CREATE TABLE ' + notExists + def.name + ' (\n' +
|
|
def.columns.join(',\n ') +
|
|
')';
|
|
case goog.gears.BaseStore.SchemaType.VIRTUAL_TABLE:
|
|
return 'CREATE VIRTUAL TABLE ' + notExists + def.name +
|
|
' USING FTS2 (\n' + def.columns.join(',\n ') + ')';
|
|
case goog.gears.BaseStore.SchemaType.INDEX:
|
|
return 'CREATE' + (def.isUnique ? ' UNIQUE' : '') +
|
|
' INDEX ' + notExists + def.name + ' ON ' +
|
|
def.tableName + ' (\n' + def.columns.join(',\n ') + ')';
|
|
case goog.gears.BaseStore.SchemaType.BEFORE_INSERT_TRIGGER:
|
|
return this.getCreateTriggerStatement_('BEFORE INSERT', def, notExists);
|
|
case goog.gears.BaseStore.SchemaType.AFTER_INSERT_TRIGGER:
|
|
return this.getCreateTriggerStatement_('AFTER INSERT', def, notExists);
|
|
case goog.gears.BaseStore.SchemaType.BEFORE_UPDATE_TRIGGER:
|
|
return this.getCreateTriggerStatement_('BEFORE UPDATE', def, notExists);
|
|
case goog.gears.BaseStore.SchemaType.AFTER_UPDATE_TRIGGER:
|
|
return this.getCreateTriggerStatement_('AFTER UPDATE', def, notExists);
|
|
case goog.gears.BaseStore.SchemaType.BEFORE_DELETE_TRIGGER:
|
|
return this.getCreateTriggerStatement_('BEFORE DELETE', def, notExists);
|
|
case goog.gears.BaseStore.SchemaType.AFTER_DELETE_TRIGGER:
|
|
return this.getCreateTriggerStatement_('AFTER DELETE', def, notExists);
|
|
}
|
|
return '';
|
|
};
|
|
|
|
|
|
/**
|
|
* Generates an SQLITE DROP statement from a definition array.
|
|
* @param {Object} def a schema statement definition.
|
|
* @return {string} the statement.
|
|
* @private
|
|
*/
|
|
goog.gears.BaseStore.prototype.getDropStatement_ = function(def) {
|
|
switch (def.type) {
|
|
case goog.gears.BaseStore.SchemaType.TABLE:
|
|
case goog.gears.BaseStore.SchemaType.VIRTUAL_TABLE:
|
|
return 'DROP TABLE IF EXISTS ' + def.name;
|
|
case goog.gears.BaseStore.SchemaType.INDEX:
|
|
return 'DROP INDEX IF EXISTS ' + def.name;
|
|
case goog.gears.BaseStore.SchemaType.BEFORE_INSERT_TRIGGER:
|
|
case goog.gears.BaseStore.SchemaType.AFTER_INSERT_TRIGGER:
|
|
case goog.gears.BaseStore.SchemaType.BEFORE_UPDATE_TRIGGER:
|
|
case goog.gears.BaseStore.SchemaType.AFTER_UPDATE_TRIGGER:
|
|
case goog.gears.BaseStore.SchemaType.BEFORE_DELETE_TRIGGER:
|
|
case goog.gears.BaseStore.SchemaType.AFTER_DELETE_TRIGGER:
|
|
return 'DROP TRIGGER IF EXISTS ' + def.name;
|
|
}
|
|
return '';
|
|
};
|
|
|
|
|
|
/**
|
|
* Creates tables and indicies in the target database.
|
|
*
|
|
* @param {Array} defs definition arrays. This is an array of objects
|
|
* where each object describes a database object to create and drop.
|
|
* each object contains a 'type' field which of type
|
|
* goog.gears.BaseStore.SchemaType. Each object also contains a
|
|
* 'name' which contains the name of the object to create.
|
|
* A table object contains a 'columns' field which is an array
|
|
* that contains the column definitions for the table.
|
|
* A virtual table object contains c 'columns' field which contains
|
|
* the name of the columns. They are assumed to be of type text.
|
|
* An index object contains a 'tableName' field which is the name
|
|
* of the table that the index is on. It contains an 'isUnique'
|
|
* field which is a boolean indicating whether the index is
|
|
* unqiue or not. It also contains a 'columns' field which is
|
|
* an array that contains the columns names (possibly along with the
|
|
* ordering) that form the index.
|
|
* The trigger objects contain a 'tableName' field indicating the
|
|
* table the trigger is on. The type indicates the type of trigger.
|
|
* The trigger object may include a 'when' field which contains
|
|
* the when clause for the trigger. The trigger object also contains
|
|
* an 'actions' field which is an array of strings containing
|
|
* the actions for this trigger.
|
|
* @param {boolean=} opt_ifNotExists true if the table or index should be
|
|
* created only if it does not exist. Otherwise trying to create a table
|
|
* or index that already exists will result in an exception being thrown.
|
|
*/
|
|
goog.gears.BaseStore.prototype.createSchema = function(defs, opt_ifNotExists) {
|
|
this.database_.begin();
|
|
try {
|
|
for (var i = 0; i < defs.length; ++i) {
|
|
var sql = this.getCreateStatement_(defs[i], opt_ifNotExists);
|
|
this.database_.execute(sql);
|
|
}
|
|
this.database_.commit();
|
|
} catch (ex) {
|
|
this.database_.rollback(ex);
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
* Drops tables and indicies in a target database.
|
|
*
|
|
* @param {Array} defs Definition arrays.
|
|
*/
|
|
goog.gears.BaseStore.prototype.dropSchema = function(defs) {
|
|
this.database_.begin();
|
|
try {
|
|
for (var i = defs.length - 1; i >= 0; --i) {
|
|
this.database_.execute(this.getDropStatement_(defs[i]));
|
|
}
|
|
this.database_.commit();
|
|
} catch (ex) {
|
|
this.database_.rollback(ex);
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
* Creates triggers specified in definitions. Will first attempt
|
|
* to drop the trigger with this name first.
|
|
*
|
|
* @param {Array} defs Definition arrays.
|
|
*/
|
|
goog.gears.BaseStore.prototype.createTriggers = function(defs) {
|
|
this.database_.begin();
|
|
try {
|
|
for (var i = 0; i < defs.length; i++) {
|
|
var def = defs[i];
|
|
switch (def.type) {
|
|
case goog.gears.BaseStore.SchemaType.BEFORE_INSERT_TRIGGER:
|
|
case goog.gears.BaseStore.SchemaType.AFTER_INSERT_TRIGGER:
|
|
case goog.gears.BaseStore.SchemaType.BEFORE_UPDATE_TRIGGER:
|
|
case goog.gears.BaseStore.SchemaType.AFTER_UPDATE_TRIGGER:
|
|
case goog.gears.BaseStore.SchemaType.BEFORE_DELETE_TRIGGER:
|
|
case goog.gears.BaseStore.SchemaType.AFTER_DELETE_TRIGGER:
|
|
this.database_.execute('DROP TRIGGER IF EXISTS ' + def.name);
|
|
this.database_.execute(this.getCreateStatement_(def));
|
|
break;
|
|
}
|
|
}
|
|
this.database_.commit();
|
|
} catch (ex) {
|
|
this.database_.rollback(ex);
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
* Returns true if the table exists in the database
|
|
*
|
|
* @param {string} name The table name.
|
|
* @return {boolean} Whether the table exists in the database.
|
|
*/
|
|
goog.gears.BaseStore.prototype.hasTable = function(name) {
|
|
return this.hasInSchema_('table', name);
|
|
};
|
|
|
|
|
|
/**
|
|
* Returns true if the index exists in the database
|
|
*
|
|
* @param {string} name The index name.
|
|
* @return {boolean} Whether the index exists in the database.
|
|
*/
|
|
goog.gears.BaseStore.prototype.hasIndex = function(name) {
|
|
return this.hasInSchema_('index', name);
|
|
};
|
|
|
|
|
|
/**
|
|
* @param {string} name The name of the trigger.
|
|
* @return {boolean} Whether the schema contains a trigger with the given name.
|
|
*/
|
|
goog.gears.BaseStore.prototype.hasTrigger = function(name) {
|
|
return this.hasInSchema_('trigger', name);
|
|
};
|
|
|
|
|
|
/**
|
|
* Returns true if the database contains the index or table
|
|
*
|
|
* @private
|
|
* @param {string} type The type of object to test for, 'table' or 'index'.
|
|
* @param {string} name The table or index name.
|
|
* @return {boolean} Whether the database contains the index or table.
|
|
*/
|
|
goog.gears.BaseStore.prototype.hasInSchema_ = function(type, name) {
|
|
return this.database_.queryValue('SELECT 1 FROM SQLITE_MASTER ' +
|
|
'WHERE TYPE=? AND NAME=?',
|
|
type,
|
|
name) != null;
|
|
};
|
|
|
|
|
|
/** @override */
|
|
goog.gears.BaseStore.prototype.disposeInternal = function() {
|
|
goog.gears.BaseStore.superClass_.disposeInternal.call(this);
|
|
this.database_ = null;
|
|
};
|
|
|
|
|
|
/**
|
|
* HACK(arv): The JSCompiler check for undefined properties sees that these
|
|
* fields are never set and raises warnings.
|
|
* @type {Array.<Object>}
|
|
* @private
|
|
*/
|
|
goog.gears.schemaDefDummy_ = [
|
|
{
|
|
type: '',
|
|
name: '',
|
|
when: '',
|
|
tableName: '',
|
|
actions: [],
|
|
isUnique: false
|
|
}
|
|
];
|