chromium/content/test/data/indexeddb/cursor_prefetch.js

// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// These constants should match the ones in
// third_party/blink/renderer/modules/indexeddb/idb_cursor.h to make sure the
// test hits the right code paths.
var kPrefetchThreshold = 2;
var kMinPrefetchAmount = 5;

var kNumberOfItems = 200;

function test() {
  indexedDBTest(setVersionSuccess, fillObjectStore);
}

function setVersionSuccess() {
  debug("setVersionSuccess():");
  window.db = event.target.result;
  window.trans = event.target.transaction;
  shouldBeTrue("trans !== null");
  var store = db.createObjectStore('store');
  store.createIndex('index', '');
}

function fillObjectStore() {
  debug("fillObjectStore()");
  var trans = db.transaction(['store'], 'readwrite');
  trans.onabort = unexpectedAbortCallback;
  trans.oncomplete = firstTest;

  var store = trans.objectStore('store');
  debug("Storing " + kNumberOfItems + " object in the object store.");
  for (var i = 0; i < kNumberOfItems; ++i) {
    var req = store.put(i, i);
    req.onerror = unexpectedErrorCallback;
  }

  // Let the transaction finish.
}

function firstTest() {
  debug("firstTest()");

  // Test iterating straight through the object store.

  var trans = db.transaction(['store'], 'readwrite');
  trans.onabort = unexpectedAbortCallback;
  trans.oncomplete = secondTest;

  var store = trans.objectStore('store');
  var cursorReq = store.openCursor();
  cursorReq.onerror = unexpectedErrorCallback;

  count = 0;
  cursorReq.onsuccess = function() {
    cursor = event.target.result;
    if (cursor === null) {
      shouldBe("count", "kNumberOfItems");
      return; // Let the transaction finish.
    }

    if (cursor.key !== count)
      shouldBe("cursor.key", "count");
    if (cursor.value !== count)
      shouldBe("cursor.value", "count");

    ++count;

    cursor.continue();
  };
}

function secondTest() {
  debug("secondTest()");

  // Test iterating through the object store, intermixed with
  // continue calls to specific keys.

  var trans = db.transaction(['store'], 'readwrite');
  trans.onabort = unexpectedAbortCallback;
  trans.oncomplete = thirdTest;

  var store = trans.objectStore('store');
  var cursorReq = store.openCursor();
  cursorReq.onerror = unexpectedErrorCallback;

  var jumpTable = [{from: 5,   to: 17},
                   {from: 25,  to: 30},
                   {from: 31,  to: 35},
                   {from: 70,  to: 80},
                   {from: 98,  to: 99}];

  count = 0;
  expectedKey = 0;

  cursorReq.onsuccess = function() {
    cursor = event.target.result;
    if (cursor === null) {
      debug("Finished iterating after " + count + " steps.");
      return; // Let the transaction finish.
    }

    if (cursor.key !== expectedKey)
      shouldBe("cursor.key", "expectedKey");
    if (cursor.value !== expectedKey)
      shouldBe("cursor.value", "expectedKey");

    ++count;

    for (var i = 0; i < jumpTable.length; ++i) {
      if (jumpTable[i].from === cursor.key) {
        expectedKey = jumpTable[i].to;
        debug("Jumping from "+ cursor.key + " to " + expectedKey);
        cursor.continue(expectedKey);
        return;
      }
    }

    ++expectedKey;
    cursor.continue();
  };
}

function thirdTest() {
  debug("thirdTest()");

  // Test iterating straight through the object store in reverse.

  var trans = db.transaction(['store'], 'readwrite');
  trans.onabort = unexpectedAbortCallback;
  trans.oncomplete = fourthTest;

  var store = trans.objectStore('store');
  var cursorReq = store.openCursor(
      IDBKeyRange.upperBound(kNumberOfItems-1), 'prev');
  cursorReq.onerror = unexpectedErrorCallback;

  count = 0;
  cursorReq.onsuccess = function() {
    cursor = event.target.result;
    if (cursor === null) {
      shouldBe("count", "kNumberOfItems");
      return; // Let the transaction finish.
    }

    expectedKey = kNumberOfItems - count - 1;

    if (cursor.key !== expectedKey)
      shouldBe("cursor.key", "expectedKey");
    if (cursor.value !== expectedKey)
      shouldBe("cursor.value", "expectedKey");

    ++count;

    cursor.continue();
  };
}

function fourthTest() {
  debug("fourthTest()");

  // Test iterating, and then stopping before reaching the end.
  // Make sure transaction terminates anyway.

  var trans = db.transaction(['store'], 'readwrite');
  trans.onabort = unexpectedAbortCallback;
  trans.oncomplete = function() {
    debug("fourthTest() transaction completed");
    fifthTest();
  };

  var store = trans.objectStore('store');
  var cursorReq = store.openCursor();
  cursorReq.onerror = unexpectedErrorCallback;

  count = 0;
  cursorReq.onsuccess = function() {
    cursor = event.target.result;

    if (cursor.key !== count)
      shouldBe("cursor.key", "count");
    if (cursor.value !== count)
      shouldBe("cursor.value", "count");

    ++count;

    if (count === 25) {
      // Schedule some other request.
      var otherReq = store.get(42);
      otherReq.onerror = unexpectedErrorCallback;
      otherReq.onsuccess = function() {
        if (count === 25) {
          debug("Other request fired before continue, as expected.");
        } else {
          debug("Other request fired out-of-order!");
          fail();
        }
      };

      cursor.continue();
      return;
    }

    if (count === 30) {
      // Do a continue first, then another request.
      cursor.continue();

      var otherReq = store.get(42);
      otherReq.onerror = unexpectedErrorCallback;
      otherReq.onsuccess = function() {
        if (count === 31) {
          debug("Other request fired right after continue as expected.");
        } else {
          debug("Other request didn't fire right after continue as expected.");
          fail();
        }
      };

      return;
    }

    if (count === 75) {
      return;  // Sudden stop.
    }

    cursor.continue();
  };
}

