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