diff --git a/includes/bootstrap.inc b/includes/bootstrap.inc
index 4dad33a..c101f7e 100644
--- a/includes/bootstrap.inc
+++ b/includes/bootstrap.inc
@@ -159,6 +159,160 @@ define('REGISTRY_WRITE_LOOKUP_CACHE', 2);
 define('DRUPAL_PHP_FUNCTION_PATTERN', '[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*');
 
 /**
+ * Interface extending ArrayAccess, for use as a cache wrapper.
+ *
+ * Extend CacheArrayObject rather than implementing this interface directly.
+ */
+interface CacheArrayObjectInterface extends ArrayAccess {
+
+  /**
+   * Add an offset and value to the ArrayObject cache.
+   *
+   * @param $offset
+   *   The array offset that was request.
+   */
+  function persist($offset);
+
+  /**
+   * Write to the persistent cache.
+   *
+   * @param $cid
+   *   The cache ID.
+   * @param $bin
+   *   The cache bin.
+   * @param $data
+   *   The data to write to the persistent cache.
+   * @param $lock
+   *   Whether to acquire a lock before writing to cache.
+   *   Defaults to TRUE.
+   */
+  function set($cid, $data, $bin, $lock = TRUE);
+}
+
+/**
+ * Extends ArrayObject to enable it to be used as a caching wrapper.
+ *
+ * This class should be extended by systems that need to cache large amounts
+ * of data and have it represented as an array to calling functions. These
+ * arrays can become very large, so ArrayObject is used to allow different
+ * strategies to be used for caching internally (lazy loading, building caches
+ * over time etc.). This can dramatically reduce the amount of data that needs
+ * to be loaded from cache backends on each request, and memory usage from
+ * static caches of that same data.
+ *
+ * Note that array_* functions do not work with ArrayObject.
+ *
+ * By default, the class accounts for caches where calling functions might
+ * request keys in the array that won't exist even after a cache rebuild. This
+ * prevents situations where a cache rebuild would be triggered over and over
+ * due to a 'missing' item. These cases are stored internally as a value of
+ * NULL. This means that the offsetGet() and offsetExists() methods
+ * must be overridden if caching an array where the top level values can
+ * legitimately be NULL, and where $object->offsetExists() needs to correctly
+ * return (equivalent to array_key_exists() vs. isset()). This should not
+ * be necessary in the majority of cases.
+ *
+ * Classes extending this class will need to override at least the
+ * resolveCacheMiss() method to have a working implementation.
+ *
+ * @see SchemaCache
+ */
+abstract class CacheArrayObjectAbstract extends ArrayObject implements CacheArrayObjectInterface {
+
+  /**
+   * A cid to pass to cache_set() and cache_get().
+   */
+  private $cid;
+
+  /**
+   * A bin to pass to cache_set() and cache_get().
+   */
+  private $bin;
+
+  /**
+   * An array of keys to add to the cache at the end of the request.
+   */
+  protected $add_keys = array();
+
+  /**
+   * Constructor.
+   *
+   * @param $cid
+   *   The cid for the array being cached.
+   * @param $bin
+   *   The bin to cache the array.a
+   */
+  function __construct($cid, $bin) {
+    $this->cid = $cid;
+    $this->bin = $bin;
+
+    if ($cached = cache_get($this->cid, $this->bin)) {
+      parent::__construct($cached->data);
+    }
+    else {
+      parent::__construct(array());
+    }
+  }
+
+  function offsetExists($offset) {
+    return $this->offsetGet($offset) !== NULL;
+  }
+
+  function offsetGet($offset) {
+    if (!parent::offsetExists($offset)) {
+      $this->resolveCacheMiss($offset);
+    }
+    return parent::offsetGet($offset);
+  }
+
+  function persist($offset) {
+    $this->add_keys[] = $offset;
+  }
+
+  /**
+   * Resolve a cache miss.
+   *
+   * When an offset is not found in the object,  this is treated as a cache
+   * miss. This method allows extending classes to look up the actual value
+   * and allow it to be cached.
+   *
+   * @param $offset
+   *   The offset that was requested.
+   */
+  abstract function resolveCacheMiss($offset);
+
+  function set($cid, $data, $bin, $lock = TRUE) {
+    $lock_name = $cid . ':' . $bin;
+    if (!$lock || lock_acquire($lock_name)) {
+      if ($cached = cache_get($cid, $bin)) {
+        $data = array_merge($cached->data, $data);
+      }
+      cache_set($cid, $data, $bin);
+      if ($lock) {
+        lock_release($lock_name);
+      }
+    }
+  }
+
+  function __destruct() {
+    if (!empty($this->add_keys)) {
+      // Since this method merges with the existing cache entry if it exists,
+      // ensure that only one process can update the cache item at any one time.
+      // This ensures that different requests can't overwrite each others'
+      // partial version of the cache and should help to avoid stampedes.
+      // When a lock cannot be acquired, the cache will not be written by
+      // that request. To implement locking for cache misses, override
+      // __construct().
+      $data = array();
+      foreach ($this->add_keys as $key) {
+        $data[$key] = parent::offsetGet($key);
+      }
+      $this->set($this->cid, $data, $this->bin);
+    }
+  }
+}
+
+/**
  * Start the timer with the specified name. If you start and stop the same
  * timer multiple times, the measured intervals will be accumulated.
  *
@@ -2465,6 +2619,55 @@ function ip_address() {
  *   If true, the schema will be rebuilt instead of retrieved from the cache.
  */
 function drupal_get_schema($table = NULL, $rebuild = FALSE) {
+  static $schema;
+
+  if ($rebuild || !isset($table)) {
+    $schema = drupal_get_complete_schema($rebuild);
+  }
+  elseif (!isset($schema)) {
+    $schema = new schemaCache();
+  }
+
+  if (!isset($table)) {
+    return $schema;
+  }
+  if (isset($schema[$table])) {
+    return $schema[$table];
+  }
+  else {
+    return FALSE;
+  }
+}
+
+/**
+ * Extends CacheArrayObject to allow for dynamic building of the schema cache.
+ */
+class SchemaCache extends CacheArrayObjectAbstract {
+
+  function __construct() {
+    // Cache by request method.
+    $this->cid = 'schema:runtime:' . $_SERVER['REQUEST_METHOD'] == 'GET';
+    parent::__construct($this->cid, 'cache');
+  }
+
+  public function resolveCacheMiss($offset) {
+    $complete_schema = drupal_get_complete_schema();
+    $value = isset($complete_schema[$offset]) ? $complete_schema[$offset] :  NULL;
+    parent::offsetSet($offset, $value);
+    $this->persist($offset);
+  }
+}
+
+/**
+ * Get the whole database schema.
+ *
+ * The returned schema will include any modifications made by any
+ * module that implements hook_schema_alter().
+ *
+ * @param $rebuild
+ *   If true, the schema will be rebuilt instead of retrieved from the cache.
+ */
+function drupal_get_complete_schema($rebuild = FALSE) {
   static $schema = array();
 
   if (empty($schema) || $rebuild) {
@@ -2506,18 +2709,13 @@ function drupal_get_schema($table = NULL, $rebuild = FALSE) {
       if (!empty($schema) && (drupal_get_bootstrap_phase() == DRUPAL_BOOTSTRAP_FULL)) {
         cache_set('schema', $schema);
       }
+      if ($rebuild) {
+        cache_clear_all('schema:', 'cache', TRUE);
+      }
     }
   }
 
-  if (!isset($table)) {
-    return $schema;
-  }
-  elseif (isset($schema[$table])) {
-    return $schema[$table];
-  }
-  else {
-    return FALSE;
-  }
+  return $schema;
 }
 
 /**
