Alternative approach for #106559.

From: damz <damz@damz-dev.local>


---
 database/sqlite/database.inc |   15 ++++++++++++++
 database/sqlite/schema.inc   |   11 ++++++++---
 path.inc                     |   24 ++++++++++++++++++-----
 path/path.module             |    4 ++++
 system/system.install        |   44 ++++++++++++++++++++++++++++++++++++++++++
 5 files changed, 90 insertions(+), 8 deletions(-)

diff --git includes/database/sqlite/database.inc includes/database/sqlite/database.inc
index 6d8166d..ebbe01e 100644
--- includes/database/sqlite/database.inc
+++ includes/database/sqlite/database.inc
@@ -39,6 +39,7 @@ class DatabaseConnection_sqlite extends DatabaseConnection {
     $this->sqliteCreateFunction('length', 'strlen', 1);
     $this->sqliteCreateFunction('concat', array($this, 'sqlFunctionConcat'));
     $this->sqliteCreateFunction('substring', array($this, 'sqlFunctionSubstring'), 3);
+    $this->sqliteCreateFunction('substring_index', array($this, 'sqlFunctionSubstringIndex'), 3);
     $this->sqliteCreateFunction('rand', array($this, 'sqlFunctionRand'));
   }
 
@@ -83,6 +84,20 @@ class DatabaseConnection_sqlite extends DatabaseConnection {
   }
 
   /**
+   * SQLite compatibility implementation for the SUBSTRING_INDEX() SQL function.
+   */
+  public function sqlFunctionSubstringIndex($string, $delimiter, $count) {
+    $end = 0;
+    for ($i = 0; $i < $count; $i++) {
+      $end = strpos($string, $delimiter, $end + 1);
+      if ($end === FALSE) {
+        $end = strlen($string);
+      }
+    }
+    return substr($string, 0, $end);
+  }
+
+  /**
    * SQLite compatibility implementation for the RAND() SQL function.
    */
   public function sqlFunctionRand($seed = NULL) {
diff --git includes/database/sqlite/schema.inc includes/database/sqlite/schema.inc
index 3a4bac0..565a9cc 100644
--- includes/database/sqlite/schema.inc
+++ includes/database/sqlite/schema.inc
@@ -430,7 +430,10 @@ class DatabaseSchema_sqlite extends DatabaseSchema {
    */
   public function addIndex(&$ret, $table, $name, $fields) {
     $schema['indexes'][$name] = $fields;
-    $ret[] = update_sql($this->createIndexSql($table, $schema));
+    $statements = $this->createIndexSql($table, $schema);
+    foreach ($statements as $statement) {
+      $ret[] = update_sql($statement);
+    }
   }
 
   /**
@@ -461,8 +464,10 @@ class DatabaseSchema_sqlite extends DatabaseSchema {
    */
   public function addUniqueKey(&$ret, $table, $name, $fields) {
     $schema['unique keys'][$name] = $fields;
-    $ret[] = update_sql($this->createIndexSql($table, $schema));
-
+    $statements = $this->createIndexSql($table, $schema);
+    foreach ($statements as $statement) {
+      $ret[] = update_sql($statement);
+    }
   }
 
   /**
diff --git includes/path.inc includes/path.inc
index 017100e..c4c825d 100644
--- includes/path.inc
+++ includes/path.inc
@@ -48,7 +48,7 @@ function drupal_lookup_path($action, $path = '', $path_language = '') {
   // $map is an array with language keys, holding arrays of Drupal paths to alias relations
   $map = &drupal_static(__FUNCTION__, array());
   $no_src = &drupal_static(__FUNCTION__ . ':no_src', array());
-  $count = &drupal_static(__FUNCTION__ . ':count');
+  $prefixes = &drupal_static(__FUNCTION__ . ':prefixes');
   $system_paths = &drupal_static(__FUNCTION__ . ':system_paths');
   $no_aliases = &drupal_static(__FUNCTION__ . ':no_alias', array());
   $first_call = &drupal_static(__FUNCTION__ . ':first_call', TRUE);
@@ -56,18 +56,26 @@ function drupal_lookup_path($action, $path = '', $path_language = '') {
   $path_language = $path_language ? $path_language : $language->language;
 
   // Use $count to avoid looking up paths in subsequent calls if there simply are no aliases
-  if (!isset($count)) {
-    $count = db_query('SELECT COUNT(pid) FROM {url_alias}')->fetchField();
+  if (!isset($prefixes)) {
+    try {
+      $prefixes = db_query('SELECT DISTINCT prefix FROM {url_alias}')->fetchAllKeyed(0, 0);
+    }
+    catch (Exception $e) {
+      $prefix = array();
+    }
   }
 
   if ($action == 'wipe') {
     $map = array();
     $no_src = array();
-    $count = NULL;
+    $prefixes = NULL;
     $system_paths = array();
     $no_aliases = array();
   }
-  elseif ($count > 0 && $path != '') {
+  elseif (!empty($prefixes) && $path != '') {
+    // Derive the top level component of the path.
+    $prefix = strtok($path, '/');
+
     if ($action == 'alias') {
       // During the first call to drupal_lookup_path() per language, load the
       // expected system paths for the page from cache.
@@ -93,6 +101,12 @@ function drupal_lookup_path($action, $path = '', $path_language = '') {
       if (isset($map[$path_language][$path])) {
         return $map[$path_language][$path];
       }
+      else if (!isset($prefixes[$prefix])) {
+        // If the top_level part before the first / is not in the prefix list,
+        // then there is no need to do anything further, it is not in the
+        // database.
+        return FALSE;
+      }
       // For system paths which were not cached, query aliases individually.
       else if (!isset($no_aliases[$path_language][$path])) {
         // Get the most fitting result falling back with alias without language
diff --git modules/path/path.module modules/path/path.module
index f0729d9..5ae725f 100644
--- modules/path/path.module
+++ modules/path/path.module
@@ -83,6 +83,7 @@ function path_admin_delete($pid = 0) {
 function path_set_alias($path = NULL, $alias = NULL, $pid = NULL, $language = '') {
   $path = urldecode($path);
   $alias = urldecode($alias);
+  $prefix = strtok($path, '/');
   // First we check if we deal with an existing alias and delete or modify it based on pid.
   if ($pid) {
     // An existing alias.
@@ -98,6 +99,7 @@ function path_set_alias($path = NULL, $alias = NULL, $pid = NULL, $language = ''
         ->fields(array(
           'src'      => $path,
           'dst'      => $alias,
+          'prefix'   => $prefix,
           'language' => $language))
         ->condition('pid', $pid)
         ->execute();
@@ -112,6 +114,7 @@ function path_set_alias($path = NULL, $alias = NULL, $pid = NULL, $language = ''
         ->fields(array(
           'src' => $path,
           'dst' => $alias,
+          'prefix' => $prefix,
           'language' => $language
         ))
         ->condition('dst', $alias)
@@ -123,6 +126,7 @@ function path_set_alias($path = NULL, $alias = NULL, $pid = NULL, $language = ''
         ->fields(array(
           'src'     => $path,
           'dst'      => $alias,
+          'prefix' => $prefix,
           'language' => $language,
         ))
         ->execute();
diff --git modules/system/system.install modules/system/system.install
index f0505aa..2eb73a8 100644
--- modules/system/system.install
+++ modules/system/system.install
@@ -339,6 +339,11 @@ function system_install() {
       \'SELECT CASE WHEN $1 THEN $2 ELSE $3 END;\'
       LANGUAGE \'sql\''
     );
+
+    db_query('CREATE OR REPLACE FUNCTION "substr_index"(text, text, integer) RETURNS text AS
+      \'SELECT array_to_string((string_to_array($1, $2)) [1:$3], $2);\'
+      LANGUAGE \'sql\''
+    );
   }
 
   // Create tables.
@@ -1320,6 +1325,13 @@ function system_schema() {
         'not null' => TRUE,
         'default' => '',
       ),
+      'prefix' => array(
+        'description' => 'The top level Drupal path of this alias; e.g. node.',
+        'type' => 'varchar',
+        'length' => 32,
+        'not null' => TRUE,
+        'default' => '',
+      ),
       'language' => array(
         'description' => 'The language this alias is for; if blank, the alias will be used for unknown languages. Each Drupal path can have an alias for each supported language.',
         'type' => 'varchar',
@@ -1334,6 +1346,7 @@ function system_schema() {
     'primary key' => array('pid'),
     'indexes' => array(
       'src_language' => array('src', 'language'),
+      'prefix' => array('prefix'),
     ),
   );
 
@@ -3496,6 +3509,37 @@ function system_update_7023() {
 }
 
 /**
+ * Generate the URL alias prefix list.
+ */
+function system_update_7024() {
+  $ret = array();
+
+  // Add the alternative implementation of substring_index() for PostgreSQL.
+  // Note: this should go into the driver itself, but we have no support
+  // for driver-specific update yet.
+  if (db_driver() == 'pgsql') {
+    db_query('CREATE OR REPLACE FUNCTION "substr_index"(text, text, integer) RETURNS text AS
+      \'SELECT array_to_string((string_to_array($1, $2)) [1:$3], $2);\'
+      LANGUAGE \'sql\''
+    );
+  }
+
+  db_add_field($ret, 'url_alias', 'prefix', array(
+    'description' => 'The top level Drupal path of this alias; e.g. node.',
+    'type' => 'varchar',
+    'length' => 32,
+    'not null' => TRUE,
+    'default' => '',
+  ));
+  db_add_index($ret, 'url_alias', 'prefix', array('prefix'));
+
+  // For each alias in the database, get the top level component. This is the portion of the 
+  // alias before the first /, if present, otherwise, the whole alias itself. 
+  $ret[] = update_sql("UPDATE {url_alias} SET prefix = SUBSTRING_INDEX(src, '/', 1)");
+  return $ret;
+}
+
+/**
  * @} End of "defgroup updates-6.x-to-7.x"
  * The next series of updates should start at 8000.
  */
