objectstore.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389
  1. // Copyright 2011 The Closure Library Authors. All Rights Reserved.
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS-IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. /**
  15. * @fileoverview Wrapper for an IndexedDB object store.
  16. *
  17. */
  18. goog.provide('goog.db.ObjectStore');
  19. goog.require('goog.async.Deferred');
  20. goog.require('goog.db.Cursor');
  21. goog.require('goog.db.Error');
  22. goog.require('goog.db.Index');
  23. goog.require('goog.db.KeyRange');
  24. goog.require('goog.debug');
  25. goog.require('goog.events');
  26. /**
  27. * Creates an IDBObjectStore wrapper object. Object stores have methods for
  28. * storing and retrieving records, and are accessed through a transaction
  29. * object. They also have methods for creating indexes associated with the
  30. * object store. They can only be created when setting the version of the
  31. * database. Should not be created directly, access object stores through
  32. * transactions.
  33. * @see goog.db.UpgradeNeededCallback
  34. * @see goog.db.Transaction#objectStore
  35. *
  36. * @param {!IDBObjectStore} store The backing IndexedDb object.
  37. * @constructor
  38. * @final
  39. *
  40. * TODO(arthurhsu): revisit msg in exception and errors in this class. In newer
  41. * Chrome (v22+) the error/request come with a DOM error string that is
  42. * already very descriptive.
  43. */
  44. goog.db.ObjectStore = function(store) {
  45. /**
  46. * Underlying IndexedDB object store object.
  47. *
  48. * @type {!IDBObjectStore}
  49. * @private
  50. */
  51. this.store_ = store;
  52. };
  53. /**
  54. * @return {string} The name of the object store.
  55. */
  56. goog.db.ObjectStore.prototype.getName = function() {
  57. return this.store_.name;
  58. };
  59. /**
  60. * Helper function for put and add.
  61. *
  62. * @param {string} fn Function name to call on the object store.
  63. * @param {string} msg Message to give to the error.
  64. * @param {*} value Value to insert into the object store.
  65. * @param {IDBKeyType=} opt_key The key to use.
  66. * @return {!goog.async.Deferred} The resulting deferred request.
  67. * @private
  68. */
  69. goog.db.ObjectStore.prototype.insert_ = function(fn, msg, value, opt_key) {
  70. // TODO(user): refactor wrapping an IndexedDB request in a Deferred by
  71. // creating a higher-level abstraction for it (mostly affects here and
  72. // goog.db.Index)
  73. var d = new goog.async.Deferred();
  74. var request;
  75. try {
  76. // put or add with (value, undefined) throws an error, so we need to check
  77. // for undefined ourselves
  78. if (opt_key) {
  79. request = this.store_[fn](value, opt_key);
  80. } else {
  81. request = this.store_[fn](value);
  82. }
  83. } catch (ex) {
  84. msg += goog.debug.deepExpose(value);
  85. if (opt_key) {
  86. msg += ', with key ' + goog.debug.deepExpose(opt_key);
  87. }
  88. d.errback(goog.db.Error.fromException(ex, msg));
  89. return d;
  90. }
  91. request.onsuccess = function(ev) {
  92. d.callback(ev.target.result);
  93. };
  94. request.onerror = function(ev) {
  95. msg += goog.debug.deepExpose(value);
  96. if (opt_key) {
  97. msg += ', with key ' + goog.debug.deepExpose(opt_key);
  98. }
  99. d.errback(goog.db.Error.fromRequest(ev.target, msg));
  100. };
  101. return d;
  102. };
  103. /**
  104. * Adds an object to the object store. Replaces existing objects with the
  105. * same key.
  106. *
  107. * @param {*} value The value to put.
  108. * @param {IDBKeyType=} opt_key The key to use. Cannot be used if the
  109. * keyPath was specified for the object store. If the keyPath was not
  110. * specified but autoIncrement was not enabled, it must be used.
  111. * @return {!goog.async.Deferred} The deferred put request.
  112. */
  113. goog.db.ObjectStore.prototype.put = function(value, opt_key) {
  114. return this.insert_(
  115. 'put', 'putting into ' + this.getName() + ' with value', value, opt_key);
  116. };
  117. /**
  118. * Adds an object to the object store. Requires that there is no object with
  119. * the same key already present.
  120. *
  121. * @param {*} value The value to add.
  122. * @param {IDBKeyType=} opt_key The key to use. Cannot be used if the
  123. * keyPath was specified for the object store. If the keyPath was not
  124. * specified but autoIncrement was not enabled, it must be used.
  125. * @return {!goog.async.Deferred} The deferred add request.
  126. */
  127. goog.db.ObjectStore.prototype.add = function(value, opt_key) {
  128. return this.insert_(
  129. 'add', 'adding into ' + this.getName() + ' with value ', value, opt_key);
  130. };
  131. /**
  132. * Removes an object from the store. No-op if there is no object present with
  133. * the given key.
  134. *
  135. * @param {IDBKeyType|!goog.db.KeyRange} keyOrRange The key or range to remove
  136. * objects under.
  137. * @return {!goog.async.Deferred} The deferred remove request.
  138. */
  139. goog.db.ObjectStore.prototype.remove = function(keyOrRange) {
  140. var d = new goog.async.Deferred();
  141. var request;
  142. try {
  143. request = this.store_['delete'](
  144. keyOrRange instanceof goog.db.KeyRange ? keyOrRange.range() :
  145. keyOrRange);
  146. } catch (err) {
  147. var msg = 'removing from ' + this.getName() + ' with key ' +
  148. goog.debug.deepExpose(keyOrRange);
  149. d.errback(goog.db.Error.fromException(err, msg));
  150. return d;
  151. }
  152. request.onsuccess = function(ev) { d.callback(); };
  153. var self = this;
  154. request.onerror = function(ev) {
  155. var msg = 'removing from ' + self.getName() + ' with key ' +
  156. goog.debug.deepExpose(keyOrRange);
  157. d.errback(goog.db.Error.fromRequest(ev.target, msg));
  158. };
  159. return d;
  160. };
  161. /**
  162. * Gets an object from the store. If no object is present with that key
  163. * the result is {@code undefined}.
  164. *
  165. * @param {IDBKeyType} key The key to look up.
  166. * @return {!goog.async.Deferred} The deferred get request.
  167. */
  168. goog.db.ObjectStore.prototype.get = function(key) {
  169. var d = new goog.async.Deferred();
  170. var request;
  171. try {
  172. request = this.store_.get(key);
  173. } catch (err) {
  174. var msg = 'getting from ' + this.getName() + ' with key ' +
  175. goog.debug.deepExpose(key);
  176. d.errback(goog.db.Error.fromException(err, msg));
  177. return d;
  178. }
  179. request.onsuccess = function(ev) { d.callback(ev.target.result); };
  180. var self = this;
  181. request.onerror = function(ev) {
  182. var msg = 'getting from ' + self.getName() + ' with key ' +
  183. goog.debug.deepExpose(key);
  184. d.errback(goog.db.Error.fromRequest(ev.target, msg));
  185. };
  186. return d;
  187. };
  188. /**
  189. * Gets all objects from the store and returns them as an array.
  190. *
  191. * @param {!goog.db.KeyRange=} opt_range The key range. If undefined iterates
  192. * over the whole object store.
  193. * @param {!goog.db.Cursor.Direction=} opt_direction The direction. If undefined
  194. * moves in a forward direction with duplicates.
  195. * @return {!goog.async.Deferred} The deferred getAll request.
  196. */
  197. goog.db.ObjectStore.prototype.getAll = function(opt_range, opt_direction) {
  198. var d = new goog.async.Deferred();
  199. var cursor;
  200. try {
  201. cursor = this.openCursor(opt_range, opt_direction);
  202. } catch (err) {
  203. d.errback(err);
  204. return d;
  205. }
  206. var result = [];
  207. goog.events.listen(cursor, goog.db.Cursor.EventType.NEW_DATA, function() {
  208. result.push(cursor.getValue());
  209. cursor.next();
  210. });
  211. goog.events.listenOnce(
  212. cursor,
  213. [goog.db.Cursor.EventType.ERROR, goog.db.Cursor.EventType.COMPLETE],
  214. function(evt) {
  215. cursor.dispose();
  216. if (evt.type == goog.db.Cursor.EventType.COMPLETE) {
  217. d.callback(result);
  218. } else {
  219. d.errback();
  220. }
  221. });
  222. return d;
  223. };
  224. /**
  225. * Opens a cursor over the specified key range. Returns a cursor object which is
  226. * able to iterate over the given range.
  227. *
  228. * Example usage:
  229. *
  230. * <code>
  231. * var cursor = objectStore.openCursor(goog.db.Range.bound('a', 'c'));
  232. *
  233. * var key = goog.events.listen(
  234. * cursor, goog.db.Cursor.EventType.NEW_DATA, function() {
  235. * // Do something with data.
  236. * cursor.next();
  237. * });
  238. *
  239. * goog.events.listenOnce(
  240. * cursor, goog.db.Cursor.EventType.COMPLETE, function() {
  241. * // Clean up listener, and perform a finishing operation on the data.
  242. * goog.events.unlistenByKey(key);
  243. * });
  244. * </code>
  245. *
  246. * @param {!goog.db.KeyRange=} opt_range The key range. If undefined iterates
  247. * over the whole object store.
  248. * @param {!goog.db.Cursor.Direction=} opt_direction The direction. If undefined
  249. * moves in a forward direction with duplicates.
  250. * @return {!goog.db.Cursor} The cursor.
  251. * @throws {goog.db.Error} If there was a problem opening the cursor.
  252. */
  253. goog.db.ObjectStore.prototype.openCursor = function(opt_range, opt_direction) {
  254. return goog.db.Cursor.openCursor(this.store_, opt_range, opt_direction);
  255. };
  256. /**
  257. * Deletes all objects from the store.
  258. *
  259. * @return {!goog.async.Deferred} The deferred clear request.
  260. */
  261. goog.db.ObjectStore.prototype.clear = function() {
  262. var msg = 'clearing store ' + this.getName();
  263. var d = new goog.async.Deferred();
  264. var request;
  265. try {
  266. request = this.store_.clear();
  267. } catch (err) {
  268. d.errback(goog.db.Error.fromException(err, msg));
  269. return d;
  270. }
  271. request.onsuccess = function(ev) { d.callback(); };
  272. request.onerror = function(ev) {
  273. d.errback(goog.db.Error.fromRequest(ev.target, msg));
  274. };
  275. return d;
  276. };
  277. /**
  278. * Creates an index in this object store. Can only be called inside a
  279. * {@link goog.db.UpgradeNeededCallback}.
  280. *
  281. * @param {string} name Name of the index to create.
  282. * @param {string|!Array<string>} keyPath Attribute or array of attributes to
  283. * index on.
  284. * @param {!Object=} opt_parameters Optional parameters object. The only
  285. * available option is unique, which defaults to false. If unique is true,
  286. * the index will enforce that there is only ever one object in the object
  287. * store for each unique value it indexes on.
  288. * @return {!goog.db.Index} The newly created, wrapped index.
  289. * @throws {goog.db.Error} In case of an error creating the index.
  290. */
  291. goog.db.ObjectStore.prototype.createIndex = function(
  292. name, keyPath, opt_parameters) {
  293. try {
  294. return new goog.db.Index(
  295. this.store_.createIndex(name, keyPath, opt_parameters));
  296. } catch (ex) {
  297. var msg = 'creating new index ' + name + ' with key path ' + keyPath;
  298. throw goog.db.Error.fromException(ex, msg);
  299. }
  300. };
  301. /**
  302. * Gets an index.
  303. *
  304. * @param {string} name Name of the index to fetch.
  305. * @return {!goog.db.Index} The requested wrapped index.
  306. * @throws {goog.db.Error} In case of an error getting the index.
  307. */
  308. goog.db.ObjectStore.prototype.getIndex = function(name) {
  309. try {
  310. return new goog.db.Index(this.store_.index(name));
  311. } catch (ex) {
  312. var msg = 'getting index ' + name;
  313. throw goog.db.Error.fromException(ex, msg);
  314. }
  315. };
  316. /**
  317. * Deletes an index from the object store. Can only be called inside a
  318. * {@link goog.db.UpgradeNeededCallback}.
  319. *
  320. * @param {string} name Name of the index to delete.
  321. * @throws {goog.db.Error} In case of an error deleting the index.
  322. */
  323. goog.db.ObjectStore.prototype.deleteIndex = function(name) {
  324. try {
  325. this.store_.deleteIndex(name);
  326. } catch (ex) {
  327. var msg = 'deleting index ' + name;
  328. throw goog.db.Error.fromException(ex, msg);
  329. }
  330. };
  331. /**
  332. * Gets number of records within a key range.
  333. *
  334. * @param {!goog.db.KeyRange=} opt_range The key range. If undefined, this will
  335. * count all records in the object store.
  336. * @return {!goog.async.Deferred} The deferred number of records.
  337. */
  338. goog.db.ObjectStore.prototype.count = function(opt_range) {
  339. var d = new goog.async.Deferred();
  340. try {
  341. var range = opt_range ? opt_range.range() : null;
  342. var request = this.store_.count(range);
  343. request.onsuccess = function(ev) { d.callback(ev.target.result); };
  344. var self = this;
  345. request.onerror = function(ev) {
  346. d.errback(goog.db.Error.fromRequest(ev.target, self.getName()));
  347. };
  348. } catch (ex) {
  349. d.errback(goog.db.Error.fromException(ex, this.getName()));
  350. }
  351. return d;
  352. };