? views_caching.patch Index: views.install =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/views/views.install,v retrieving revision 1.46 diff -u -p -r1.46 views.install --- views.install 7 Apr 2009 20:39:51 -0000 1.46 +++ views.install 21 May 2009 05:11:09 -0000 @@ -126,6 +126,11 @@ function views_schema_1() { ); $schema['cache_views'] = drupal_get_schema_unprocessed('system', 'cache'); + + $schema['cache_views_data'] = drupal_get_schema_unprocessed('system', 'cache'); + $schema['cache_views_data']['description'] = 'Cache table for views to store pre-rendered queries, results, and display output.'; + $schema['cache_views_data']['fields']['serialized']['default'] = 1; + $schema['views_object_cache'] = array( 'description' => 'A special cache used to store objects that are being edited; it serves to save state in an ordinarily stateless environment.', @@ -239,3 +244,18 @@ function views_update_6004() { return $ret; } + +/** + * Add the cache_views_data table to support standard caching. + */ +function views_update_6005() { + $ret = array(); + + $table = drupal_get_schema_unprocessed('system', 'cache'); + $table['description'] = 'Cache table for views to store pre-rendered queries, results, and display output.'; + $table['fields']['serialized']['default'] = 1; + + db_create_table($ret, 'cache_views_data', $table); + + return $ret; +} Index: includes/plugins.inc =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/views/includes/plugins.inc,v retrieving revision 1.152 diff -u -p -r1.152 plugins.inc --- includes/plugins.inc 7 Jan 2009 23:31:12 -0000 1.152 +++ includes/plugins.inc 21 May 2009 05:11:09 -0000 @@ -235,6 +235,26 @@ function views_views_plugins() { 'help topic' => 'access-perm', ), ), + 'cache' => array( + 'parent' => array( + 'no ui' => TRUE, + 'handler' => 'views_plugin_cache', + 'parent' => '', + ), + 'none' => array( + 'title' => t('None'), + 'help' => t('No caching of Views data.'), + 'handler' => 'views_plugin_cache_none', + 'help topic' => 'cache-none', + ), + 'time' => array( + 'title' => t('Time-based'), + 'help' => t('Simple time-based caching of data.'), + 'handler' => 'views_plugin_cache_time', + 'uses options' => TRUE, + 'help topic' => 'cache-time', + ), + ), ); } @@ -244,7 +264,7 @@ function views_views_plugins() { * @return Nested array of plugins, grouped by type. */ function views_discover_plugins() { - $cache = array('display' => array(), 'style' => array(), 'row' => array(), 'argument default' => array(), 'argument validator' => array(), 'access' => array()); + $cache = array('display' => array(), 'style' => array(), 'row' => array(), 'argument default' => array(), 'argument validator' => array(), 'access' => array(), 'cache' => array()); // Get plugins from all mdoules. foreach (module_implements('views_plugins') as $module) { $function = $module . '_views_plugins'; Index: includes/view.inc =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/views/includes/view.inc,v retrieving revision 1.152 diff -u -p -r1.152 view.inc --- includes/view.inc 20 May 2009 02:53:29 -0000 1.152 +++ includes/view.inc 21 May 2009 05:11:09 -0000 @@ -551,87 +551,99 @@ class view extends views_db_object { // @todo Load a build_info from cache. $start = views_microtime(); - // If that fails, let's build! - $this->build_info = array( - 'query' => '', - 'count_query' => '', - 'query_args' => array(), - ); - - $this->init_query(); - - // Call a module hook and see if it wants to present us with a - // pre-built query or instruct us not to build the query for - // some reason. - // @todo: Implement this. Use the same mechanism Panels uses. - - // Run through our handlers and ensure they have necessary information. - $this->init_handlers(); - - // Let the handlers interact with each other if they really want. - $this->_pre_query(); - - if ($this->display_handler->uses_exposed()) { - $this->exposed_widgets = $this->render_exposed_form(); - if (form_set_error() || !empty($this->build_info['abort'])) { - $this->built = TRUE; - return empty($this->build_info['fail']); - } - } - - // Build all the relationships first thing. - $this->_build('relationship'); - - // Build all the filters. - $this->_build('filter'); - - $this->build_sort = TRUE; - - // Arguments can, in fact, cause this whole thing to abort. - if (!$this->_build_arguments()) { - $this->build_time = views_microtime() - $start; - return $this->built; - } + $cache = $this->display_handler->get_cache_plugin(); - // Initialize the style; arguments may have changed which style we use, - // so waiting as long as possible is important. But we need to know - // about the style when we go to build fields. - if (!$this->init_style()) { - $this->build_info['fail'] = TRUE; - return FALSE; - } - - if ($this->style_plugin->uses_fields()) { - $this->_build('field'); + if ($cache && $cached_build = $cache->cache_get('build_info')) { + $this->build_info = $cached_build; } - - // Build our sort criteria if we were instructed to do so. - if (!empty($this->build_sort)) { - // Allow the style handler to deal with sorting. - if ($this->style_plugin->build_sort()) { - $this->_build('sort'); + else { + // If that fails, let's build! + $this->build_info = array( + 'query' => '', + 'count_query' => '', + 'query_args' => array(), + ); + + $this->init_query(); + + // Call a module hook and see if it wants to present us with a + // pre-built query or instruct us not to build the query for + // some reason. + // @todo: Implement this. Use the same mechanism Panels uses. + + // Run through our handlers and ensure they have necessary information. + $this->init_handlers(); + + // Let the handlers interact with each other if they really want. + $this->_pre_query(); + + if ($this->display_handler->uses_exposed()) { + $this->exposed_widgets = $this->render_exposed_form(); + if (form_set_error() || !empty($this->build_info['abort'])) { + $this->built = TRUE; + return empty($this->build_info['fail']); + } + } + + // Build all the relationships first thing. + $this->_build('relationship'); + + // Build all the filters. + $this->_build('filter'); + + $this->build_sort = TRUE; + + // Arguments can, in fact, cause this whole thing to abort. + if (!$this->_build_arguments()) { + $this->build_time = views_microtime() - $start; + return $this->built; + } + + // Initialize the style; arguments may have changed which style we use, + // so waiting as long as possible is important. But we need to know + // about the style when we go to build fields. + if (!$this->init_style()) { + $this->build_info['fail'] = TRUE; + return FALSE; } - } - - // Allow display handler to affect the query: - $this->display_handler->query(); - - // Allow style handler to affect the query: - $this->style_plugin->query(); + + if ($this->style_plugin->uses_fields()) { + $this->_build('field'); + } + + // Build our sort criteria if we were instructed to do so. + if (!empty($this->build_sort)) { + // Allow the style handler to deal with sorting. + if ($this->style_plugin->build_sort()) { + $this->_build('sort'); + } + } + + // Allow display handler to affect the query: + $this->display_handler->query(); + + // Allow style handler to affect the query: + $this->style_plugin->query(); + + if (variable_get('views_sql_signature', FALSE)) { + $this->query->add_field(NULL, "'" . $this->name . ':' . $this->current_display . "'", 'view_name'); + } + + // Let modules modify the query just prior to finalizing it. + foreach (module_implements('views_query_alter') as $module) { + $function = $module . '_views_query_alter'; + $function($this, $this->query); + } + + $this->build_info['query'] = $this->query->query(); + $this->build_info['count_query'] = $this->query->query(TRUE); + $this->build_info['query_args'] = $this->query->get_where_args(); - if (variable_get('views_sql_signature', FALSE)) { - $this->query->add_field(NULL, "'" . $this->name . ':' . $this->current_display . "'", 'view_name'); - } - - // Let modules modify the query just prior to finalizing it. - foreach (module_implements('views_query_alter') as $module) { - $function = $module . '_views_query_alter'; - $function($this, $this->query); + if ($cache) { + $cache->cache_set('build_info'); + } } - $this->build_info['query'] = $this->query->query(); - $this->build_info['count_query'] = $this->query->query(TRUE); - $this->build_info['query_args'] = $this->query->get_where_args(); $this->built = TRUE; $this->build_time = views_microtime() - $start; Index: plugins/views_plugin_cache.inc =================================================================== RCS file: plugins/views_plugin_cache.inc diff -N plugins/views_plugin_cache.inc --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ plugins/views_plugin_cache.inc 21 May 2009 05:11:09 -0000 @@ -0,0 +1,70 @@ +view = &$view; + $this->display = &$display; + $this->options = array(); + + if (is_object($display->handler)) { + // Note: The below is read only. + $this->options = $display->handler->get_option('cache'); + } + } + + /** + * Retrieve the default options when this is a new access + * control plugin + */ + function option_defaults(&$options) { } + + /** + * Provide the default form for setting options. + */ + function options_form(&$form, &$form_state) { } + + /** + * Provide the default form form for validating options + */ + function options_validate(&$form, &$form_state) { } + + /** + * Provide the default form form for submitting options + */ + function options_submit(&$form, &$form_state) { } + + /** + * Return a string to display as the clickable title for the + * access control. + */ + function summary_title() { + return t('Unknown'); + } + + /** + * Save data to the cache. + */ + function cache_set($type, $data = NULL) { } + + + /** + * Retrieve data from the cache. + */ + function cache_get($type) { + return FALSE; + } +} Index: plugins/views_plugin_cache_none.inc =================================================================== RCS file: plugins/views_plugin_cache_none.inc diff -N plugins/views_plugin_cache_none.inc --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ plugins/views_plugin_cache_none.inc 21 May 2009 05:11:09 -0000 @@ -0,0 +1,11 @@ +view = &$view; + $this->display = &$display; + $this->options = array(); + + if (is_object($display->handler)) { + // Note: The below is read only. + $this->options = $display->handler->get_option('cache'); + } + } + + /** + * Retrieve the default options when this is a new access + * control plugin + */ + function option_defaults(&$options) { + $options['lifespan'] = 3600; + } + + /** + * Provide the default form for setting options. + */ + function options_form(&$form, &$form_state) { + $options = array(60, 300, 1800, 3600, 21600, 518400); + $form['lifespan'] = array( + '#type' => 'select', + '#title' => t('Cache lifespan'), + '#description' => t('The length of time cached data should be kept.'), + '#options' => drupal_map_assoc($options, 'format_interval'), + '#default_value' => $this->options['expiration'], + ); + } + + /** + * Provide the default form form for validating options + */ + function options_validate(&$form, &$form_state) { } + + /** + * Provide the default form form for submitting options + */ + function options_submit(&$form, &$form_state) { } + + /** + * Return a string to display as the clickable title for the + * access control. + */ + function summary_title() { + return format_interval($this->options['lifespan'], 1); + } + + /** + * Save data to the cache. + */ + function cache_set($type) { + switch ($type) { + case 'build_info': + cache_set($this->_build_info_key(), $view->build_info, 'cache_views_data'); + break; + case 'results': + break; + case 'output': + break; + } + } + + /** + * Retrieve data from the cache. + */ + function cache_get($type) { + $cutoff = time() - $this->options['lifespan']; + switch ($type) { + case 'build_info': + if ($cache = cache_get($this->_build_info_key(), 'cache_views_data')) { + if ($cache->created < $cutoff) { + return $cache->data; + } + } + break; + case 'results': + return FALSE; + if ($cache = cache_get($this->_results_key(), 'cache_views_data')) { + if ($cache->created < $cutoff) { + return $cache->data; + } + } + break; + case 'output': + return FALSE; + if ($cache = cache_get($this->_output_key(), 'cache_views_data')) { + if ($cache->created < $cutoff) { + return $cache->data; + } + } + break; + } + } + + function _build_info_key() { + $key_data = array( + 'build_info' => $this->view->build_info, + ); + return $this->view->name .':'. $this->display->id .':build:'. md5(serialize($key_data)); + } + + function _results_key() { + $key_data = array(); + return $this->view->name .':'. $this->display->id .':results:'. md5(serialize($key_data)); + } + + function _output_key() { + $key_data = array(); + return $this->view->name .':'. $this->display->id .':output:'. md5(serialize($key_data)); + } +} Index: plugins/views_plugin_display.inc =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/views/plugins/views_plugin_display.inc,v retrieving revision 1.21 diff -u -p -r1.21 views_plugin_display.inc --- plugins/views_plugin_display.inc 20 May 2009 02:53:30 -0000 1.21 +++ plugins/views_plugin_display.inc 21 May 2009 05:11:09 -0000 @@ -148,6 +148,7 @@ class views_plugin_display extends views function defaultable_sections($section = NULL) { $sections = array( 'access' => array('access'), + 'cache' => array('cache'), 'title' => array('title'), 'header' => array('header', 'header_format', 'header_empty'), 'footer' => array('footer', 'footer_format', 'footer_empty'), @@ -212,6 +213,7 @@ class views_plugin_display extends views 'defaults' => array( 'default' => array( 'access' => TRUE, + 'cache' => TRUE, 'title' => TRUE, 'header' => TRUE, 'header_format' => TRUE, @@ -270,6 +272,11 @@ class views_plugin_display extends views 'type' => array('default' => 'none'), ), ), + 'cache' => array( + 'contains' => array( + 'type' => array('default' => 'none'), + ), + ), 'title' => array( 'default' => '', 'translatable' => TRUE, @@ -482,6 +489,22 @@ class views_plugin_display extends views } /** + * Get the cache plugin + */ + function get_cache_plugin($name = NULL) { + if (!$name) { + $cache = $this->get_option('cache'); + $name = $cache['type']; + } + + $plugin = views_get_plugin('cache', $name); + if ($plugin) { + $plugin->init($this->view, $this->display); + return $plugin; + } + } + + /** * Get the handler object for a single handler. */ function &get_handler($type, $id) { @@ -687,6 +710,25 @@ class views_plugin_display extends views $options['access']['links']['access_options'] = t('Change settings for this access type.'); } + $cache_plugin = $this->get_cache_plugin(); + if (!$cache_plugin) { + // default to the no cache control plugin. + $cache_plugin = views_get_plugin('cache', 'none'); + } + + $cache_str = $cache_plugin->summary_title(); + + $options['cache'] = array( + 'category' => 'basic', + 'title' => t('Caching'), + 'value' => $cache_str, + 'desc' => t('Specify caching type for this display.'), + ); + + if (!empty($cache_plugin->definition['uses options'])) { + $options['cache']['links']['cache_options'] = t('Change settings for this caching type.'); + } + if ($this->uses_link_display()) { // Only show the 'link display' if there is more than one option. $count = 0; @@ -893,6 +935,47 @@ class views_plugin_display extends views $plugin->options_form($form['access_options'], $form_state); } break; + case 'cache': + $form['#title'] .= t('Caching'); + $form['cache'] = array( + '#prefix' => '
', + '#suffix' => '
', + '#tree' => TRUE, + ); + + $cache = $this->get_option('cache'); + $form['cache']['type'] = array( + '#type' => 'radios', + '#options' => views_fetch_plugin_names('cache'), + '#default_value' => $cache['type'], + ); + + $cache_plugin = views_fetch_plugin_data('cache', $cache['type']); + if (!empty($cache_plugin['uses options'])) { + $form['markup'] = array( + '#prefix' => '
', + '#suffix' => '
', + '#value' => t('You may also adjust the !settings for the currently selected style by clicking on the icon.', array('!settings' => $this->option_link(t('settings'), 'cache_options'))), + ); + } + break; + case 'cache_options': + $cache = $this->get_option('cache'); + $plugin = $this->get_cache_plugin(); + $form['#title'] .= t('Caching options'); + if ($plugin) { + $form['#help_topic'] = $plugin->definition['help topic']; + + $form['cache_options'] = array( + '#tree' => TRUE, + ); + $form['cache_options']['type'] = array( + '#type' => 'value', + '#value' => $cache['type'], + ); + $plugin->options_form($form['cache_options'], $form_state); + } + break; case 'header': $form['#title'] .= t('Header'); $form['header_empty'] = array( @@ -1269,6 +1352,12 @@ class views_plugin_display extends views $plugin->options_validate($form['access_options'], $form_state); } break; + case 'cache_options': + $plugin = $this->get_cache_plugin(); + if ($plugin) { + $plugin->options_validate($form['cache_options'], $form_state); + } + break; } } @@ -1304,6 +1393,28 @@ class views_plugin_display extends views $this->set_option('access', $form_state['values'][$section]); } break; + case 'cache': + $cache = $this->get_option('cache'); + if ($cache['type'] != $form_state['values']['cache']['type']) { + $plugin = views_get_plugin('cache', $form_state['values']['cache']['type']); + if ($plugin) { + $cache = array('type' => $form_state['values']['cache']['type']); + $plugin->option_defaults($cache); + $this->set_option('cache', $cache); + if (!empty($plugin->definition['uses options'])) { + views_ui_add_form_to_stack('display', $this->view, $this->display->id, array('cache_options')); + } + } + } + + break; + case 'cache_options': + $plugin = views_get_plugin('cache', $form_state['values'][$section]['type']); + if ($plugin) { + $plugin->options_submit($form['cache_options'], $form_state); + $this->set_option('cache', $form_state['values'][$section]); + } + break; case 'title': case 'link_display': $this->set_option($section, $form_state['values'][$section]);