function fifthTest() {
  debug("fifthTest()");

  // Test iterating over the pre-fetch threshold, but make sure the
  // cursor is positioned so that it is actually at the last element
  // in the range when pre-fetch fires, and make sure a null cursor
  // is the result as expected.

  var trans = db.transaction(['store'], 'readwrite');
  trans.onabort = unexpectedAbortCallback;
  trans.oncomplete = sixthTest;

  var store = trans.objectStore('store');

  var startKey = kNumberOfItems - 1 - kPrefetchThreshold;
  var cursorReq = store.openCursor(IDBKeyRange.lowerBound(startKey));
  cursorReq.onerror = unexpectedErrorCallback;

  count = 0;
  cursorReq.onsuccess = function() {
    cursor = event.target.result;

    if (cursor === null) {
      debug("cursor is null");
      shouldBe("count", "kPrefetchThreshold + 1");
      return;
    }

    debug("count: " + count);
    ++count;
    cursor.continue();
  };
}

function sixthTest() {
  debug("sixthTest()");

  // Test stepping two cursors simultaneously. First cursor1 steps
  // for a while, then cursor2, then back to cursor1, etc.

  var trans = db.transaction(['store'], 'readwrite');
  trans.onabort = unexpectedAbortCallback;
  trans.oncomplete = seventhTest;
  var store = trans.objectStore('store');

  cursor1 = null;
  cursor2 = null;

  count1 = 0;
  count2 = 0;

  var cursor1func = function() {
    var cursor = event.target.result;
    if (cursor === null) {
      shouldBe("count1", "kNumberOfItems");
      cursor2.continue();
      return;
    }

    if (cursor1 === null) {
      cursor1 = cursor;
    }

    if (cursor1.key !== count1)
      shouldBe("cursor1.key", "count1");
    if (cursor1.value !== count1)
      shouldBe("cursor1.value", "count1");

    ++count1;

    if (count1 % 20 === 0) {
      if (cursor2 !== null) {
        cursor2.continue();
      } else {
        var req = store.openCursor();
        req.onerror = unexpectedErrorCallback;
        req.onsuccess = cursor2func;
      }
    } else {
      cursor1.continue();
    }
  };

  var cursor2func = function() {
    var cursor = event.target.result;
    if (cursor === null) {
      shouldBe("count2", "kNumberOfItems");
      return;
    }

    if (cursor2 === null) {
      cursor2 = cursor;
    }

    if (cursor2.key !== count2)
      shouldBe("cursor2.key", "count2");
    if (cursor2.value !== count2)
      shouldBe("cursor2.value", "count2");

    ++count2;

    if (count2 % 20 === 0) {
      cursor1.continue();
    } else {
      cursor2.continue();
    }
  };

  var req = store.openCursor();
  req.onerror = unexpectedErrorCallback;
  req.onsuccess = cursor1func;
}

function seventhTest() {
  debug("seventhTest()");

  // Test iterating straight through an index.

  var trans = db.transaction(['store'], 'readwrite');
  trans.onabort = unexpectedAbortCallback;
  trans.oncomplete = eighthTest;

  var store = trans.objectStore('store');
  var index = store.index('index');

  var cursorReq = index.openCursor();
  cursorReq.onerror = unexpectedErrorCallback;
  count = 0;

  cursorReq.onsuccess = function() {
    cursor = event.target.result;
    if (cursor === null) {
      shouldBe("count", "kNumberOfItems");
      return;
    }

    if (cursor.key !== count)
      shouldBe("cursor.key", "count");
    if (cursor.primaryKey !== count)
      shouldBe("cursor.primaryKey", "count");
    if (cursor.value !== count)
      shouldBe("cursor.value", "count");

    ++count;
    cursor.continue();
  };
}

function eighthTest() {
  debug("eighthTest()");

  // Run a key cursor over an index.

  var trans = db.transaction(['store'], 'readwrite');
  trans.onabort = unexpectedAbortCallback;
  trans.oncomplete = done;

  var store = trans.objectStore('store');
  var index = store.index('index');

  var cursorReq = index.openKeyCursor();
  cursorReq.onerror = unexpectedErrorCallback;
  count = 0;

  cursorReq.onsuccess = function() {
    cursor = event.target.result;
    if (cursor === null) {
      shouldBe("count", "kNumberOfItems");
      return;
    }

    if (cursor.key !== count)
      shouldBe("cursor.key", "count");
    if (cursor.primaryKey !== count)
      shouldBe("cursor.primaryKey", "count");

    ++count;
    cursor.continue();
  };
}