cordova-sqlcipher-adapter
Version:
SQLCipher database adapter for PhoneGap/Cordova, based on cordova-sqlite-storage
298 lines (262 loc) • 8.38 kB
JavaScript
(function () {
"use strict";
var Statement, Database, ItemDataSource, GroupDataSource;
// Alternative typeof implementation yielding more meaningful results,
// see http://javascriptweblog.wordpress.com/2011/08/08/fixing-the-javascript-typeof-operator/
function type(obj) {
var typeString;
typeString = Object.prototype.toString.call(obj);
return typeString.substring(8, typeString.length - 1).toLowerCase();
}
function throwSQLiteError(message, comException) {
var error = new Error(message);
error.resultCode = comException.number & 0xffff;
throw error;
}
Statement = WinJS.Class.define(function (db, sql, args) {
try {
this.statement = db.connection.prepare(sql);
} catch (comException) {
throwSQLiteError('Error preparing an SQLite statement.', comException);
}
if (args) {
this.bind(args);
}
}, {
bind: function (args) {
var index, resultCode;
args.forEach(function (arg, i) {
index = i + 1;
switch (type(arg)) {
case 'number':
if (arg % 1 === 0) {
resultCode = this.statement.bindInt64(index, arg);
} else {
resultCode = this.statement.bindDouble(index, arg);
}
break;
case 'string':
resultCode = this.statement.bindText(index, arg);
break;
case 'null':
resultCode = this.statement.bindNull(index);
break;
default:
throw new Error("Unsupported argument type: " + type(arg));
}
if (resultCode !== SQLite3.ResultCode.ok) {
throw new Error("Error " + resultCode + " when binding argument to SQL query.");
}
}, this);
},
run: function () {
this.statement.step();
},
one: function () {
this.statement.step();
return this._getRow();
},
all: function () {
var result = [];
this.each(function (row) {
result.push(row);
});
return result;
},
each: function (callback) {
var resultCode = this.statement.step();
while (resultCode === SQLite3.ResultCode.row) {
callback(this._getRow());
resultCode = this.statement.step();
}
if (resultCode !== SQLite3.ResultCode.done && resultCode !== SQLite3.ResultCode.ok) {
throw new Error("SQLite3 step error result code: " + resultCode);
}
},
map: function (callback) {
var result = [];
this.each(function (row) {
result.push(callback(row));
});
return result;
},
close: function () {
this.statement.close();
},
_getRow: function () {
var i, len, name, row = {};
for (i = 0, len = this.statement.columnCount() ; i < len; i += 1) {
name = this.statement.columnName(i);
row[name] = this._getColumn(i);
}
return row;
},
_getColumn: function (index) {
switch (this.statement.columnType(index)) {
case SQLite3.Datatype.integer:
return this.statement.columnInt64(index);
case SQLite3.Datatype.float:
return this.statement.columnDouble(index);
case SQLite3.Datatype.text:
return this.statement.columnText(index);
case SQLite3.Datatype["null"]:
return null;
default:
throw new Error('Unsupported column type in column ' + index);
}
}
});
Database = WinJS.Class.define(function (dbPath) {
try {
this.connection = SQLite3.Database(dbPath);
} catch (comException) {
throwSQLiteError('Error creating an SQLite database connection.', comException);
}
}, {
run: function (sql, args) {
var statement = this.prepare(sql, args);
statement.run();
statement.close();
},
one: function (sql, args) {
var row, statement = this.prepare(sql, args);
row = statement.one();
statement.close();
return row;
},
all: function (sql, args) {
var rows, statement = this.prepare(sql, args);
rows = statement.all();
statement.close();
return rows;
},
each: function (sql, args, callback) {
if (!callback && type(args) === 'function') {
callback = args;
args = null;
}
var statement = this.prepare(sql, args);
statement.each(callback);
statement.close();
},
map: function (sql, args, callback) {
if (!callback && type(args) === 'function') {
callback = args;
args = null;
}
var rows, statement = this.prepare(sql, args);
rows = statement.map(callback);
statement.close();
return rows;
},
prepare: function (sql, args) {
return new Statement(this, sql, args);
},
itemDataSource: function (sql, args, keyColumnName, groupKeyColumnName) {
if (type(args) === 'string') {
groupKeyColumnName = keyColumnName;
keyColumnName = args;
args = undefined;
}
return new ItemDataSource(this, sql, args, keyColumnName, groupKeyColumnName);
},
groupDataSource: function (sql, args, keyColumnName, sizeColumnName) {
if (type(args) === 'string') {
sizeColumnName = keyColumnName;
keyColumnName = args;
args = undefined;
}
return new GroupDataSource(this, sql, args, keyColumnName, sizeColumnName);
},
close: function () {
return this.connection.closedb();
},
close_v2: function () {
return this.connection.close_v2();
},
lastInsertRowid: function () {
return this.connection.lastInsertRowid();
},
totalChanges: function () {
return this.connection.totalChanges();
},
key: function (k) {
return this.connection.key(k);
}
});
ItemDataSource = WinJS.Class.derive(WinJS.UI.VirtualizedDataSource,
function (db, sql, args, keyColumnName, groupKeyColumnName) {
var dataAdapter = {
getCount: function () {
var row = db.one('SELECT COUNT(*) AS cnt FROM (' + sql + ')', args);
return WinJS.Promise.wrap(row.cnt);
},
itemsFromIndex: function (requestIndex, countBefore, countAfter) {
var items,
limit = countBefore + 1 + countAfter,
offset = requestIndex - countBefore;
items = db.map(
'SELECT * FROM (' + sql + ') LIMIT ' + limit + ' OFFSET ' + offset,
function (row) {
var item = {
key: row[keyColumnName].toString(),
data: row
};
if (groupKeyColumnName) {
item.groupKey = row[groupKeyColumnName].toString();
}
return item;
});
return WinJS.Promise.wrap({
items: items,
offset: countBefore,
atEnd: items.length < limit
});
}
};
this._baseDataSourceConstructor(dataAdapter);
}
);
GroupDataSource = WinJS.Class.derive(WinJS.UI.VirtualizedDataSource,
function (db, sql, args, keyColumnName, sizeColumnName) {
var groups,
dataAdapter,
keyIndexMap = {},
groupIndex = 0,
firstItemIndex = 0;
groups = db.map(sql, args, function (row) {
var item = {
key: row[keyColumnName].toString(),
groupSize: row[sizeColumnName],
firstItemIndexHint: firstItemIndex,
data: row
};
keyIndexMap[item.key] = groupIndex;
groupIndex += 1;
firstItemIndex += item.groupSize;
return item;
});
dataAdapter = {
getCount: function () {
return WinJS.Promise.wrap(groups.length);
},
itemsFromIndex: function (requestIndex, countBefore, countAfter) {
return WinJS.Promise.wrap({
items: groups.slice(),
offset: requestIndex,
absoluteIndex: requestIndex,
atStart: true,
atEnd: true
});
},
itemsFromKey: function (key, countBefore, countAfter) {
return this.itemsFromIndex(keyIndexMap[key], countBefore, countAfter);
}
};
this._baseDataSourceConstructor(dataAdapter);
}
);
WinJS.Namespace.define('SQLite3JS', {
Database: Database
});
}());