? : ? adaptive_path_4.patch ? adaptive_path_with_tables.patch ? pdo-225450-264.patch ? sites/default/files ? sites/default/settings.php Index: includes/common.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/common.inc,v retrieving revision 1.779 diff -u -p -r1.779 common.inc --- includes/common.inc 19 Jul 2008 10:38:13 -0000 1.779 +++ includes/common.inc 1 Aug 2008 11:08:27 -0000 @@ -1484,6 +1484,7 @@ function drupal_page_footer() { registry_cache_hook_implementations(FALSE, TRUE); registry_cache_path_files(); + drupal_lookup_path('footer'); } /** Index: includes/path.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/path.inc,v retrieving revision 1.24 diff -u -p -r1.24 path.inc --- includes/path.inc 24 Jun 2008 22:12:15 -0000 1.24 +++ includes/path.inc 1 Aug 2008 11:08:28 -0000 @@ -11,14 +11,29 @@ */ /** + * Do not cache anything. + */ +define('PATH_CACHE_NONE', 0); + +/** + * Cache everything. + */ +define('PATH_CACHE_ALL', 1); + +/** + * Cache the aliases on page that seem unchanging. + */ +define('PATH_CACHE_ADAPTIVE', 2); + +/** * Initialize the $_GET['q'] variable to the proper normal path. */ function drupal_init_path() { if (!empty($_GET['q'])) { - $_GET['q'] = drupal_get_normal_path(trim($_GET['q'], '/')); + $_GET['q'] = drupal_get_normal_path(trim($_GET['q'], '/'), '', TRUE); } else { - $_GET['q'] = drupal_get_normal_path(variable_get('site_frontpage', 'node')); + $_GET['q'] = drupal_get_normal_path(variable_get('site_frontpage', 'node'), '', TRUE); } } @@ -38,58 +53,134 @@ function drupal_init_path() { * Optional language code to search the path with. Defaults to the page language. * If there's no path defined for that language it will search paths without * language. + * @param $first_call + * TRUE to postpone adaptive cache initialization. Internal use only. * * @return * Either a Drupal system path, an aliased path, or FALSE if no path was * found. */ -function drupal_lookup_path($action, $path = '', $path_language = '') { +function drupal_lookup_path($action, $path = '', $path_language = '', $first_call = FALSE) { global $language; // $map is an array with language keys, holding arrays of Drupal paths to alias relations static $map = array(), $no_src = array(), $count; - $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_result(db_query('SELECT COUNT(pid) FROM {url_alias}')); } + if (!$count) { + return FALSE; + } + + $path_language = $path_language ? $path_language : $language->language; + $strategy = variable_get('path_strategy', PATH_CACHE_NONE); + + if ($action == 'footer' && $strategy == PATH_CACHE_ADAPTIVE) { + foreach ($map as $path_language => $entry) { + foreach ($entry as $src => $dst) { + // The new database layer will make this PostgreSQL and whatever + // compatible. Let's focus on functionality for now. + db_query("INSERT DELAYED INTO {path_alias_statistics_map} (page_path, language, src, dst) VALUES ('%s', '%s', '%s', '%s')", $_GET['q'], $path_language, $src, $dst); + } + } + return; + } + if ($strategy == PATH_CACHE_ALL) { + return drupal_lookup_path_cached($action, $path, $path_language); + } + if (!$first_call && $strategy == PATH_CACHE_ADAPTIVE) { + $cache = cache_get($_GET['q'] .':'. $path_language, 'cache_path'); + if ($cache) { + $map[$path_language] = $cache->data; + } + else { + $map[$path_language] = array(); + $result = db_query("SELECT src, dst FROM {path_alias_map} WHERE page_path = '%s' AND language = '%s'", $_GET['q'], $path_language); + while ($pair = db_fetch_array($result)) { + $map[$path_language][$pair['src']] = $pair['dst']; + } + cache_set($_GET['q'] .':'. $path_language, $map[$path_language], 'cache_path'); + } + } if ($action == 'wipe') { $map = array(); $no_src = array(); - } - elseif ($count > 0 && $path != '') { - if ($action == 'alias') { - if (isset($map[$path_language][$path])) { - return $map[$path_language][$path]; - } - // Get the most fitting result falling back with alias without language - $alias = db_result(db_query("SELECT dst FROM {url_alias} WHERE src = '%s' AND language IN('%s', '') ORDER BY language DESC", $path, $path_language)); - $map[$path_language][$path] = $alias; - return $alias; - } - // Check $no_src for this $path in case we've already determined that there - // isn't a path that has this alias - elseif ($action == 'source' && !isset($no_src[$path_language][$path])) { - // Look for the value $path within the cached $map - $src = ''; - if (!isset($map[$path_language]) || !($src = array_search($path, $map[$path_language]))) { - // Get the most fitting result falling back with alias without language - if ($src = db_result(db_query("SELECT src FROM {url_alias} WHERE dst = '%s' AND language IN('%s', '') ORDER BY language DESC", $path, $path_language))) { - $map[$path_language][$src] = $path; + // @TODO: This is less than optimal: the path cache needs only be wiped + // if an alias changed (and then only for a specific language), but not + // upon creation or deletion. + cache_clear_all('*', 'cache_path', TRUE); + db_query('DELETE FROM {path_alias_map}'); + db_query('DELETE FROM {path_alias_statistics_map}'); + return FALSE; + } + elseif ($path != '') { + switch ($action) { + case 'alias': + if (isset($map[$path_language][$path])) { + return $map[$path_language][$path]; } - else { - // We can't record anything into $map because we do not have a valid - // index and there is no need because we have not learned anything - // about any Drupal path. Thus cache to $no_src. - $no_src[$path_language][$path] = TRUE; + // Get the most fitting result falling back with alias without language + $alias = db_result(db_query("SELECT dst FROM {url_alias} WHERE src = '%s' AND language IN ('%s', '') ORDER BY language DESC", $path, $path_language)); + $map[$path_language][$path] = $alias; + return $alias; + case 'source': + // Check $no_src for this $path in case we've already determined that there + // isn't a path that has this alias + if (!isset($no_src[$path_language][$path])) { + // Look for the value $path within the cached $map + $src = ''; + if (!isset($map[$path_language]) || !($src = array_search($path, $map[$path_language]))) { + // Get the most fitting result falling back with alias without language + if ($src = db_result(db_query("SELECT src FROM {url_alias} WHERE dst = '%s' AND language IN ('%s', '') ORDER BY language DESC", $path, $path_language))) { + $map[$path_language][$src] = $path; + } + else { + // We can't record anything into $map because we do not have a valid + // index and there is no need because we have not learned anything + // about any Drupal path. Thus cache to $no_src. + $no_src[$path_language][$path] = TRUE; + } + } + return $src; } + break; + } + } + return FALSE; +} + +function drupal_lookup_path_cached($action, $path, $path_language) { + static $map = array(); + if ($action == 'wipe') { + // @TODO: see drupal_lookup_path wipe. + $map = array(); + cache_clear_all('*', 'cache_path', TRUE); + return FALSE; + } + if (empty($map)) { + $cache = cache_get('path', 'cache_path'); + if ($cache) { + $map = $cache->data; + } + else { + $map = array(); + $result = db_query("SELECT src, dst, language FROM {url_alias}"); + while ($url_alias = db_fetch_object($result)) { + $map[$url_alias->language][$url_alias->src] = $url_alias->dst; } + cache_set('path', $map, 'cache_path'); + } + } + foreach (array($path_language, '') as $language) { + if ($action == 'alias' && isset($map[$language][$path])) { + return $map[$language][$path]; + } + if ($action == 'source' && isset($map[$language]) && ($src = array_search($path, $map[$language]))) { return $src; } } - return FALSE; } @@ -120,14 +211,16 @@ function drupal_get_path_alias($path, $p * A Drupal path alias. * @param $path_language * An optional language code to look up the path in. + * @param $first_call + * TRUE to postpone adaptive cache initialization. Internal use only. * * @return * The internal path represented by the alias, or the original alias if no * internal path was found. */ -function drupal_get_normal_path($path, $path_language = '') { +function drupal_get_normal_path($path, $path_language = '', $first_call = FALSE) { $result = $path; - if ($src = drupal_lookup_path('source', $path, $path_language)) { + if ($src = drupal_lookup_path('source', $path, $path_language, $first_call)) { $result = $src; } if (function_exists('custom_url_rewrite_inbound')) { Index: modules/path/path.admin.inc =================================================================== RCS file: /cvs/drupal/drupal/modules/path/path.admin.inc,v retrieving revision 1.8 diff -u -p -r1.8 path.admin.inc --- modules/path/path.admin.inc 14 Apr 2008 17:48:38 -0000 1.8 +++ modules/path/path.admin.inc 1 Aug 2008 11:08:28 -0000 @@ -239,3 +239,22 @@ function path_admin_filter_get_keys() { $path = explode('/', $_GET['q'], 5); return count($path) == 5 ? $path[4] : ''; } + +function path_admin_settings() { + global $db_type; + + $options = array( + PATH_CACHE_NONE => t('Do not cache anything'), + PATH_CACHE_ALL => t('Cache all aliases (not recommended for a big number of aliases)'), + ); + if (strpos($db_type, 'mysql') !== FALSE) { + $options[PATH_CACHE_ADAPTIVE] = t('Cache the aliases on page that seem unchanging.'); + } + $form['path_strategy'] = array( + '#type' => 'radios', + '#title' => 'Caching', + '#options' => $options, + '#default_value' => variable_get('path_strategy', PATH_CACHE_NONE), + ); + return system_settings_form($form); +} Index: modules/path/path.module =================================================================== RCS file: /cvs/drupal/drupal/modules/path/path.module,v retrieving revision 1.144 diff -u -p -r1.144 path.module --- modules/path/path.module 21 Jun 2008 18:22:38 -0000 1.144 +++ modules/path/path.module 1 Aug 2008 11:08:28 -0000 @@ -63,6 +63,13 @@ function path_menu() { 'access arguments' => array('administer url aliases'), 'type' => MENU_LOCAL_TASK, ); + $items['admin/build/path/settings'] = array( + 'title' => 'URL alias settings', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('path_admin_settings'), + 'access arguments' => array('administer url aliases'), + 'type' => MENU_LOCAL_TASK, + ); return $items; } @@ -215,3 +222,11 @@ function path_perm() { function path_load($pid) { return db_fetch_array(db_query('SELECT * FROM {url_alias} WHERE pid = %d', $pid)); } + +function path_cron() { + db_query_temporary('SELECT page_path, language, %f * MAX(c) AS cutoff FROM (SELECT page_path, language, COUNT(*) AS c FROM {path_alias_statistics_map} GROUP BY page_path, language, src, dst) AS x GROUP BY page_path, language', variable_get('path_cutoff', .6), 'page_cutoff'); + db_query("ALTER TABLE page_cutoff ADD KEY path_language (page_path, language)"); + db_query('DELETE FROM {path_alias_map}'); + db_query('INSERT INTO {path_alias_map} (page_path, language, src, dst) SELECT page_path, language, src, dst FROM {path_alias_statistics_map} p GROUP BY page_path, language, src, dst HAVING COUNT(*) > (SELECT cutoff FROM page_cutoff pc WHERE pc.page_path = p.page_path AND pc.language = p.language)'); + db_query('DELETE FROM {path_alias_statistics_map}'); +} \ No newline at end of file Index: modules/path/path.test =================================================================== RCS file: /cvs/drupal/drupal/modules/path/path.test,v retrieving revision 1.2 diff -u -p -r1.2 path.test --- modules/path/path.test 25 Apr 2008 18:26:02 -0000 1.2 +++ modules/path/path.test 1 Aug 2008 11:08:28 -0000 @@ -2,14 +2,6 @@ // $Id: path.test,v 1.2 2008/04/25 18:26:02 dries Exp $ class PathTestCase extends DrupalWebTestCase { - function getInfo() { - return array( - 'name' => t('Path alias functionality'), - 'description' => t('Add, edit, delete, and change alias and verify its consistency in the database.'), - 'group' => t('Path'), - ); - } - /** * Create user, setup permissions, log user in, and create a node. */ @@ -21,9 +13,9 @@ class PathTestCase extends DrupalWebTest } /** - * Test alias functionality through the admin interfaces. + * Test alias functionality through the admin interfaces for a path caching type. */ - function testAdminAlias() { + function checkAdminAlias($type) { // create test node $node1 = $this->createNode(); @@ -35,7 +27,7 @@ class PathTestCase extends DrupalWebTest // Confirm that the alias works. $this->drupalGet($edit['dst']); - $this->assertText($node1->title, 'Alias works.'); + $this->assertText($node1->title, t('@type: Alias works.', array('@type' => $type))); // Change alias. $pid = $this->getPID($edit['dst']); @@ -46,11 +38,11 @@ class PathTestCase extends DrupalWebTest // Confirm that the alias works. $this->drupalGet($edit['dst']); - $this->assertText($node1->title, 'Changed alias works.'); + $this->assertText($node1->title, t('@type: Changed alias works.', array('@type' => $type))); // Confirm that previous alias no longer works. $this->drupalGet($previous); - $this->assertNoText($node1->title, 'Previous alias no longer works.'); + $this->assertNoText($node1->title, t('@type: Previous alias no longer works.', array('@type' => $type))); $this->assertResponse(404); // Create second test node. @@ -69,13 +61,13 @@ class PathTestCase extends DrupalWebTest // Confirm that the alias no longer works. $this->drupalGet($edit['dst']); - $this->assertNoText($node1->title, 'Alias was successfully deleted.'); + $this->assertNoText($node1->title, t('@type: Alias was successfully deleted.', array('@type' => $type))); } /** * Test alias functionality through the node interfaces. */ - function testNodeAlias() { + function checkNodeAlias($type) { // Create test node. $node1 = $this->createNode(); @@ -86,7 +78,7 @@ class PathTestCase extends DrupalWebTest // Confirm that the alias works. $this->drupalGet($edit['path']); - $this->assertText($node1->title, 'Alias works.'); + $this->assertText($node1->title, t('@type: Alias works.', array('@type' => $type))); // Change alias. $previous = $edit['path']; @@ -95,11 +87,11 @@ class PathTestCase extends DrupalWebTest // Confirm that the alias works. $this->drupalGet($edit['path']); - $this->assertText($node1->title, 'Changed alias works.'); + $this->assertText($node1->title, t('@type: Changed alias works.', array('@type' => $type))); // Make sure that previous alias no longer works. $this->drupalGet($previous); - $this->assertNoText($node1->title, 'Previous alias no longer works.'); + $this->assertNoText($node1->title, t('@type: Previous alias no longer works.', array('@type' => $type))); $this->assertResponse(404); // Create second test node. @@ -110,14 +102,14 @@ class PathTestCase extends DrupalWebTest $this->drupalPost('node/' . $node2->nid . '/edit', $edit, t('Save')); // Confirm that the alias didn't make a duplicate. - $this->assertText(t('The path is already in use.'), 'Attempt to moved alias was rejected.'); + $this->assertText(t('The path is already in use.'), t('@type: Attempt to moved alias was rejected.', array('@type' => $type))); // Delete alias. $this->drupalPost('node/' . $node1->nid . '/edit', array('path' => ''), t('Save')); // Confirm that the alias no longer works. $this->drupalGet($edit['path']); - $this->assertNoText($node1->title, 'Alias was successfully deleted.'); + $this->assertNoText($node1->title, t('@type: Alias was successfully deleted.', array('@type' => $type))); } function getPID($dst) { @@ -137,3 +129,87 @@ class PathTestCase extends DrupalWebTest return $node; } } + +class PathNoneTestCase extends PathTestCase { + function getInfo() { + return array( + 'name' => t('Path no cache'), + 'description' => t('Add, edit, delete, and change alias and verify its consistency in the database without path caching.'), + 'group' => t('Path'), + ); + } + + /** + * Test alias functionality through the admin interfaces for PATH_CACHE_NONE. + */ + function testAdminAliasCacheNone() { + variable_set('path_strategy', PATH_CACHE_NONE); + $this->checkAdminAlias('Path Cache None'); + } + + /** + * Test alias functionality through the node interfaces for PATH_CACHE_NONE. + */ + function testNodeAliasCacheNone() { + variable_set('path_strategy', PATH_CACHE_NONE); + $this->checkNodeAlias('Path Cache None'); + } +} + +class PathAllTestCase extends PathTestCase { + function getInfo() { + return array( + 'name' => t('Path (all cache)'), + 'description' => t('Add, edit, delete, and change alias and verify its consistency in the database with every alias cached.'), + 'group' => t('Path'), + ); + } + + /** + * Test alias functionality through the admin interfaces for PATH_CACHE_ALL. + */ + function testAdminAliasCacheAll() { + variable_set('path_strategy', PATH_CACHE_ALL); + $this->checkAdminAlias('Path Cache All'); + } + + /** + * Test alias functionality through the node interfaces for PATH_CACHE_ALL. + */ + function testNodeAliasCacheAll() { + variable_set('path_strategy', PATH_CACHE_ALL); + $this->checkNodeAlias('Path Cache All'); + } +} + +class PathAdaptiveTestCase extends PathTestCase { + function getInfo() { + return array( + 'name' => t('Path adaptive cache'), + 'description' => t('Add, edit, delete, and change alias and verify its consistency in the database with an adaptive alias strategy.'), + 'group' => t('Path'), + ); + } + + /** + * Test alias functionality through the admin interfaces for PATH_CACHE_ADAPTIVE. + */ + function testAdminAliasCacheAdaptive() { + global $db_type; + if (strpos($db_type, 'mysql') !== FALSE) { + variable_set('path_strategy', PATH_CACHE_ADAPTIVE); + $this->checkAdminAlias('Path Cache Adaptive'); + } + } + + /** + * Test alias functionality through the node interfaces for PATH_CACHE_ADAPTIVE. + */ + function testNodeAliasCacheAdaptive() { + global $db_type; + if (strpos($db_type, 'mysql') !== FALSE) { + variable_set('path_strategy', PATH_CACHE_ADAPTIVE); + $this->checkNodeAlias('Path Cache Adaptive'); + } + } +} \ No newline at end of file Index: modules/system/system.install =================================================================== RCS file: /cvs/drupal/drupal/modules/system/system.install,v retrieving revision 1.256 diff -u -p -r1.256 system.install --- modules/system/system.install 1 Jul 2008 20:36:40 -0000 1.256 +++ modules/system/system.install 1 Aug 2008 11:08:31 -0000 @@ -606,10 +606,12 @@ function system_schema() { $schema['cache_form'] = $schema['cache']; $schema['cache_form']['description'] = t('Cache table for the form system to store recently built forms and their storage data, to be used in subsequent page requests.'); - $schema['cache_page'] = $schema['cache']; - $schema['cache_page']['description'] = t('Cache table used to store compressed pages for anonymous users, if page caching is enabled.'); $schema['cache_menu'] = $schema['cache']; $schema['cache_menu']['description'] = t('Cache table for the menu system to store router information as well as generated link trees for various menu/page/user combinations.'); + $schema['cache_page'] = $schema['cache']; + $schema['cache_page']['description'] = t('Cache table used to store compressed pages for anonymous users, if page caching is enabled.'); + $schema['cache_path'] = $schema['cache']; + $schema['cache_path']['description'] = t('Cache table used to store path aliases.'); $schema['cache_registry'] = $schema['cache']; $schema['cache_registry']['description'] = t('Cache table for the code registry system to remember what code files need to be loaded on any given page.'); @@ -3030,6 +3032,28 @@ function system_update_7009() { } /** + * Create the cache_path table. + */ +function system_update_7010() { + $ret = array(); + $schema['cache_path'] = array( + 'fields' => array( + 'cid' => array('type' => 'varchar', 'length' => 255, 'not null' => TRUE, 'default' => ''), + 'data' => array('type' => 'blob', 'not null' => FALSE, 'size' => 'big'), + 'expire' => array('type' => 'int', 'not null' => TRUE, 'default' => 0), + 'created' => array('type' => 'int', 'not null' => TRUE, 'default' => 0), + 'headers' => array('type' => 'text', 'not null' => FALSE), + 'serialized' => array('type' => 'int', 'size' => 'small', 'not null' => TRUE, 'default' => 0) + ), + 'indexes' => array('expire' => array('expire')), + 'primary key' => array('cid'), + ); + db_create_table($ret, 'cache_path', $schema['cache_path']); + return $ret; +} + + +/** * @} End of "defgroup updates-6.x-to-7.x" * The next series of updates should start at 8000. */