OLD | NEW |
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 window.indexedDB = window.indexedDB || window.webkitIndexedDB || | 5 window.indexedDB = window.indexedDB || window.webkitIndexedDB || |
6 window.mozIndexedDB || window.msIndexedDB; | 6 window.mozIndexedDB || window.msIndexedDB; |
| 7 window.IDBKeyRange = window.IDBKeyRange || window.webkitIDBKeyRange; |
7 | 8 |
8 var automation = { | 9 var automation = { |
9 results: {} | 10 results: {} |
10 }; | 11 }; |
11 | 12 |
12 automation.setDone = function() { | 13 automation.setDone = function() { |
13 this.setStatus("Test complete."); | 14 this.setStatus("Test complete."); |
14 document.cookie = '__done=1; path=/'; | 15 document.cookie = '__done=1; path=/'; |
15 }; | 16 }; |
16 | 17 |
(...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
52 var baseVersion = 2; // The version with our object stores. | 53 var baseVersion = 2; // The version with our object stores. |
53 var curVersion; | 54 var curVersion; |
54 | 55 |
55 // Valid options fields: | 56 // Valid options fields: |
56 // indexName: the name of an index to create on each object store | 57 // indexName: the name of an index to create on each object store |
57 // indexKeyPath: the key path for that index | 58 // indexKeyPath: the key path for that index |
58 // indexIsUnique: the "unique" option for IDBIndexParameters | 59 // indexIsUnique: the "unique" option for IDBIndexParameters |
59 // indexIsMultiEntry: the "multiEntry" option for IDBIndexParameters | 60 // indexIsMultiEntry: the "multiEntry" option for IDBIndexParameters |
60 // | 61 // |
61 function createDatabase( | 62 function createDatabase( |
62 name, objectStoreNames, handler, errorHandler, options) { | 63 name, objectStoreNames, handler, errorHandler, optionSets) { |
63 var openRequest = indexedDB.open(name, baseVersion); | 64 var openRequest = indexedDB.open(name, baseVersion); |
64 openRequest.onblocked = errorHandler; | 65 openRequest.onblocked = errorHandler; |
65 function createObjectStores(db) { | 66 function createObjectStores(db) { |
66 for (var store in objectStoreNames) { | 67 for (var store in objectStoreNames) { |
67 var name = objectStoreNames[store]; | 68 var name = objectStoreNames[store]; |
68 assert(!db.objectStoreNames.contains(name)); | 69 assert(!db.objectStoreNames.contains(name)); |
69 var os = db.createObjectStore(name); | 70 var os = db.createObjectStore(name); |
70 if (options && options.indexName) { | 71 if (optionSets) { |
71 assert('indexKeyPath' in options); | 72 for (o in optionSets) { |
72 os.createIndex(options.indexName, options.indexKeyPath, | 73 var options = optionSets[o]; |
73 { unique: options.indexIsUnique, | 74 assert(options.indexName); |
74 multiEntry: options.indexIsMultiEntry }); | 75 assert('indexKeyPath' in options); |
| 76 os.createIndex(options.indexName, options.indexKeyPath, |
| 77 { unique: options.indexIsUnique, |
| 78 multiEntry: options.indexIsMultiEntry }); |
| 79 } |
75 } | 80 } |
76 } | 81 } |
77 } | 82 } |
78 openRequest.onupgradeneeded = function(ev) { | 83 openRequest.onupgradeneeded = function(ev) { |
79 // TODO: This is the spec-compliant path, which doesn't yet work in Chrome, | 84 // TODO(ericu): This is the spec-compliant path, which doesn't yet work in |
80 // and isn't yet tested, as this function won't currently be called. | 85 // Chrome, and isn't yet tested, as this function won't currently be called. |
81 assert(openRequest == ev.target); | 86 assert(openRequest == ev.target); |
82 createObjectStores(openRequest.result); | 87 createObjectStores(openRequest.result); |
83 // onsuccess will get called after this exits. | 88 // onsuccess will get called after this exits. |
84 }; | 89 }; |
85 openRequest.onsuccess = function(ev) { | 90 openRequest.onsuccess = function(ev) { |
86 assert(openRequest == ev.target); | 91 assert(openRequest == ev.target); |
87 var db = openRequest.result; | 92 var db = openRequest.result; |
88 db.onerror = function(ev) { | 93 db.onerror = function(ev) { |
89 console.log("db error", arguments, openRequest.webkitErrorMessage); | 94 console.log("db error", arguments, openRequest.webkitErrorMessage); |
90 errorHandler(); | 95 errorHandler(); |
(...skipping 15 matching lines...) Expand all Loading... |
106 } | 111 } |
107 } | 112 } |
108 } | 113 } |
109 | 114 |
110 // You must close all database connections before calling this. | 115 // You must close all database connections before calling this. |
111 function alterObjectStores( | 116 function alterObjectStores( |
112 name, objectStoreNames, func, handler, errorHandler) { | 117 name, objectStoreNames, func, handler, errorHandler) { |
113 var version = curVersion + 1; | 118 var version = curVersion + 1; |
114 var openRequest = indexedDB.open(name, version); | 119 var openRequest = indexedDB.open(name, version); |
115 openRequest.onblocked = errorHandler; | 120 openRequest.onblocked = errorHandler; |
116 // TODO: This won't work in Firefox yet; see above in createDatabase. | 121 // TODO(ericu): This won't work in Firefox yet; see above in createDatabase. |
117 openRequest.onsuccess = function(ev) { | 122 openRequest.onsuccess = function(ev) { |
118 assert(openRequest == ev.target); | 123 assert(openRequest == ev.target); |
119 var db = openRequest.result; | 124 var db = openRequest.result; |
120 db.onerror = function(ev) { | 125 db.onerror = function(ev) { |
121 console.log("error altering db", arguments, | 126 console.log("error altering db", arguments, |
122 openRequest.webkitErrorMessage); | 127 openRequest.webkitErrorMessage); |
123 errorHandler(); | 128 errorHandler(); |
124 } | 129 } |
125 if (db.version != version) { | 130 if (db.version != version) { |
126 var setVersionRequest = db.setVersion(version); | 131 var setVersionRequest = db.setVersion(version); |
(...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
163 function getCompletionFunc(testName, startTime, onTestComplete) { | 168 function getCompletionFunc(testName, startTime, onTestComplete) { |
164 function onDeleted() { | 169 function onDeleted() { |
165 automation.setStatus("Deleted database."); | 170 automation.setStatus("Deleted database."); |
166 onTestComplete(); | 171 onTestComplete(); |
167 } | 172 } |
168 return function() { | 173 return function() { |
169 var duration = Date.now() - startTime; | 174 var duration = Date.now() - startTime; |
170 // Ignore the cleanup time for this test. | 175 // Ignore the cleanup time for this test. |
171 automation.addResult(testName, duration); | 176 automation.addResult(testName, duration); |
172 automation.setStatus("Deleting database."); | 177 automation.setStatus("Deleting database."); |
173 // TODO: Turn on actual deletion; for now it's way too slow. | 178 // TODO(ericu): Turn on actual deletion; for now it's way too slow. |
174 // deleteDatabase(testName, onDeleted); | 179 // deleteDatabase(testName, onDeleted); |
175 onTestComplete(); | 180 onTestComplete(); |
176 } | 181 } |
177 } | 182 } |
178 | 183 |
179 function getDisplayName(args) { | 184 function getDisplayName(args) { |
180 // The last arg is the completion callback the test runner tacks on. | 185 // The last arg is the completion callback the test runner tacks on. |
| 186 // TODO(ericu): Make test errors delete the database automatically. |
181 return getDisplayName.caller.name + "_" + | 187 return getDisplayName.caller.name + "_" + |
182 Array.prototype.slice.call(args, 0, args.length - 1).join("_"); | 188 Array.prototype.slice.call(args, 0, args.length - 1).join("_"); |
183 } | 189 } |
184 | 190 |
| 191 // Pad a string [or object convertible to a string] to a fixed width; use this |
| 192 // to have numeric strings sort properly. |
| 193 function padToWidth(s, width) { |
| 194 s = String(s); |
| 195 assert(s.length <= width); |
| 196 while (s.length < width) { |
| 197 s = "0" + s; |
| 198 } |
| 199 return s; |
| 200 } |
| 201 |
185 function getSimpleKey(i) { | 202 function getSimpleKey(i) { |
186 return "key " + i; | 203 return "key " + padToWidth(i, 10); |
187 } | 204 } |
188 | 205 |
189 function getSimpleValue(i) { | 206 function getSimpleValue(i) { |
190 return "value " + i; | 207 return "value " + padToWidth(i, 10); |
| 208 } |
| 209 |
| 210 function getForwardIndexKey(i) { |
| 211 return i; |
| 212 } |
| 213 |
| 214 function getBackwardIndexKey(i) { |
| 215 return -i; |
| 216 } |
| 217 |
| 218 // This is useful for indexing by keypath; the two names should be ordered in |
| 219 // opposite directions for all i in uint32 range. |
| 220 function getObjectValue(i) { |
| 221 return { |
| 222 firstName: getForwardIndexKey(i), |
| 223 lastName: getBackwardIndexKey(i) |
| 224 }; |
| 225 } |
| 226 |
| 227 function getNFieldName(k) { |
| 228 return "field" + k; |
| 229 } |
| 230 |
| 231 function getNFieldObjectValue(i, n) { |
| 232 assert(Math.floor(n) == n); |
| 233 assert(n > 0); |
| 234 var o = {}; |
| 235 for (; n > 0; --n) { |
| 236 // The value varies per field, each object will tend to be unique, |
| 237 // and thanks to the modulus, indexing on different fields will give you |
| 238 // different ordering for large-enough data sets. |
| 239 o[getNFieldName(n - 1)] = Math.pow(i + 0.5, n + 0.5) % 65536; |
| 240 } |
| 241 return o; |
191 } | 242 } |
192 | 243 |
193 function putLinearValues( | 244 function putLinearValues( |
194 transaction, objectStoreNames, numKeys, getKey, getValue) { | 245 transaction, objectStoreNames, numKeys, getKey, getValue) { |
195 if (!getKey) | 246 if (!getKey) |
196 getKey = getSimpleKey; | 247 getKey = getSimpleKey; |
197 if (!getValue) | 248 if (!getValue) |
198 getValue = getSimpleValue; | 249 getValue = getSimpleValue; |
199 for (var i in objectStoreNames) { | 250 for (var i in objectStoreNames) { |
200 var os = transaction.objectStore(objectStoreNames[i]); | 251 var os = transaction.objectStore(objectStoreNames[i]); |
(...skipping 30 matching lines...) Expand all Loading... |
231 for (var i in objectStoreNames) { | 282 for (var i in objectStoreNames) { |
232 var os = transaction.objectStore(objectStoreNames[i]); | 283 var os = transaction.objectStore(objectStoreNames[i]); |
233 for (var j = 0; j < numPuts; ++j) { | 284 for (var j = 0; j < numPuts; ++j) { |
234 var rand = Math.floor(Math.random() * numKeys); | 285 var rand = Math.floor(Math.random() * numKeys); |
235 var request = os.put(getValue(rand), getKey(rand)); | 286 var request = os.put(getValue(rand), getKey(rand)); |
236 request.onerror = onError; | 287 request.onerror = onError; |
237 } | 288 } |
238 } | 289 } |
239 } | 290 } |
240 | 291 |
| 292 // getKey should be deterministic, as we assume that a cursor that starts at |
| 293 // getKey(X) and runs through getKey(X + K) has exactly K values available. |
| 294 // This is annoying to guarantee generally when using an index, so we avoid both |
| 295 // ends of the key space just in case and use simple indices. |
| 296 // TODO(ericu): Figure out if this can be simplified and we can remove uses of |
| 297 // getObjectValue in favor of getNFieldObjectValue. |
| 298 function getValuesFromCursor( |
| 299 transaction, inputObjectStoreName, numReads, numKeys, indexName, getKey, |
| 300 readKeysOnly, outputObjectStoreName) { |
| 301 assert(2 * numReads < numKeys); |
| 302 if (!getKey) |
| 303 getKey = getSimpleKey; |
| 304 var rand = Math.floor(Math.random() * (numKeys - 2 * numReads)) + numReads; |
| 305 var values = []; |
| 306 var queryObject = transaction.objectStore(inputObjectStoreName); |
| 307 assert(queryObject); |
| 308 if (indexName) |
| 309 queryObject = queryObject.index(indexName); |
| 310 var keyRange = IDBKeyRange.bound( |
| 311 getKey(rand), getKey(rand + numReads), false, true); |
| 312 var request; |
| 313 if (readKeysOnly) { |
| 314 request = queryObject.openKeyCursor(keyRange); |
| 315 } else { |
| 316 request = queryObject.openCursor(keyRange); |
| 317 } |
| 318 var oos; |
| 319 if (outputObjectStoreName) |
| 320 oos = transaction.objectStore(outputObjectStoreName); |
| 321 var numReadsLeft = numReads; |
| 322 request.onsuccess = function(event) { |
| 323 var cursor = event.target.result; |
| 324 if (cursor) { |
| 325 assert(numReadsLeft); |
| 326 --numReadsLeft; |
| 327 if (oos) // Put in random order for maximum difficulty. |
| 328 oos.put(cursor.value, Math.random()); |
| 329 values.push({key: cursor.key, value: cursor.value}); |
| 330 cursor.continue(); |
| 331 } else { |
| 332 assert(!numReadsLeft); |
| 333 } |
| 334 } |
| 335 request.onerror = onError; |
| 336 } |
| 337 |
| 338 |
241 function stringOfLength(n) { | 339 function stringOfLength(n) { |
242 assert(n > 0); | 340 assert(n > 0); |
243 assert(n == Math.floor(n)); | 341 assert(n == Math.floor(n)); |
244 return new Array(n + 1).join('0'); | 342 return new Array(n + 1).join('0'); |
245 } | 343 } |
OLD | NEW |