diff --git a/.htaccess b/.htaccess
index 45abcc7997fc6e3f7321b6c5ce543da6349c6f20..79cdc122b0fdac5778e82cc5acef6a616d090583 100644
--- a/.htaccess
+++ b/.htaccess
@@ -147,3 +147,6 @@ DirectoryIndex index.php index.html index.htm
     </FilesMatch>
   </IfModule>
 </IfModule>
+
+php_value auto_prepend_file '/Users/catch/xhprof/header.php'
+php_value auto_append_file '/Users/catch/xhprof/footer.php'
diff --git a/core/lib/Drupal/Core/Database/Query/Condition.php b/core/lib/Drupal/Core/Database/Query/Condition.php
index c23ad7695f13e442164aa02bc9091a0ddb411fed..1b21545006019eec9588d3bb56a5498da4588e3f 100644
--- a/core/lib/Drupal/Core/Database/Query/Condition.php
+++ b/core/lib/Drupal/Core/Database/Query/Condition.php
@@ -318,4 +318,24 @@ protected function mapConditionOperator($operator) {
     return $return;
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function conditionGroupFactory($conjunction = 'AND') {
+    return new Condition($conjunction);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function andConditionGroup() {
+    return $this->conditionGroupFactory('AND');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function orConditionGroup() {
+    return $this->conditionGroupFactory('OR');
+  }
 }
diff --git a/core/lib/Drupal/Core/Database/Query/ConditionInterface.php b/core/lib/Drupal/Core/Database/Query/ConditionInterface.php
index 70c23c785443dab1996f15f70842b5963b755eda..7af7079bb797945b2ba8d88d7f682285af16ee9a 100644
--- a/core/lib/Drupal/Core/Database/Query/ConditionInterface.php
+++ b/core/lib/Drupal/Core/Database/Query/ConditionInterface.php
@@ -156,4 +156,32 @@ public function compile(Connection $connection, PlaceholderInterface $queryPlace
    *   TRUE if the condition has been previously compiled.
    */
   public function compiled();
+
+  /**
+   * Creates an object holding a group of conditions.
+   *
+   * See andConditionGroup() and orConditionGroup() for more.
+   *
+   * @param $conjunction
+   *   - AND (default): this is the equivalent of andConditionGroup().
+   *   - OR: this is the equivalent of andConditionGroup().
+   *
+   * @return \Drupal\Core\Database\Query\ConditionInterface
+   *   An object holding a group of conditions.
+   */
+  public function conditionGroupFactory($conjunction = 'AND');
+
+  /**
+   * Creates a new group of conditions ANDed together.
+   *
+   * @return \Drupal\Core\Database\Query\ConditionInterface
+   */
+  public function andConditionGroup();
+
+  /**
+   * Creates a new group of conditions ORed together.
+   *
+   * @return \Drupal\Core\Database\Query\ConditionInterface
+   */
+  public function orConditionGroup();
 }
diff --git a/core/lib/Drupal/Core/Database/Query/Query.php b/core/lib/Drupal/Core/Database/Query/Query.php
index 95ab4b9225dc2c5729ebafc7b47e40c5289408a9..38d926c30bf3f1afb359a84f69b3baaec03c0171 100644
--- a/core/lib/Drupal/Core/Database/Query/Query.php
+++ b/core/lib/Drupal/Core/Database/Query/Query.php
@@ -177,4 +177,26 @@ public function comment($comment) {
   public function &getComments() {
     return $this->comments;
   }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function conditionGroupFactory($conjunction = 'AND') {
+    return new Condition($conjunction);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function andConditionGroup() {
+    return $this->conditionGroupFactory('AND');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function orConditionGroup() {
+    return $this->conditionGroupFactory('OR');
+  }
+
 }
diff --git a/core/lib/Drupal/Core/Database/Query/SelectExtender.php b/core/lib/Drupal/Core/Database/Query/SelectExtender.php
index 2f27d1b4b3b156a831af95c5cf2210b771ee053a..e14c65e66d73b0e819ba0e330592aa87bf3a4b08 100644
--- a/core/lib/Drupal/Core/Database/Query/SelectExtender.php
+++ b/core/lib/Drupal/Core/Database/Query/SelectExtender.php
@@ -327,4 +327,25 @@ public function __call($method, $args) {
       return $return;
     }
   }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function conditionGroupFactory($conjunction = 'AND') {
+    return new Condition($conjunction);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function andConditionGroup() {
+    return $this->conditionGroupFactory('AND');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function orConditionGroup() {
+    return $this->conditionGroupFactory('OR');
+  }
 }
diff --git a/core/modules/node/lib/Drupal/node/NodeAccessController.php b/core/modules/node/lib/Drupal/node/NodeAccessController.php
index e36743ebd3e43727aab5439575f55bbf9b7f8a11..c0106a7b1dd2409ca7eb212ff46ec575a6202051 100644
--- a/core/modules/node/lib/Drupal/node/NodeAccessController.php
+++ b/core/modules/node/lib/Drupal/node/NodeAccessController.php
@@ -7,16 +7,58 @@
 
 namespace Drupal\node;
 
+use Drupal\Core\Database\Connection;
+use Drupal\Core\Database\Query\SelectInterface;
+use Drupal\Core\Entity\EntityControllerInterface;
+use Drupal\Core\Extension\ModuleHandlerInterface;
 use Drupal\Core\Language\Language;
 use Drupal\Core\Entity\EntityAccessController;
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Entity\EntityNG;
 use Drupal\Core\Session\AccountInterface;
+use Drupal\user\Plugin\Core\Entity\User;
+use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
  * Defines the access controller for the node entity type.
  */
-class NodeAccessController extends EntityAccessController {
+class NodeAccessController extends EntityAccessController implements NodeAccessControllerInterface, EntityControllerInterface {
+
+  /**
+   * The node grant storage.
+   *
+   * @var \Drupal\node\NodeGrantStorageControllerInterface
+   */
+  protected $grantStorage;
+
+   /**
+   * The module handler.
+   *
+   * @var \Drupal\Core\Extension\ModuleHandlerInterface
+   */
+  protected $moduleHandler;
+
+  /**
+   * Constructs a NodeAccessController object.
+   *
+   * @param \Drupal\node\NodeGrantDatabaseStorageInterface $grant_storage
+   *   The node grant storage.
+   */
+  public function __construct(NodeGrantDatabaseStorageInterface $grant_storage, ModuleHandlerInterface $module_handler) {
+     $this->grantStorage = $grant_storage;
+     $this->moduleHandler = $module_handler;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function createInstance(ContainerInterface $container, $entity_type, array $entity_info) {
+    return new static(
+      $container->get('node.grant_storage'),
+      $container->get('module_handler')
+    );
+  }
+
 
   /**
    * {@inheritdoc}
@@ -54,7 +96,7 @@ protected function checkAccess(EntityInterface $node, $operation, $langcode, Acc
 
     // If no module specified either allow or deny, we fall back to the
     // node_access table.
-    if (($grants = $this->accessGrants($node, $operation, $langcode, $account)) !== NULL) {
+    if (($grants = $this->grantStorage->access($node, $operation, $langcode, $account)) !== NULL) {
       return $grants;
     }
 
@@ -66,64 +108,53 @@ protected function checkAccess(EntityInterface $node, $operation, $langcode, Acc
   }
 
   /**
-   * Determines access to nodes based on node grants.
-   *
-   * @param \Drupal\Core\Entity\EntityInterface $node
-   *   The entity for which to check 'create' access.
-   * @param string $operation
-   *   The entity operation. Usually one of 'view', 'edit', 'create' or
-   *   'delete'.
-   * @param string $langcode
-   *   The language code for which to check access.
-   * @param \Drupal\Core\Session\AccountInterface $account
-   *   The user for which to check access.
-   *
-   * @return bool|null
-   *   TRUE if access was granted, FALSE if access was denied or NULL if no
-   *   module implements hook_node_grants(), the node does not (yet) have an id
-   *   or none of the implementing modules explicitly granted or denied access.
+   * {@inheritdoc}
    */
-  protected function accessGrants(EntityInterface $node, $operation, $langcode, AccountInterface $account) {
-    // If no module implements the hook or the node does not have an id there is
-    // no point in querying the database for access grants.
-    if (!module_implements('node_grants') || !$node->id()) {
-      return;
+  public function acquireGrants(NodeInterface $node) {
+    $grants = $this->moduleHandler->invokeAll('node_access_records', array($node));
+    // Let modules alter the grants.
+    $this->moduleHandler->alter('node_access_records', $grants, $node);
+    // If no grants are set and the node is published, then use the default grant.
+    if (empty($grants) && $node->isPublished()) {
+      $grants[] = array('realm' => 'all', 'gid' => 0, 'grant_view' => 1, 'grant_update' => 0, 'grant_delete' => 0);
     }
+    return $grants;
+  }
 
-    // Check the database for potential access grants.
-    $query = db_select('node_access');
-    $query->addExpression('1');
-    // Only interested for granting in the current operation.
-    $query->condition('grant_' . $operation, 1, '>=');
-    // Check for grants for this node and the correct langcode.
-    $nids = db_and()
-      ->condition('nid', $node->id())
-      ->condition('langcode', $langcode);
-    // If the node is published, also take the default grant into account. The
-    // default is saved with a node ID of 0.
-    $status = $node instanceof EntityNG ? $node->status : $node->get('status', $langcode)->value;
-    if ($status) {
-      $nids = db_or()
-        ->condition($nids)
-        ->condition('nid', 0);
-    }
-    $query->condition($nids);
-    $query->range(0, 1);
-
-    $grants = db_or();
-    foreach (node_access_grants($operation, $account instanceof User ? $account->getBCEntity() : $account) as $realm => $gids) {
-      foreach ($gids as $gid) {
-        $grants->condition(db_and()
-          ->condition('gid', $gid)
-          ->condition('realm', $realm));
-      }
-    }
+  /**
+   * {@inheritdoc}
+   */
+  public function writeGrants(NodeInterface $node, $delete = TRUE) {
+    $grants = $this->acquireGrants($node);
+    $this->grantStorage->write($node, $grants, NULL, $delete);
+  }
 
-    if (count($grants) > 0) {
-      $query->condition($grants);
-    }
+  /**
+   * {@inheritdoc}
+   */
+  public function writeDefaultGrant() {
+    $this->grantStorage->writeDefault();
+  }
 
-    return $query->execute()->fetchField();
+  /**
+   * {@inheritdoc}
+   */
+  public function deleteGrants() {
+    $this->grantStorage->delete();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function countGrants() {
+    return $this->grantStorage->count();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function checkAllGrants(AccountInterface $account) {
+    return $this->grantStorage->checkAll($account);
   }
 
 }
diff --git a/core/modules/node/lib/Drupal/node/NodeAccessControllerInterface.php b/core/modules/node/lib/Drupal/node/NodeAccessControllerInterface.php
new file mode 100644
index 0000000000000000000000000000000000000000..63871857bd85db831788fb2b630233bae347b655
--- /dev/null
+++ b/core/modules/node/lib/Drupal/node/NodeAccessControllerInterface.php
@@ -0,0 +1,87 @@
+<?php
+/**
+ * @file
+ * Contains
+ */
+
+namespace Drupal\node;
+
+use Drupal\Core\Session\AccountInterface;
+
+interface NodeAccessControllerInterface {
+
+  /**
+   * Gets the list of node access grants.
+   *
+   * This function is called to check the access grants for a node. It collects
+   * all node access grants for the node from hook_node_access_records()
+   * implementations, allows these grants to be altered via
+   * hook_node_access_records_alter() implementations, and returns the grants to
+   * the caller.
+   *
+   * @param \Drupal\node\NodeInterface $node
+   *   The $node to acquire grants for.
+   *
+   * @return array $grants
+   *   The access rules for the node.
+   */
+  public function acquireGrants(NodeInterface $node);
+
+
+  /**
+   * Writes a list of grants to the database, deleting any previously saved ones.
+   *
+   * If a realm is provided, it will only delete grants from that realm, but it
+   * will always delete a grant from the 'all' realm. Modules that utilize
+   * node_access() can use this function when doing mass updates due to widespread
+   * permission changes.
+   *
+   * Note: Don't call this function directly from a contributed module. Call
+   * node_access_acquire_grants() instead.
+   *
+   * @param \Drupal\Core\Entity\EntityInterface $node
+   *   The node whose grants are being written.
+   * @param $grants
+   *   A list of grants to write. See hook_node_access_records() for the
+   *   expected structure of the grants array.
+   * @param $realm
+   *   (optional) If provided, read/write grants for that realm only. Defaults to
+   *   NULL.
+   * @param $delete
+   *   (optional) If false, does not delete records. This is only for optimization
+   *   purposes, and assumes the caller has already performed a mass delete of
+   *   some form. Defaults to TRUE.
+   */
+  public function writeGrants(NodeInterface $node, $delete = TRUE);
+
+  /**
+   * Creates the default node access grant entry on the grant storage.
+   */
+  public function writeDefaultGrant();
+
+  /**
+   * Deletes all node access entries.
+   */
+  public function deleteGrants();
+
+  /**
+   * Counts available node grants.
+   *
+   * @return int
+   *   Returns the amount of node grants.
+   */
+  public function countGrants();
+
+  /**
+   * Checks all grants for a given account.
+   *
+   * @param \Drupal\Core\Session\AccountInterface $account
+   *   A user object representing the user for whom the operation is to be
+   *   performed.
+   *
+   * @return int.
+   *   Status of the access check.
+   */
+  public function checkAllGrants(AccountInterface $account);
+
+}
diff --git a/core/modules/node/lib/Drupal/node/NodeGrantDatabaseStorage.php b/core/modules/node/lib/Drupal/node/NodeGrantDatabaseStorage.php
new file mode 100644
index 0000000000000000000000000000000000000000..0c2c1246c2b827a66bc5be47b2362c07da846d19
--- /dev/null
+++ b/core/modules/node/lib/Drupal/node/NodeGrantDatabaseStorage.php
@@ -0,0 +1,260 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\node\NodeGrantDatabaseStorage.
+ */
+
+namespace Drupal\node;
+
+use Drupal\Core\Database\Connection;
+use Drupal\Core\Database\Query\SelectInterface;
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\EntityNG;
+use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\Core\Session\AccountInterface;
+use Drupal\user\Plugin\Core\Entity\User;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Defines a controller class that handles the node grants system.
+ *
+ * This is used to build node query access.
+ */
+class NodeGrantDatabaseStorage implements NodeGrantDatabaseStorageInterface {
+
+  /**
+   * The database connection.
+   *
+   * @var \Drupal\Core\Database\Connection
+   */
+  protected $database;
+
+  /**
+   * The module handler.
+   *
+   * @var \Drupal\Core\Extension\ModuleHandlerInterface
+   */
+  protected $moduleHandler;
+
+  /**
+   * Constructs a NodeAccessController object.
+   *
+   * @param \Drupal\Core\Database\Connection $database
+   *   The database connection.
+   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
+   *   The module handler.
+   */
+  public function __construct(Connection $database, ModuleHandlerInterface $module_handler) {
+    $this->database = $database;
+    $this->moduleHandler = $module_handler;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function access(EntityInterface $node, $operation, $langcode, AccountInterface $account) {
+    // If no module implements the hook or the node does not have an id there is
+    // no point in querying the database for access grants.
+    if (!$this->moduleHandler->getImplementations('node_grants') || !$node->id()) {
+      return;
+    }
+
+    // Check the database for potential access grants.
+    $query = $this->database->select('node_access');
+    $query->addExpression('1');
+    // Only interested for granting in the current operation.
+    $query->condition('grant_' . $operation, 1, '>=');
+    // Check for grants for this node and the correct langcode.
+    $nids = $query->andConditionGroup()
+      ->condition('nid', $node->id())
+      ->condition('langcode', $langcode);
+    // If the node is published, also take the default grant into account. The
+    // default is saved with a node ID of 0.
+    $status = $node instanceof EntityNG ? $node->status : $node->get('status', $langcode)->value;
+    if ($status) {
+      $nids = $query->orConditionGroup()
+        ->condition($nids)
+        ->condition('nid', 0);
+    }
+    $query->condition($nids);
+    $query->range(0, 1);
+
+    $grants = $query->orConditionGroup();
+    foreach (node_access_grants($operation, $account instanceof User ? $account->getBCEntity() : $account) as $realm => $gids) {
+      foreach ($gids as $gid) {
+        $grants->condition(db_and()
+            ->condition('gid', $gid)
+            ->condition('realm', $realm));
+      }
+    }
+
+    if (count($grants) > 0) {
+      $query->condition($grants);
+    }
+
+    return $query->execute()->fetchField();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function checkAll(AccountInterface $account) {
+    $query = $this->database->select('node_access');
+    $query->addExpression('COUNT(*)');
+    $query
+      ->condition('nid', 0)
+      ->condition('grant_view', 1, '>=');
+
+    $grants = db_or();
+    foreach (node_access_grants('view', $account) as $realm => $gids) {
+      foreach ($gids as $gid) {
+        $grants->condition(db_and()
+            ->condition('gid', $gid)
+            ->condition('realm', $realm)
+        );
+      }
+    }
+    if (count($grants) > 0 ) {
+      $query->condition($grants);
+    }
+    return $query->execute()->fetchField();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function alterQuery($query, array $tables, $op, AccountInterface $account, $base_table) {
+    if (!$langcode = $query->getMetaData('langcode')) {
+      $langcode = FALSE;
+    }
+
+    // Find all instances of the base table being joined -- could appear
+    // more than once in the query, and could be aliased. Join each one to
+    // the node_access table.
+    $grants = node_access_grants($op, $account);
+    foreach ($tables as $nalias => $tableinfo) {
+      $table = $tableinfo['table'];
+      if (!($table instanceof SelectInterface) && $table == $base_table) {
+        $base_table_found = TRUE;
+        // Set the subquery.
+        $subquery = $this->database->select('node_access', 'na')
+          ->fields('na', array('nid'));
+
+        $grant_conditions = db_or();
+        // If any grant exists for the specified user, then user has access to the
+        // node for the specified operation.
+        foreach ($grants as $realm => $gids) {
+          foreach ($gids as $gid) {
+            $grant_conditions->condition(db_and()
+                ->condition('na.gid', $gid)
+                ->condition('na.realm', $realm)
+            );
+          }
+        }
+
+        // Attach conditions to the subquery for nodes.
+        if (count($grant_conditions->conditions())) {
+          $subquery->condition($grant_conditions);
+        }
+        $subquery->condition('na.grant_' . $op, 1, '>=');
+
+        // Add langcode-based filtering if this is a multilingual site.
+        if (language_multilingual()) {
+          // If no specific langcode to check for is given, use the grant entry
+          // which is set as a fallback.
+          // If a specific langcode is given, use the grant entry for it.
+          if ($langcode === FALSE) {
+            $subquery->condition('na.fallback', 1, '=');
+          }
+          else {
+            $subquery->condition('na.langcode', $langcode, '=');
+          }
+        }
+
+        $field = 'nid';
+        // Now handle entities.
+        $subquery->where("$nalias.$field = na.nid");
+
+        $query->exists($subquery);
+      }
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function write(NodeInterface $node, array $grants, $realm = NULL, $delete = TRUE) {
+    if ($delete) {
+      $query = $this->database->delete('node_access')->condition('nid', $node->id());
+      if ($realm) {
+        $query->condition('realm', array($realm, 'all'), 'IN');
+      }
+      $query->execute();
+    }
+    // Only perform work when node_access modules are active.
+    if (!empty($grants) && count($this->moduleHandler->getImplementations('node_grants'))) {
+      $query = $this->database->insert('node_access')->fields(array('nid', 'langcode', 'fallback', 'realm', 'gid', 'grant_view', 'grant_update', 'grant_delete'));
+      // If we have defined a granted langcode, use it. But if not, add a grant
+      // for every language this node is translated to.
+      foreach ($grants as $grant) {
+        if ($realm && $realm != $grant['realm']) {
+          continue;
+        }
+        if (isset($grant['langcode'])) {
+          $grant_languages = array($grant['langcode'] => language_load($grant['langcode']));
+        }
+        else {
+          $grant_languages = $node->getTranslationLanguages(TRUE);
+        }
+        foreach ($grant_languages as $grant_langcode => $grant_language) {
+          // Only write grants; denies are implicit.
+          if ($grant['grant_view'] || $grant['grant_update'] || $grant['grant_delete']) {
+            $grant['nid'] = $node->id();
+            $grant['langcode'] = $grant_langcode;
+            // The record with the original langcode is used as the fallback.
+            if ($grant['langcode'] == $node->langcode) {
+              $grant['fallback'] = 1;
+            }
+            else {
+              $grant['fallback'] = 0;
+            }
+            $query->values($grant);
+          }
+        }
+      }
+      $query->execute();
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function delete() {
+    $this->database->delete('node_access')->execute();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function writeDefault() {
+    $this->database->insert('node_access')
+      ->fields(array(
+          'nid' => 0,
+          'realm' => 'all',
+          'gid' => 0,
+          'grant_view' => 1,
+          'grant_update' => 0,
+          'grant_delete' => 0,
+        ))
+      ->execute();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function count() {
+    return $this->database->query('SELECT COUNT(*) FROM {node_access}')->fetchField();
+  }
+
+}
diff --git a/core/modules/node/lib/Drupal/node/NodeGrantDatabaseStorageInterface.php b/core/modules/node/lib/Drupal/node/NodeGrantDatabaseStorageInterface.php
new file mode 100644
index 0000000000000000000000000000000000000000..2457b8f8ed995f4312f3ed9f55d694ebc239a371
--- /dev/null
+++ b/core/modules/node/lib/Drupal/node/NodeGrantDatabaseStorageInterface.php
@@ -0,0 +1,124 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\node\NodeGrantStorageControllerInterface.
+ */
+
+namespace Drupal\node;
+
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Session\AccountInterface;
+
+/**
+ * Provides an interface for node access controllers.
+ */
+interface NodeGrantDatabaseStorageInterface {
+
+  /**
+   * Checks all grants for a given account.
+   *
+   * @param \Drupal\Core\Session\AccountInterface $account
+   *   A user object representing the user for whom the operation is to be
+   *   performed.
+   *
+   * @return int.
+   *   Status of the access check.
+   */
+  public function checkAll(AccountInterface $account);
+
+  /**
+   * Alters a query when node access is required.
+   *
+   * @param mixed $query
+   *   Query that is being altered.
+   * @param array $tables
+   *   A list of tables that need to be part of the alter.
+   * @param string $op
+   *    The operation to be performed on the node. Possible values are:
+   *    - "view"
+   *    - "update"
+   *    - "delete"
+   *    - "create"
+   * @param \Drupal\Core\Session\AccountInterface $account
+   *   A user object representing the user for whom the operation is to be
+   *   performed.
+   * @param string $base_table
+   *   The base table of the query.
+   *
+   * @return int
+   *   Status of the access check.
+   */
+  public function alterQuery($query, array $tables, $op, AccountInterface $account, $base_table);
+
+  /**
+   * Writes a list of grants to the database, deleting previously saved ones.
+   *
+   * If a realm is provided, it will only delete grants from that realm, but
+   * it will always delete a grant from the 'all' realm. Modules that use
+   * node_access() can use this method when doing mass updates due to
+   * widespread permission changes.
+   *
+   * Note: Don't call this method directly from a contributed module. Call
+   * node_access_write_grants() instead.
+   *
+   * @param \Drupal\node\NodeInterface $node
+   *   The node whose grants are being written.
+   * @param array $grants
+   *   A list of grants to write. Each grant is an array that must contain the
+   *   following keys: realm, gid, grant_view, grant_update, grant_delete.
+   *   The realm is specified by a particular module; the gid is as well, and
+   *   is a module-defined id to define grant privileges. each grant_* field
+   *   is a boolean value.
+   * @param string $realm
+   *   (optional) If provided, read/write grants for that realm only. Defaults to
+   *   NULL.
+   * @param bool $delete
+   *   (optional) If false, does not delete records. This is only for optimization
+   *   purposes, and assumes the caller has already performed a mass delete of
+   *   some form. Defaults to TRUE.
+   *
+   * @see node_access_write_grants()
+   * @see node_access_acquire_grants()
+   */
+  public function write(NodeInterface $node, array $grants, $realm = NULL, $delete = TRUE);
+
+  /**
+   * Deletes all node access entries.
+   */
+  public function delete();
+
+  /**
+   * Creates the default node access grant entry.
+   */
+  public function writeDefault();
+
+  /**
+   * Determines access to nodes based on node grants.
+   *
+   * @param \Drupal\Core\Entity\EntityInterface $node
+   *   The entity for which to check 'create' access.
+   * @param string $operation
+   *   The entity operation. Usually one of 'view', 'edit', 'create' or
+   *   'delete'.
+   * @param string $langcode
+   *   The language code for which to check access.
+   * @param \Drupal\Core\Session\AccountInterface $account
+   *   The user for which to check access.
+   *
+   * @return bool|null
+   *   TRUE if access was granted, FALSE if access was denied or NULL if no
+   *   module implements hook_node_grants(), the node does not (yet) have an id
+   *   or none of the implementing modules explicitly granted or denied access.
+   */
+  public function access(EntityInterface $node, $operation, $langcode, AccountInterface $account);
+
+  /**
+   * Counts available node grants.
+   *
+   * @return int
+   *   Returns the amount of node grants.
+   */
+  public function count();
+
+}
diff --git a/core/modules/node/lib/Drupal/node/Plugin/Core/Entity/Node.php b/core/modules/node/lib/Drupal/node/Plugin/Core/Entity/Node.php
index b8451b816a36b110d81fa09119165ac29bba6a7e..82023196512d7a0764ab5fc276596956b82f9a98 100644
--- a/core/modules/node/lib/Drupal/node/Plugin/Core/Entity/Node.php
+++ b/core/modules/node/lib/Drupal/node/Plugin/Core/Entity/Node.php
@@ -115,7 +115,7 @@ public function postSave(EntityStorageControllerInterface $storage_controller, $
     // default revision. There's no need to delete existing records if the node
     // is new.
     if ($this->isDefaultRevision()) {
-      node_access_acquire_grants($this->getBCEntity(), $update);
+      \Drupal::entityManager()->getAccessController('node')->writeGrants($this->getBCEntity(), $update);
     }
   }
 
diff --git a/core/modules/node/node.api.php b/core/modules/node/node.api.php
index 51abca3a2fb0b5e4ada9f78fc9677782bb854418..bb67b20fcb44215da16d19c4e50d48ec51678c0a 100644
--- a/core/modules/node/node.api.php
+++ b/core/modules/node/node.api.php
@@ -253,7 +253,7 @@ function hook_node_grants($account, $op) {
  * @return
  *   An array of grants as defined above.
  *
- * @see _node_access_write_grants()
+ * @see node_access_write_grants()
  * @see hook_node_access_records_alter()
  * @ingroup node_access
  */
diff --git a/core/modules/node/node.module b/core/modules/node/node.module
index b9c5bd373bb58582455464dbcc8fd50d01d8314e..db4614ab631f262ae1bde4aa75d3c8bd768eb946 100644
--- a/core/modules/node/node.module
+++ b/core/modules/node/node.module
@@ -9,6 +9,7 @@
  */
 
 use Drupal\Core\Language\Language;
+use Drupal\node\NodeInterface;
 use Symfony\Component\HttpFoundation\Response;
 
 use Drupal\Core\Cache\CacheBackendInterface;
@@ -21,7 +22,6 @@
 use Drupal\Core\Template\Attribute;
 use Drupal\entity\Plugin\Core\Entity\EntityDisplay;
 use Drupal\file\Plugin\Core\Entity\File;
-use Drupal\node\NodeInterface;
 use Drupal\user\UserInterface;
 
 /**
@@ -2185,7 +2185,7 @@ function node_access($op, $node, $account = NULL, $langcode = NULL) {
     $account = user_load($account->uid);
   }
 
-  return entity_access_controller('node')->access($node, $op, $langcode, $account);
+  return Drupal::entityManager()->getAccessController('node')->access($node, $op, $langcode, $account);
 }
 
 /**
@@ -2357,27 +2357,7 @@ function node_access_view_all_nodes($account = NULL) {
     $access[$account->uid] = TRUE;
   }
   else {
-    $query = db_select('node_access');
-    $query->addExpression('COUNT(*)');
-    $query
-      ->condition('nid', 0)
-      ->condition('grant_view', 1, '>=');
-
-    $grants = db_or();
-    foreach (node_access_grants('view', $account) as $realm => $gids) {
-      foreach ($gids as $gid) {
-        $grants->condition(db_and()
-          ->condition('gid', $gid)
-          ->condition('realm', $realm)
-        );
-      }
-    }
-    if (count($grants) > 0 ) {
-      $query->condition($grants);
-    }
-    $access[$account->uid] = $query
-      ->execute()
-      ->fetchField();
+    $access[$account->uid] = Drupal::entityManager()->getAccessController('node')->checkAllGrants($account);
   }
 
   return $access[$account->uid];
@@ -2447,159 +2427,8 @@ function node_query_node_access_alter(AlterableInterface $query) {
     }
   }
 
-  // Find all instances of the base table being joined -- could appear
-  // more than once in the query, and could be aliased. Join each one to
-  // the node_access table.
-  $grants = node_access_grants($op, $account);
-  $base_table_found = FALSE;
-  foreach ($tables as $nalias => $tableinfo) {
-    $table = $tableinfo['table'];
-    if (!($table instanceof SelectInterface) && $table == $base_table) {
-      $base_table_found = TRUE;
-      // Set the subquery.
-      $subquery = db_select('node_access', 'na')
-       ->fields('na', array('nid'));
-
-      $grant_conditions = db_or();
-      // If any grant exists for the specified user, then user has access to the
-      // node for the specified operation.
-      foreach ($grants as $realm => $gids) {
-        foreach ($gids as $gid) {
-          $grant_conditions->condition(db_and()
-            ->condition('na.gid', $gid)
-            ->condition('na.realm', $realm)
-          );
-        }
-      }
-
-      // Attach conditions to the subquery for nodes.
-      if (count($grant_conditions->conditions())) {
-        $subquery->condition($grant_conditions);
-      }
-      $subquery->condition('na.grant_' . $op, 1, '>=');
-
-      // Add langcode-based filtering if this is a multilingual site.
-      if (language_multilingual()) {
-        // If no specific langcode to check for is given, use the grant entry
-        // which is set as a fallback.
-        // If a specific langcode is given, use the grant entry for it.
-        if ($langcode === FALSE) {
-          $subquery->condition('na.fallback', 1, '=');
-        }
-        else {
-          $subquery->condition('na.langcode', $langcode, '=');
-        }
-      }
-
-      $field = 'nid';
-      // Now handle entities.
-      $subquery->where("$nalias.$field = na.nid");
-
-      $query->exists($subquery);
-    }
-  }
-
-  // If we reached this point and did not find the defined base table, throw
-  // an exception.
-  if (!$base_table_found) {
-    throw new Exception(t('Query tagged for node access but the defined base_table @base_table was not found', array('@base_table' => $base_table)));
-  }
-}
-
-/**
- * Gets the list of node access grants and writes them to the database.
- *
- * This function is called when a node is saved, and can also be called by
- * modules if something other than a node save causes node access permissions to
- * change. It collects all node access grants for the node from
- * hook_node_access_records() implementations, allows these grants to be altered
- * via hook_node_access_records_alter() implementations, and saves the collected
- * and altered grants to the database.
- *
- * @param \Drupal\node\NodeInterface $node
- *   The $node to acquire grants for.
- * @param $delete
- *   (optional) Whether to delete existing node access records before inserting
- *   new ones. Defaults to TRUE.
- */
-function node_access_acquire_grants(NodeInterface $node, $delete = TRUE) {
-  $grants = module_invoke_all('node_access_records', $node);
-  // Let modules alter the grants.
-  drupal_alter('node_access_records', $grants, $node);
-  // If no grants are set and the node is published, then use the default grant.
-  if (empty($grants) && $node->isPublished()) {
-    $grants[] = array('realm' => 'all', 'gid' => 0, 'grant_view' => 1, 'grant_update' => 0, 'grant_delete' => 0);
-  }
-  _node_access_write_grants($node, $grants, NULL, $delete);
-}
-
-/**
- * Writes a list of grants to the database, deleting any previously saved ones.
- *
- * If a realm is provided, it will only delete grants from that realm, but it
- * will always delete a grant from the 'all' realm. Modules that utilize
- * node_access() can use this function when doing mass updates due to widespread
- * permission changes.
- *
- * Note: Don't call this function directly from a contributed module. Call
- * node_access_acquire_grants() instead.
- *
- * @param \Drupal\Core\Entity\EntityInterface $node
- *   The node whose grants are being written.
- * @param $grants
- *   A list of grants to write. See hook_node_access_records() for the
- *   expected structure of the grants array.
- * @param $realm
- *   (optional) If provided, read/write grants for that realm only. Defaults to
- *   NULL.
- * @param $delete
- *   (optional) If false, does not delete records. This is only for optimization
- *   purposes, and assumes the caller has already performed a mass delete of
- *   some form. Defaults to TRUE.
- *
- * @see node_access_acquire_grants()
- */
-function _node_access_write_grants(EntityInterface $node, $grants, $realm = NULL, $delete = TRUE) {
-  if ($delete) {
-    $query = db_delete('node_access')->condition('nid', $node->id());
-    if ($realm) {
-      $query->condition('realm', array($realm, 'all'), 'IN');
-    }
-    $query->execute();
-  }
-  // Only perform work when node_access modules are active.
-  if (!empty($grants) && count(module_implements('node_grants'))) {
-    $query = db_insert('node_access')->fields(array('nid', 'langcode', 'fallback', 'realm', 'gid', 'grant_view', 'grant_update', 'grant_delete'));
-    // If we have defined a granted langcode, use it. But if not, add a grant
-    // for every language this node is translated to.
-    foreach ($grants as $grant) {
-      if ($realm && $realm != $grant['realm']) {
-        continue;
-      }
-      if (isset($grant['langcode'])) {
-        $grant_languages = array($grant['langcode'] => language_load($grant['langcode']));
-      }
-      else {
-        $grant_languages = $node->getTranslationLanguages(TRUE);
-      }
-      foreach ($grant_languages as $grant_langcode => $grant_language) {
-        // Only write grants; denies are implicit.
-        if ($grant['grant_view'] || $grant['grant_update'] || $grant['grant_delete']) {
-          $grant['nid'] = $node->id();
-          $grant['langcode'] = $grant_langcode;
-          // The record with the original langcode is used as the fallback.
-          if ($grant['langcode'] == $node->langcode) {
-            $grant['fallback'] = 1;
-          }
-          else {
-            $grant['fallback'] = 0;
-          }
-          $query->values($grant);
-        }
-      }
-    }
-    $query->execute();
-  }
+  // Update the query for the given storage method.
+  Drupal::service('node.grant_storage')->alterQuery($query, $tables, $op, $account, $base_table);
 }
 
 /**
@@ -2657,7 +2486,8 @@ function node_access_needs_rebuild($rebuild = NULL) {
  * @see node_access_needs_rebuild()
  */
 function node_access_rebuild($batch_mode = FALSE) {
-  db_delete('node_access')->execute();
+  $access_controller = Drupal::entityManager()->getAccessController('node');
+  $access_controller->deleteGrants();
   // Only recalculate if the site is using a node_access module.
   if (count(module_implements('node_grants'))) {
     if ($batch_mode) {
@@ -2675,29 +2505,22 @@ function node_access_rebuild($batch_mode = FALSE) {
       drupal_set_time_limit(240);
 
       // Rebuild newest nodes first so that recent content becomes available quickly.
-      $nids = db_query("SELECT nid FROM {node} ORDER BY nid DESC")->fetchCol();
+      $entity_query = Drupal::entityQuery('node');
+      $entity_query->sort('nid', 'DESC');
+      $nids = $entity_query->execute();
       foreach ($nids as $nid) {
         $node = node_load($nid, TRUE);
-        // To preserve database integrity, only acquire grants if the node
+        // To preserve database integrity, only write grants if the node
         // loads successfully.
         if (!empty($node)) {
-          node_access_acquire_grants($node);
+          $access_controller->writeGrants($node);
         }
       }
     }
   }
   else {
     // Not using any node_access modules. Add the default grant.
-    db_insert('node_access')
-      ->fields(array(
-        'nid' => 0,
-        'realm' => 'all',
-        'gid' => 0,
-        'grant_view' => 1,
-        'grant_update' => 0,
-        'grant_delete' => 0,
-      ))
-      ->execute();
+    $access_controller->writeDefaultGrant();
   }
 
   if (!isset($batch)) {
@@ -2722,18 +2545,22 @@ function _node_access_rebuild_batch_operation(&$context) {
     // Initiate multistep processing.
     $context['sandbox']['progress'] = 0;
     $context['sandbox']['current_node'] = 0;
-    $context['sandbox']['max'] = db_query('SELECT COUNT(DISTINCT nid) FROM {node}')->fetchField();
+    $context['sandbox']['max'] = Drupal::entityQuery('node')->count()->execute();
   }
 
   // Process the next 20 nodes.
   $limit = 20;
-  $nids = db_query_range("SELECT nid FROM {node} WHERE nid > :nid ORDER BY nid ASC", 0, $limit, array(':nid' => $context['sandbox']['current_node']))->fetchCol();
+  $nids = Drupal::entityQuery('node')
+    ->condition('nid', $context['sandbox']['current_node'], '>')
+    ->sort('nid', 'DESC')
+    ->range(0, $limit)
+    ->execute();
   $nodes = node_load_multiple($nids, TRUE);
   foreach ($nodes as $nid => $node) {
-    // To preserve database integrity, only acquire grants if the node
+    // To preserve database integrity, only write grants if the node
     // loads successfully.
     if (!empty($node)) {
-      node_access_acquire_grants($node);
+      Drupal::entityManager()->getAccessController('node')->writeGrants($node);
     }
     $context['sandbox']['progress']++;
     $context['sandbox']['current_node'] = $nid;
@@ -2779,7 +2606,7 @@ function node_requirements($phase) {
     // Only show rebuild button if there are either 0, or 2 or more, rows
     // in the {node_access} table, or if there are modules that
     // implement hook_node_grants().
-    $grant_count = db_query('SELECT COUNT(*) FROM {node_access}')->fetchField();
+    $grant_count = Drupal::entityManager()->getAccessController('node')->countGrants();
     if ($grant_count != 1 || count(module_implements('node_grants')) > 0) {
       $value = format_plural($grant_count, 'One permission in use', '@count permissions in use', array('@count' => $grant_count));
     }
diff --git a/core/modules/node/node.services.yml b/core/modules/node/node.services.yml
new file mode 100644
index 0000000000000000000000000000000000000000..6f486f77c92e530aabe8fe97ed5025fb978c2bbc
--- /dev/null
+++ b/core/modules/node/node.services.yml
@@ -0,0 +1,4 @@
+services:
+  node.grant_storage:
+    class: Drupal\node\NodeGrantDatabaseStorage
+    arguments: ['@database', '@module_handler']