UNPKG

ydn.db

Version:

Javascript database library for IndexedDB, WebDatabase (WebSQL) and WebStorage (localStorage) storage mechanisms supporting version migration, advanced query and transaction workflow.

602 lines (507 loc) 13.2 kB
// Copyright 2012 YDN 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 Utilities for encoding IDBKey and key comparison algorithm. * * @author kyawtun@yathit.com (Kyaw Tun) */ /** * Portion of this code is obtained from Facebook Inc's IndexedDB-polyfill * project under Apache License 2.0. * * See also in https://github.com/mozilla/releases-mozilla-aurora/blob/master/ * dom/indexedDB/Key.cpp for key encoding algorithm. */ goog.provide('ydn.db.utils'); /** * Grandfathered function to goog.object.getValueByKeys with supporting for * dotted key path. * Example usage: getValueByKeys(jsonObj, 'foo.entries') * * @param {!Object} obj An object to get the value from. Can be array-like. * @param {...(string|number|!Array.<number|string>)} var_args A number of keys * (as strings, or nubmers, for array-like objects). Can also be * specified as a single array of keys. * @return {IDBKey|undefined} The resulting value. If, at any point, the value * for a key is undefined, returns undefined. * @see goog.object.getValueByKeys */ ydn.db.utils.getValueByKeys = function(obj, var_args) { var isArrayLike, keys; if (arguments.length == 2 && goog.isString(arguments[1])) { isArrayLike = true; keys = arguments[1].split('.'); } else { isArrayLike = goog.isArrayLike(var_args); keys = isArrayLike ? var_args : arguments; } // Start with the 2nd parameter for the variable parameters syntax. for (var i = isArrayLike ? 0 : 1; i < keys.length; i++) { obj = obj[keys[i]]; if (!goog.isDef(obj)) { break; } } return /** @type {IDBKey} */ (obj); }; /** * Set object of given key path. * @param {Object} obj * @param {string} key_path doted key path. * @param {*} value value to set. */ ydn.db.utils.setValueByKeys = function(obj, key_path, value) { if (obj) { if (key_path.indexOf('.') == -1) { obj[key_path] = value; return; } var paths = key_path.split('.'); var last_key = paths.pop(); var key; while (key = paths.shift()) { if (!goog.isObject(obj[key])) { goog.asserts.assert(!goog.isDef(obj[key]), 'key "' + key + '" is not on path'); obj[key] = {}; } obj = obj[key]; } obj[last_key] = value; } }; /** * @const * @type {Object} */ ydn.db.utils.ARRAY_TERMINATOR = {}; /** * @const * @type {number} */ ydn.db.utils.BYTE_TERMINATOR = 0; /** * @const * @type {number} */ ydn.db.utils.TYPE_NUMBER = 1; /** * @const * @type {number} */ ydn.db.utils.TYPE_DATE = 2; /** * @const * @type {number} */ ydn.db.utils.TYPE_STRING = 3; /** * @const * @type {number} */ ydn.db.utils.TYPE_ARRAY = 4; /** * @const * @type {number} */ ydn.db.utils.MAX_TYPE_BYTE_SIZE = 12; // NOTE: Cannot be greater than 255 /** * * @param {*} key key to encode. * @return {string} encoded key as string. */ ydn.db.utils.encodeKey = function(key) { var stack = [key], writer = new ydn.db.utils.HexStringWriter(), type = 0, dataType, obj; while ((obj = stack.pop()) !== undefined) { if (type % 4 === 0 && type + ydn.db.utils.TYPE_ARRAY > ydn.db.utils.MAX_TYPE_BYTE_SIZE) { writer.write(type); type = 0; } dataType = typeof obj; if (obj instanceof Array) { type += ydn.db.utils.TYPE_ARRAY; if (obj.length > 0) { stack.push(ydn.db.utils.ARRAY_TERMINATOR); var i = obj.length; while (i--) stack.push(obj[i]); continue; } else { writer.write(type); } } else if (dataType === 'number') { type += ydn.db.utils.TYPE_NUMBER; writer.write(type); ydn.db.utils.encodeNumber(writer, obj); } else if (obj instanceof Date) { type += ydn.db.utils.TYPE_DATE; writer.write(type); ydn.db.utils.encodeNumber(writer, obj.valueOf()); } else if (dataType === 'string') { type += ydn.db.utils.TYPE_STRING; writer.write(type); ydn.db.utils.encodeString(writer, obj); } else if (obj === ydn.db.utils.ARRAY_TERMINATOR) { writer.write(ydn.db.utils.BYTE_TERMINATOR); } else return ''; // null; type = 0; } return writer.trim().toString(); }; /** * * @param {string} encodedKey key to decoded. * @return {IDBKey} decoded key. */ ydn.db.utils.decodeKey = function(encodedKey) { var rootArray = []; // one-element root array that contains the result var parentArray = rootArray; var type, arrayStack = [], depth, tmp; var reader = new ydn.db.utils.HexStringReader(encodedKey); while (reader.read() != null) { if (reader.current === 0) // end of array { parentArray = arrayStack.pop(); continue; } if (reader.current === null) { return rootArray[0]; } do { depth = reader.current / 4 | 0; type = reader.current % 4; for (var i = 0; i < depth; i++) { tmp = []; parentArray.push(tmp); arrayStack.push(parentArray); parentArray = tmp; } if (type === 0 && reader.current + ydn.db.utils.TYPE_ARRAY > ydn.db.utils.MAX_TYPE_BYTE_SIZE) { reader.read(); } else break; } while (true); if (type === ydn.db.utils.TYPE_NUMBER) { parentArray.push(ydn.db.utils.decodeNumber(reader)); } else if (type === ydn.db.utils.TYPE_DATE) { parentArray.push(new Date(ydn.db.utils.decodeNumber(reader))); } else if (type === ydn.db.utils.TYPE_STRING) { parentArray.push(ydn.db.utils.decodeString(reader)); // add new } else if (type === 0) // empty array case { parentArray = arrayStack.pop(); } } return rootArray[0]; }; // Utils /** * @const * @type {number} */ ydn.db.utils.p16 = 0x10000; /** * @const * @type {number} */ ydn.db.utils.p32 = 0x100000000; /** * @const * @type {number} */ ydn.db.utils.p48 = 0x1000000000000; /** * @const * @type {number} */ ydn.db.utils.p52 = 0x10000000000000; /** * @const * @type {number} */ ydn.db.utils.pNeg1074 = 5e-324; // 2^-1074); /** * @const * @type {number} */ ydn.db.utils.pNeg1022 = 2.2250738585072014e-308; // 2^-1022 /** * * @param {number} number * @return {Object} IEEE754 number. */ ydn.db.utils.ieee754 = function(number) { var s = 0, e = 0, m = 0; if (number !== 0) { if (isFinite(number)) { if (number < 0) { s = 1; number = -number; } var p = 0; if (number >= ydn.db.utils.pNeg1022) { var n = number; while (n < 1) { p--; n *= 2; } while (n >= 2) { p++; n /= 2; } e = p + 1023; } m = e ? Math.floor((number / Math.pow(2, p) - 1) * ydn.db.utils.p52) : Math.floor(number / ydn.db.utils.pNeg1074); } else { e = 0x7FF; if (isNaN(number)) { m = 2251799813685248; // QNan } else { if (number === -Infinity) s = 1; } } } return {sign: s, exponent: e, mantissa: m}; }; /** * @protected * @param {ydn.db.utils.HexStringWriter} writer * @param {number} number */ ydn.db.utils.encodeNumber = function(writer, number) { var iee_number = ydn.db.utils.ieee754(number); if (iee_number.sign) { iee_number.mantissa = ydn.db.utils.p52 - 1 - iee_number.mantissa; iee_number.exponent = 0x7FF - iee_number.exponent; } var word, m = iee_number.mantissa; writer.write((iee_number.sign ? 0 : 0x80) | (iee_number.exponent >> 4)); writer.write((iee_number.exponent & 0xF) << 4 | (0 | m / ydn.db.utils.p48)); m %= ydn.db.utils.p48; word = 0 | m / ydn.db.utils.p32; writer.write(word >> 8, word & 0xFF); m %= ydn.db.utils.p32; word = 0 | m / ydn.db.utils.p16; writer.write(word >> 8, word & 0xFF); word = m % ydn.db.utils.p16; writer.write(word >> 8, word & 0xFF); }; /** * @private * @param {ydn.db.utils.HexStringReader} reader * @return {*} */ ydn.db.utils.decodeNumber = function(reader) { var b = reader.read() | 0; var sign = b >> 7 ? false : true; var s = sign ? -1 : 1; var e = (b & 0x7F) << 4; b = reader.read() | 0; e += b >> 4; if (sign) e = 0x7FF - e; var tmp = [sign ? (0xF - (b & 0xF)) : b & 0xF]; var i = 6; while (i--) tmp.push(sign ? (0xFF - (reader.read() | 0)) : reader.read() | 0); var m = 0; i = 7; while (i--) m = m / 256 + tmp[i]; m /= 16; if (m === 0 && e === 0) return 0; return (m + 1) * Math.pow(2, e - 1023) * s; }; /** * @const * @type {number} */ ydn.db.utils.secondLayer = 0x3FFF + 0x7F; /** * @protected * @param {ydn.db.utils.HexStringWriter} writer * @param {string} string */ ydn.db.utils.encodeString = function(writer, string) { /* 3 layers: Chars 0 - 7E are encoded as 0xxxxxxx with 1 added Chars 7F - (3FFF+7F) are encoded as 10xxxxxx xxxxxxxx with 7F subtracted Chars (3FFF+80) - FFFF are encoded as 11xxxxxx xxxxxxxx xx000000 */ for (var i = 0; i < string.length; i++) { var code = string.charCodeAt(i); if (code <= 0x7E) { writer.write(code + 1); } else if (code <= ydn.db.utils.secondLayer) { code -= 0x7F; writer.write(0x80 | code >> 8, code & 0xFF); } else { writer.write(0xC0 | code >> 10, code >> 2 | 0xFF, (code | 3) << 6); } } writer.write(ydn.db.utils.BYTE_TERMINATOR); }; /** * @protected * @param {ydn.db.utils.HexStringReader} reader * @return {string} */ ydn.db.utils.decodeString = function(reader) { var buffer = [], layer = 0, unicode = 0, count = 0, $byte, tmp; while (true) { $byte = reader.read(); if ($byte === 0 || $byte == null) break; if (layer === 0) { tmp = $byte >> 6; if (tmp < 2 && !isNaN($byte)) { // kyaw: add !isNaN($byte) buffer.push(String.fromCharCode($byte - 1)); } else // tmp equals 2 or 3 { layer = tmp; unicode = $byte << 10; count++; } } else if (layer === 2) { buffer.push(String.fromCharCode(unicode + $byte + 0x7F)); layer = unicode = count = 0; } else // layer === 3 { if (count === 2) { unicode += $byte << 2; count++; } else // count === 3 { buffer.push(String.fromCharCode(unicode | $byte >> 6)); layer = unicode = count = 0; } } } return buffer.join(''); }; /** * @protected * @param {string} string * @constructor */ ydn.db.utils.HexStringReader = function(string) { this.current = null; this.string = string; this.lastIndex = this.string.length - 1; this.index = -1; }; /** * @type {?number} */ ydn.db.utils.HexStringReader.prototype.current; /** * @type {number} * @private */ ydn.db.utils.HexStringReader.prototype.index; /** * @type {number} * @private */ ydn.db.utils.HexStringReader.prototype.lastIndex; /** * @type {string} * @private */ ydn.db.utils.HexStringReader.prototype.string; /** * * @return {?number} */ ydn.db.utils.HexStringReader.prototype.read = function() { return this.current = this.index < this.lastIndex ? parseInt( this.string.charAt(++this.index) + this.string.charAt(++this.index), 16) : null; }; /** * @protected * @constructor */ ydn.db.utils.HexStringWriter = function() { this.buffer = []; this.c = undefined; }; /** * @type {string|undefined} * @private */ ydn.db.utils.HexStringWriter.prototype.c; /** * @type {Array} * @private */ ydn.db.utils.HexStringWriter.prototype.buffer; /** * * @param {...number} var_args */ ydn.db.utils.HexStringWriter.prototype.write = function(var_args) { for (var i = 0; i < arguments.length; i++) { this.c = arguments[i].toString(16); this.buffer.push(this.c.length === 2 ? this.c : this.c = '0' + this.c); } }; /** * * @return {ydn.db.utils.HexStringWriter} */ ydn.db.utils.HexStringWriter.prototype.trim = function() { var length = this.buffer.length; while (this.buffer[--length] === '00') { } this.buffer.length = ++length; return this; }; /** * @return {string} */ ydn.db.utils.HexStringWriter.prototype.toString = function() { return this.buffer.length ? this.buffer.join('') : ''; }; /** * * @param {*} first * @param {*} second * @return {number} returns 1 if the first key is * greater than the second, -1 if the first is less than the second, and 0 if * the first is equal to the second. */ ydn.db.utils.cmp = function(first, second) { var key1 = ydn.db.utils.encodeKey(first); var key2 = ydn.db.utils.encodeKey(second); return key1 > key2 ? 1 : (key1 == key2 ? 0 : -1); };