Index: includes/database/select.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/database/select.inc,v
retrieving revision 1.12
diff -u -F^f -p -r1.12 select.inc
--- includes/database/select.inc	30 Dec 2008 16:43:14 -0000	1.12
+++ includes/database/select.inc	2 Jan 2009 03:35:20 -0000
@@ -291,7 +291,13 @@ class SelectQuery extends Query implemen
   }
 
   public function execute() {
+    // Modules may alter all queries or only those having a particular tag.
     drupal_alter('query', $this);
+    if (isset($this->alterTags)) {
+      foreach ($this->alterTags as $tag => $value) {
+        drupal_alter("query_$tag", $this);
+      }
+    }
 
     $args = $this->getArguments();
 
Index: modules/node/node.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/node/node.module,v
retrieving revision 1.1008
diff -u -F^f -p -r1.1008 node.module
--- modules/node/node.module	31 Dec 2008 12:02:22 -0000	1.1008
+++ modules/node/node.module	2 Jan 2009 03:35:21 -0000
@@ -2324,17 +2324,23 @@ function node_db_rewrite_sql($query, $pr
 }
 
 /**
- * Implementation of hook_query_alter().
+ * Implementation of hook_query_TAG_alter().
  */
-function node_query_alter(QueryAlterableInterface $query) {
-  if ($query->hasTag('node_access') && !node_access_view_all_nodes()) {
+function node_query_node_access_alter(QueryAlterableInterface $query) {
+  // Skip the extra expensive alterations if site has no node access control modules.
+  if (!node_access_view_all_nodes()) {
+    // Prevent duplicate records.
     $query->distinct();
+    // The recognized operations are 'view', 'update', 'delete'.
     if (!$op = $query->getMetaData('op')) {
       $op = 'view';
     }
+    // Skip the extra joins and conditions for node admins.
     if (!user_access('bypass node access')) {
+      // The node_access table has the access grants for any given node.
       $access_alias = $query->join('node_access', 'na', 'na.nid = n.nid');
       $or = db_or();
+      // If any grant exists for the specified user, then user has access to the node for the specified operation.
       foreach (node_access_grants($op, $query->getMetaData('account')) as $realm => $gids) {
         foreach ($gids as $gid) {
           $or->condition(db_and()
Index: modules/simpletest/tests/database_test.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/simpletest/tests/database_test.module,v
retrieving revision 1.4
diff -u -F^f -p -r1.4 database_test.module
--- modules/simpletest/tests/database_test.module	12 Dec 2008 16:21:58 -0000	1.4
+++ modules/simpletest/tests/database_test.module	2 Jan 2009 03:35:21 -0000
@@ -4,16 +4,12 @@
 /**
  * Implementation of hook_query_alter().
  */
-function database_test_query_alter(SelectQuery $query) {
+function database_test_query_alter(QueryAlterableInterface $query) {
 
   if ($query->hasTag('database_test_alter_add_range')) {
     $query->range(0, 2);
   }
 
-  if ($query->hasTag('database_test_alter_remove_range')) {
-    $query->range();
-  }
-
   if ($query->hasTag('database_test_alter_add_join')) {
     $people_alias = $query->join('test', 'people', "test_task.pid=people.id");
     $name_field = $query->addField('people', 'name', 'name');
@@ -36,6 +32,14 @@ function database_test_query_alter(Selec
   }
 }
 
+
+/**
+ * Implementation of hook_query_TAG_alter(). Called by DatabaseTestCase::testAlterRemoveRange.
+ */
+function database_test_query_database_test_alter_remove_range_alter(QueryAlterableInterface $query) {
+  $query->range();
+}
+
 /**
  * Implementation of hook_menu().
  */
Index: modules/simpletest/tests/database_test.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/simpletest/tests/database_test.test,v
retrieving revision 1.33
diff -u -F^f -p -r1.33 database_test.test
--- modules/simpletest/tests/database_test.test	30 Dec 2008 16:43:18 -0000	1.33
+++ modules/simpletest/tests/database_test.test	2 Jan 2009 03:35:21 -0000
@@ -1695,7 +1695,7 @@ class DatabaseAlter2TestCase extends Dat
   }
 
   /**
-   * Test that we can remove a range() value from a query.
+   * Test that we can remove a range() value from a query. This also tests hook_query_TAG_alter().
    */
   function testAlterRemoveRange() {
     $query = db_select('test');
Index: modules/system/system.api.php
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/system.api.php,v
retrieving revision 1.8
diff -u -F^f -p -r1.8 system.api.php
--- modules/system/system.api.php	31 Dec 2008 11:08:47 -0000	1.8
+++ modules/system/system.api.php	2 Jan 2009 03:35:21 -0000
@@ -1375,6 +1375,68 @@ function hook_schema_alter(&$schema) {
 }
 
 /**
+ * Perform alterations to a structured query.
+ *
+ * Structured (aka dynamic) queries that have tags associated may be altered by any module
+ * before the query is executed.
+ *
+ * @see hook_query_TAG_alter()
+ * @see node_query_node_access_alter()
+ * 
+ * @param $query
+ *   A Query object describing the composite parts of a SQL query.
+ * @return
+ *   None.
+ */
+function hook_query_alter(QueryAlterableInterface $query) {
+  
+}
+
+/**
+ * Perform alterations to a structured query for a given tag.
+ *
+ * @see hook_query_alter()
+ * @see node_query_node_access_alter()
+ * 
+ * @param $query
+ *   An Query object describing the composite parts of a SQL query.
+ * @return
+ *   None.
+ */
+function hook_query_TAG_alter(QueryAlterableInterface $query) {
+  // Skip the extra expensive alterations if site has no node access control modules.
+  if (!node_access_view_all_nodes()) {
+    // Prevent duplicates records.
+    $query->distinct();
+    // The recognized operations are 'view', 'update', 'delete'.
+    if (!$op = $query->getMetaData('op')) {
+      $op = 'view';
+    }
+    // Skip the extra joins and conditions for node admins.
+    if (!user_access('bypass node access')) {
+      // The node_access table has the access grants for any given node.
+      $access_alias = $query->join('node_access', 'na', 'na.nid = n.nid');
+      $or = db_or();
+      // If any grant exists for the specified user, then user has access to the node for the specified operation.
+      foreach (node_access_grants($op, $query->getMetaData('account')) as $realm => $gids) {
+        foreach ($gids as $gid) {
+          $or->condition(db_and()
+            ->condition("{$access_alias}.gid", $gid)
+            ->condition("{$access_alias}.realm", $realm)
+          );
+        }
+      }
+
+      if (count($or->conditions())) {
+        $query->condition($or);
+      }
+      
+      $query->condition("{$access_alias}.grant_$op", 1, '>=');
+    }
+  }
+}
+
+/**
  * Install the current version of the database schema, and any other setup tasks.
  *
  * The hook will be called the first time a module is installed, and the
