diff --git a/core/includes/database.inc b/core/includes/database.inc index fc76b6f..7f1f278 100644 --- a/core/includes/database.inc +++ b/core/includes/database.inc @@ -34,20 +34,20 @@ * results that need to be presented on multiple pages, and the Tablesort * Extender for generating appropriate queries for sortable tables. * - * For example, one might wish to return a list of the most recent 10 nodes + * For example, one might wish to return a list of the most recent 10 rows * authored by a given user. Instead of directly issuing the SQL query * @code - * SELECT n.nid, n.title, n.created FROM node n WHERE n.uid = $uid LIMIT 0, 10; + * SELECT e.id, e.title, e.created FROM example e WHERE e.uid = $uid LIMIT 0, 10; * @endcode * one would instead call the Drupal functions: * @code - * $result = db_query_range('SELECT n.nid, n.title, n.created - * FROM {node} n WHERE n.uid = :uid', 0, 10, array(':uid' => $uid)); + * $result = db_query_range('SELECT e.id, e.title, e.created + * FROM {example} e WHERE e.uid = :uid', 0, 10, array(':uid' => $uid)); * foreach ($result as $record) { * // Perform operations on $record->title, etc. here. * } * @endcode - * Curly braces are used around "node" to provide table prefixing via + * Curly braces are used around "example" to provide table prefixing via * DatabaseConnection::prefixTables(). The explicit use of a user ID is pulled * out into an argument passed to db_query() so that SQL injection attacks * from user input can be caught and nullified. The LIMIT syntax varies between @@ -69,7 +69,7 @@ * * Named placeholders begin with a colon followed by a unique string. Example: * @code - * SELECT nid, title FROM {node} WHERE uid=:uid; + * SELECT id, title FROM {example} WHERE uid=:uid; * @endcode * * ":uid" is a placeholder that will be replaced with a literal value when @@ -81,7 +81,7 @@ * * Unnamed placeholders are simply a question mark. Example: * @code - * SELECT nid, title FROM {node} WHERE uid=?; + * SELECT id, title FROM {example} WHERE uid=?; * @endcode * * In this case, the array of arguments must be an indexed array of values to @@ -91,11 +91,11 @@ * running a LIKE query the SQL wildcard character, %, should be part of the * value, not the query itself. Thus, the following is incorrect: * @code - * SELECT nid, title FROM {node} WHERE title LIKE :title%; + * SELECT id, title FROM {example} WHERE title LIKE :title%; * @endcode * It should instead read: * @code - * SELECT nid, title FROM {node} WHERE title LIKE :title; + * SELECT id, title FROM {example} WHERE title LIKE :title; * @endcode * and the value for :title should include a % as appropriate. Again, note the * lack of quotation marks around :title. Because the value is not inserted @@ -109,7 +109,7 @@ * object-oriented API for defining a query structurally. For example, rather * than: * @code - * INSERT INTO node (nid, title, body) VALUES (1, 'my title', 'my body'); + * INSERT INTO {example} (id, uid, path, name) VALUES (1, 2, 'home', 'Home path'); * @endcode * one would instead write: * @code diff --git a/core/includes/entity.inc b/core/includes/entity.inc index 48ee728..226a763 100644 --- a/core/includes/entity.inc +++ b/core/includes/entity.inc @@ -78,6 +78,9 @@ function entity_get_info($entity_type = NULL) { // Drupal\Core\Entity\DatabaseStorageControllerInterface::buildQuery(). if (isset($entity_info[$name]['base table'])) { $entity_info[$name]['schema_fields_sql']['base table'] = drupal_schema_fields_sql($entity_info[$name]['base table']); + if (isset($entity_info[$name]['data table'])) { + $entity_info[$name]['schema_fields_sql']['data table'] = drupal_schema_fields_sql($entity_info[$name]['data table']); + } if (isset($entity_info[$name]['revision table'])) { $entity_info[$name]['schema_fields_sql']['revision table'] = drupal_schema_fields_sql($entity_info[$name]['revision table']); } diff --git a/core/includes/form.inc b/core/includes/form.inc index 49ace44..4e58ff6 100644 --- a/core/includes/form.inc +++ b/core/includes/form.inc @@ -4687,26 +4687,25 @@ function _form_set_class(&$element, $class = array()) { * $context['message'] = check_plain($node->label()); * } * - * // More advanced example: multi-step operation - load all nodes, five by five + * // More advanced example: multi-step operation - load all rows, five by five * function my_function_2(&$context) { * if (empty($context['sandbox'])) { * $context['sandbox']['progress'] = 0; - * $context['sandbox']['current_node'] = 0; - * $context['sandbox']['max'] = db_query('SELECT COUNT(DISTINCT nid) FROM {node}')->fetchField(); + * $context['sandbox']['current_id'] = 0; + * $context['sandbox']['max'] = db_query('SELECT COUNT(DISTINCT id) FROM {example}')->fetchField(); * } * $limit = 5; - * $result = db_select('node') - * ->fields('node', array('nid')) - * ->condition('nid', $context['sandbox']['current_node'], '>') - * ->orderBy('nid') + * $result = db_select('example') + * ->fields('example', array('id')) + * ->condition('id', $context['sandbox']['current_id'], '>') + * ->orderBy('id') * ->range(0, $limit) * ->execute(); * foreach ($result as $row) { - * $node = node_load($row->nid, TRUE); - * $context['results'][] = $node->nid . ' : ' . check_plain($node->label()); + * $context['results'][] = $row->id . ' : ' . check_plain($row->title); * $context['sandbox']['progress']++; - * $context['sandbox']['current_node'] = $node->nid; - * $context['message'] = check_plain($node->label()); + * $context['sandbox']['current_id'] = $row->id; + * $context['message'] = check_plain($row->title); * } * if ($context['sandbox']['progress'] != $context['sandbox']['max']) { * $context['finished'] = $context['sandbox']['progress'] / $context['sandbox']['max']; diff --git a/core/includes/menu.inc b/core/includes/menu.inc index 5a5e544..4d53069 100644 --- a/core/includes/menu.inc +++ b/core/includes/menu.inc @@ -1463,10 +1463,10 @@ function menu_tree_collect_node_links(&$tree, &$node_links) { function menu_tree_check_access(&$tree, $node_links = array()) { if ($node_links) { $nids = array_keys($node_links); - $select = db_select('node', 'n'); - $select->addField('n', 'nid'); - $select->condition('n.status', 1); - $select->condition('n.nid', $nids, 'IN'); + $select = db_select('node_property_data', 'npd'); + $select->addField('npd', 'nid'); + $select->condition('npd.status', 1); + $select->condition('npd.nid', $nids, 'IN'); $select->addTag('node_access'); $nids = $select->execute()->fetchCol(); foreach ($nids as $nid) { diff --git a/core/lib/Drupal/Core/Database/Driver/pgsql/Select.php b/core/lib/Drupal/Core/Database/Driver/pgsql/Select.php index c2a5a05..d5509a6 100644 --- a/core/lib/Drupal/Core/Database/Driver/pgsql/Select.php +++ b/core/lib/Drupal/Core/Database/Driver/pgsql/Select.php @@ -32,11 +32,11 @@ public function orderRandom() { * yet selected. * * @code - * $query = db_select('node', 'n'); - * $query->join('node_revision', 'nr', 'n.vid = nr.vid'); + * $query = db_select('example', 'e'); + * $query->join('example_revision', 'er', 'e.vid = er.vid'); * $query * ->distinct() - * ->fields('n') + * ->fields('e') * ->orderBy('timestamp'); * @endcode * diff --git a/core/lib/Drupal/Core/Entity/DatabaseStorageController.php b/core/lib/Drupal/Core/Entity/DatabaseStorageController.php index 6342502..34f5ee8 100644 --- a/core/lib/Drupal/Core/Entity/DatabaseStorageController.php +++ b/core/lib/Drupal/Core/Entity/DatabaseStorageController.php @@ -89,6 +89,13 @@ class DatabaseStorageController implements EntityStorageControllerInterface { protected $revisionKey; /** + * The table that stores properties, if the entity has multilingual support. + * + * @var string + */ + protected $dataTable; + + /** * The table that stores revisions, if the entity supports revisions. * * @var string @@ -124,6 +131,11 @@ public function __construct($entityType) { $this->uuidKey = FALSE; } + // Check if the entity type has a dedicated table for properties. + if (!empty($this->entityInfo['data table'])) { + $this->dataTable = $this->entityInfo['data table']; + } + // Check if the entity type supports revisions. if (!empty($this->entityInfo['entity keys']['revision'])) { $this->revisionKey = $this->entityInfo['entity keys']['revision']; @@ -324,6 +336,10 @@ protected function buildQuery($ids, $revision_id = FALSE) { $query->join($this->revisionTable, 'revision', "revision.{$this->revisionKey} = base.{$this->revisionKey}"); } + if (!empty($this->dataTable)) { + $query->join($this->dataTable, 'data', "data.{$this->idKey} = base.{$this->idKey}"); + } + // Add fields from the {entity} table. $entity_fields = $this->entityInfo['schema_fields_sql']['base table']; @@ -348,6 +364,22 @@ protected function buildQuery($ids, $revision_id = FALSE) { $query->addExpression('base.' . $this->revisionKey . ' = revision.' . $this->revisionKey, 'isDefaultRevision'); } + // Add fields from the entity data table. + if (!empty($this->dataTable)) { + // Add all fields from the {entity_data} table. + $entity_data_fields = drupal_map_assoc($this->entityInfo['schema_fields_sql']['data table']); + // The id field is provided by entity, so remove it. + unset($entity_data_fields[$this->idKey]); + + if (!isset($entity_revision_fields)) { + $entity_revision_fields = array(); + } + + // Only add fields not covered by the base or the revision. + $entity_data_fields = array_diff($entity_data_fields, $entity_fields, $entity_revision_fields); + $query->fields('data', $entity_data_fields); + } + $query->fields('base', $entity_fields); if ($ids) { @@ -501,6 +533,14 @@ public function save(EntityInterface $entity) { $this->preSave($entity); $this->invokeHook('presave', $entity); + // Ensure the default langcode is stored in the base table. + // @todo clarify if it wouldn't be better to rename the column in the base + // table default_langcode as well. + if ($this->dataTable) { + $langcode = $entity->langcode; + $entity->langcode = $entity->default_langcode; + } + if (!$entity->isNew()) { if ($entity->isDefaultRevision()) { $return = drupal_write_record($this->entityInfo['base table'], $entity, $this->idKey); @@ -510,6 +550,11 @@ public function save(EntityInterface $entity) { // with $isDefaultRevision = FALSE? $return = FALSE; } + // Ensure the appropriate langcodes are stored in the property data. + if ($this->dataTable) { + $entity->default_langcode = $entity->langcode; + $entity->langcode = $langcode; + } if ($this->revisionKey) { $this->saveRevision($entity); } @@ -519,6 +564,13 @@ public function save(EntityInterface $entity) { } else { $return = drupal_write_record($this->entityInfo['base table'], $entity); + // Ensure the appropriate langcodes are stored in the property data. + // @todo clarify if it wouldn't be better to rename the column in the base + // table default_langcode as well. + if ($this->dataTable) { + $entity->default_langcode = $entity->langcode; + $entity->langcode = $langcode; + } if ($this->revisionKey) { $this->saveRevision($entity); } @@ -550,6 +602,18 @@ public function save(EntityInterface $entity) { * The entity object. */ protected function saveRevision(EntityInterface $entity) { + // Ensure when saving properties in a new language a new revision is created. + // @todo try to find a nicer/unified way to do this. + if ($this->dataTable && !$entity->isNewRevision()) { + $query = db_select($this->revisionTable) + ->condition($this->idKey, $entity->id()) + ->condition('langcode', $entity->langcode); + $query->addExpression('1'); + if (!$query->execute()->fetchField()) { + $entity->setNewRevision(); + } + } + // Convert the entity into an array as it might not have the same properties // as the entity, it is just a raw structure. $record = (array) $entity; @@ -593,11 +657,32 @@ protected function preSave(EntityInterface $entity) { } * Used after the entity is saved, but before invoking the insert or update * hook. * - * @param $update + * @param boolean $update * (bool) TRUE if the entity has been updated, or FALSE if it has been * inserted. */ - protected function postSave(EntityInterface $entity, $update) { } + protected function postSave(EntityInterface $entity, $update) { + // Write the data into the property data table if one is available. + if ($this->dataTable) { + // @todo try to find a better way to deal with updates. + $query = db_select($this->entityInfo['data table']) + ->condition($this->idKey, $entity->id()) + ->condition('langcode', $entity->langcode); + $query->addExpression('1'); + if ($update && $query->execute()->fetchField()) { + // Update existing property data row. + $primary_keys = array( + $this->idKey, + 'langcode', + ); + drupal_write_record($this->entityInfo['data table'], $entity, $primary_keys); + } + else { + // Insert new property data row. + drupal_write_record($this->entityInfo['data table'], $entity); + } + } + } /** * Acts on entities before they are deleted. diff --git a/core/modules/aggregator/lib/Drupal/aggregator/Tests/AggregatorTestBase.php b/core/modules/aggregator/lib/Drupal/aggregator/Tests/AggregatorTestBase.php index f46c2c4..003d99c 100644 --- a/core/modules/aggregator/lib/Drupal/aggregator/Tests/AggregatorTestBase.php +++ b/core/modules/aggregator/lib/Drupal/aggregator/Tests/AggregatorTestBase.php @@ -96,7 +96,7 @@ function getFeedEditArray($feed_url = NULL) { */ function getDefaultFeedItemCount() { // Our tests are based off of rss.xml, so let's find out how many elements should be related. - $feed_count = db_query_range('SELECT COUNT(*) FROM {node} n WHERE n.promote = 1 AND n.status = 1', 0, config('system.rss')->get('items.limit'))->fetchField(); + $feed_count = db_query_range('SELECT COUNT(DISTINCT nid) FROM {node_property_data} npd WHERE npd.promote = 1 AND npd.status = 1', 0, config('system.rss')->get('items.limit'))->fetchField(); return $feed_count > 10 ? 10 : $feed_count; } diff --git a/core/modules/book/book.module b/core/modules/book/book.module index 7c43e28..69487c5 100644 --- a/core/modules/book/book.module +++ b/core/modules/book/book.module @@ -313,12 +313,12 @@ function book_block_view($delta = '') { elseif ($current_bid) { // Only display this block when the user is browsing a book. $select = db_select('node', 'n') - ->fields('n', array('title')) + ->fields('n', array('nid')) ->condition('n.nid', $node->book['bid']) ->addTag('node_access'); - $title = $select->execute()->fetchField(); + $nid = $select->execute()->fetchField(); // Only show the block if the user has view access for the top-level node. - if ($title) { + if ($nid) { $tree = menu_tree_all_data($node->book['menu_name'], $node->book); // There should only be one element at the top level. $data = array_shift($tree); @@ -394,20 +394,22 @@ function book_get_books() { if ($nids) { $query = db_select('book', 'b', array('fetch' => PDO::FETCH_ASSOC)); $query->join('node', 'n', 'b.nid = n.nid'); + $query->join('node_property_data', 'npd', 'n.nid = npd.nid'); $query->join('menu_links', 'ml', 'b.mlid = ml.mlid'); $query->addField('n', 'type', 'type'); - $query->addField('n', 'title', 'title'); $query->fields('b'); $query->fields('ml'); - $query->condition('n.nid', $nids, 'IN'); - $query->condition('n.status', 1); + $query->condition('npd.nid', $nids, 'IN'); + $query->condition('npd.status', 1); $query->orderBy('ml.weight'); $query->orderBy('ml.link_title'); $query->addTag('node_access'); $result2 = $query->execute(); foreach ($result2 as $link) { + $node = node_load($link['nid']); $link['href'] = $link['link_path']; $link['options'] = unserialize($link['options']); + $link['title'] = $node->label(); $all_books[$link['bid']] = $link; } } diff --git a/core/modules/comment/comment.admin.inc b/core/modules/comment/comment.admin.inc index dcbd1bc..d66a79b 100644 --- a/core/modules/comment/comment.admin.inc +++ b/core/modules/comment/comment.admin.inc @@ -82,11 +82,10 @@ function comment_admin_overview($form, &$form_state, $arg) { $query = db_select('comment', 'c') ->extend('Drupal\Core\Database\Query\PagerSelectExtender') ->extend('Drupal\Core\Database\Query\TableSortExtender'); - $query->join('node', 'n', 'n.nid = c.nid'); - $query->addField('n', 'title', 'node_title'); + $query->join('node_property_data', 'npd', 'npd.nid = c.nid'); $query->addTag('node_access'); $result = $query - ->fields('c', array('cid', 'subject', 'name', 'changed')) + ->fields('c', array('cid', 'nid', 'subject', 'name', 'changed')) ->condition('c.status', $status) ->limit(50) ->orderByHeader($header) @@ -97,8 +96,9 @@ function comment_admin_overview($form, &$form_state, $arg) { // We collect a sorted list of node_titles during the query to attach to the // comments later. foreach ($result as $row) { + $node = node_load($row->nid); $cids[] = $row->cid; - $node_titles[] = $row->node_title; + $node_titles[] = $node->label(); } $comments = comment_load_multiple($cids); diff --git a/core/modules/comment/comment.install b/core/modules/comment/comment.install index f47151f..615e74c 100644 --- a/core/modules/comment/comment.install +++ b/core/modules/comment/comment.install @@ -34,14 +34,16 @@ function comment_uninstall() { */ function comment_enable() { // Insert records into the node_comment_statistics for nodes that are missing. - $query = db_select('node', 'n'); - $query->leftJoin('node_comment_statistics', 'ncs', 'ncs.nid = n.nid'); - $query->addField('n', 'created', 'last_comment_timestamp'); - $query->addField('n', 'uid', 'last_comment_uid'); - $query->addField('n', 'nid'); + $query = db_select('node_property_data', 'npd'); + $query->leftJoin('node_comment_statistics', 'ncs', 'ncs.nid = npd.nid'); + $query->addField('npd', 'created', 'last_comment_timestamp'); + $query->addField('npd', 'uid', 'last_comment_uid'); + $query->addField('npd', 'nid'); $query->addExpression('0', 'comment_count'); $query->addExpression('NULL', 'last_comment_name'); $query->isNull('ncs.comment_count'); + // @todo Replace by proper language handling. + $query->groupBy('npd.nid'); db_insert('node_comment_statistics') ->from($query) diff --git a/core/modules/comment/comment.module b/core/modules/comment/comment.module index df0a33a..ea32034 100644 --- a/core/modules/comment/comment.module +++ b/core/modules/comment/comment.module @@ -533,13 +533,13 @@ function comment_permalink($cid) { */ function comment_get_recent($number = 10) { $query = db_select('comment', 'c'); - $query->innerJoin('node', 'n', 'n.nid = c.nid'); + $query->innerJoin('node_property_data', 'npd', 'npd.nid = c.nid'); $query->addTag('node_access'); $query->addMetaData('base_table', 'comment'); $comments = $query ->fields('c') ->condition('c.status', COMMENT_PUBLISHED) - ->condition('n.status', NODE_PUBLISHED) + ->condition('npd.status', NODE_PUBLISHED) ->orderBy('c.created', 'DESC') // Additionally order by cid to ensure that comments with the same timestamp // are returned in the exact order posted. diff --git a/core/modules/comment/lib/Drupal/comment/CommentStorageController.php b/core/modules/comment/lib/Drupal/comment/CommentStorageController.php index 4d447a5..6b50964 100644 --- a/core/modules/comment/lib/Drupal/comment/CommentStorageController.php +++ b/core/modules/comment/lib/Drupal/comment/CommentStorageController.php @@ -223,7 +223,7 @@ protected function updateNodeStatistics($nid) { } else { // Comments do not exist. - $node = db_query('SELECT uid, created FROM {node} WHERE nid = :nid', array(':nid' => $nid))->fetchObject(); + $node = db_query('SELECT uid, created FROM {node_property_data} WHERE nid = :nid LIMIT 1', array(':nid' => $nid))->fetchObject(); db_update('node_comment_statistics') ->fields(array( 'cid' => 0, diff --git a/core/modules/forum/forum.module b/core/modules/forum/forum.module index af2daa9..7c5beb9 100644 --- a/core/modules/forum/forum.module +++ b/core/modules/forum/forum.module @@ -806,14 +806,14 @@ function forum_forum_load($tid = NULL) { $_forums = taxonomy_get_tree($vid, $tid, NULL, TRUE); if (count($_forums)) { - $query = db_select('node', 'n'); - $query->join('node_comment_statistics', 'ncs', 'n.nid = ncs.nid'); - $query->join('forum', 'f', 'n.vid = f.vid'); - $query->addExpression('COUNT(n.nid)', 'topic_count'); + $query = db_select('node_property_data', 'npd'); + $query->join('node_comment_statistics', 'ncs', 'npd.nid = ncs.nid'); + $query->join('forum', 'f', 'npd.vid = f.vid'); + $query->addExpression('COUNT(npd.nid)', 'topic_count'); $query->addExpression('SUM(ncs.comment_count)', 'comment_count'); $counts = $query ->fields('f', array('tid')) - ->condition('n.status', 1) + ->condition('npd.status', 1) ->groupBy('tid') ->addTag('node_access') ->execute() @@ -837,15 +837,15 @@ function forum_forum_load($tid = NULL) { } // Query "Last Post" information for this forum. - $query = db_select('node', 'n'); - $query->join('forum', 'f', 'n.vid = f.vid AND f.tid = :tid', array(':tid' => $forum->tid)); - $query->join('node_comment_statistics', 'ncs', 'n.nid = ncs.nid'); + $query = db_select('node_property_data', 'npd'); + $query->join('forum', 'f', 'npd.vid = f.vid AND f.tid = :tid', array(':tid' => $forum->tid)); + $query->join('node_comment_statistics', 'ncs', 'npd.nid = ncs.nid'); $query->join('users', 'u', 'ncs.last_comment_uid = u.uid'); $query->addExpression('CASE ncs.last_comment_uid WHEN 0 THEN ncs.last_comment_name ELSE u.name END', 'last_comment_name'); $topic = $query ->fields('ncs', array('last_comment_timestamp', 'last_comment_uid')) - ->condition('n.status', 1) + ->condition('npd.status', 1) ->orderBy('last_comment_timestamp', 'DESC') ->range(0, 1) ->addTag('node_access') @@ -884,13 +884,13 @@ function forum_forum_load($tid = NULL) { * The number of new posts in the forum that have not been read by the user. */ function _forum_topics_unread($term, $uid) { - $query = db_select('node', 'n'); - $query->join('forum', 'f', 'n.vid = f.vid AND f.tid = :tid', array(':tid' => $term)); - $query->leftJoin('history', 'h', 'n.nid = h.nid AND h.uid = :uid', array(':uid' => $uid)); - $query->addExpression('COUNT(n.nid)', 'count'); + $query = db_select('node_property_data', 'npd'); + $query->join('forum', 'f', 'npd.vid = f.vid AND f.tid = :tid', array(':tid' => $term)); + $query->leftJoin('history', 'h', 'npd.nid = h.nid AND h.uid = :uid', array(':uid' => $uid)); + $query->addExpression('COUNT(npd.nid)', 'count'); return $query ->condition('status', 1) - ->condition('n.created', NODE_NEW_LIMIT, '>') + ->condition('npd.created', NODE_NEW_LIMIT, '>') ->isNull('h.nid') ->addTag('node_access') ->execute() @@ -958,17 +958,17 @@ function forum_get_topics($tid, $sortby, $forum_per_page) { if ($nids) { $nodes = node_load_multiple($nids); - $query = db_select('node', 'n') + $query = db_select('node_property_data', 'npd') ->extend('Drupal\Core\Database\Query\TableSortExtender'); - $query->fields('n', array('nid')); + $query->fields('npd', array('nid')); - $query->join('node_comment_statistics', 'ncs', 'n.nid = ncs.nid'); + $query->join('node_comment_statistics', 'ncs', 'npd.nid = ncs.nid'); $query->fields('ncs', array('cid', 'last_comment_uid', 'last_comment_timestamp', 'comment_count')); $query->join('forum_index', 'f', 'f.nid = ncs.nid'); $query->addField('f', 'tid', 'forum_tid'); - $query->join('users', 'u', 'n.uid = u.uid'); + $query->join('users', 'u', 'npd.uid = u.uid'); $query->addField('u', 'name'); $query->join('users', 'u2', 'ncs.last_comment_uid = u2.uid'); @@ -978,7 +978,8 @@ function forum_get_topics($tid, $sortby, $forum_per_page) { $query ->orderBy('f.sticky', 'DESC') ->orderByHeader($forum_topic_list_header) - ->condition('n.nid', $nids); + ->condition('npd.nid', $nids) + ->groupBy('npd.nid'); $result = array(); foreach ($query->execute() as $row) { @@ -1386,7 +1387,7 @@ function _forum_update_forum_index($nid) { } else { // Comments do not exist. - $node = db_query('SELECT uid, created FROM {node} WHERE nid = :nid', array(':nid' => $nid))->fetchObject(); + $node = db_query('SELECT uid, created FROM {node_property_data} WHERE nid = :nid LIMIT 1', array(':nid' => $nid))->fetchObject(); db_update('forum_index') ->fields( array( 'comment_count' => 0, diff --git a/core/modules/language/language.api.php b/core/modules/language/language.api.php index 6f19835..2ec0c4c 100644 --- a/core/modules/language/language.api.php +++ b/core/modules/language/language.api.php @@ -52,8 +52,8 @@ function hook_language_update($language) { function hook_language_delete($language) { // On nodes with this language, unset the language db_update('node') - ->fields(array('language' => '')) - ->condition('language', $language->langcode) + ->fields(array('langcode' => '')) + ->condition('langcode', $language->langcode) ->execute(); } diff --git a/core/modules/node/lib/Drupal/node/Node.php b/core/modules/node/lib/Drupal/node/Node.php index 45d93f0..f519507 100644 --- a/core/modules/node/lib/Drupal/node/Node.php +++ b/core/modules/node/lib/Drupal/node/Node.php @@ -61,6 +61,13 @@ class Node extends Entity implements ContentEntityInterface { public $langcode = LANGUAGE_NOT_SPECIFIED; /** + * The node default language code. + * + * @var string + */ + public $default_langcode; + + /** * The node title. * * @var string diff --git a/core/modules/node/lib/Drupal/node/NodeFormController.php b/core/modules/node/lib/Drupal/node/NodeFormController.php index 5add3df..06ebea4 100644 --- a/core/modules/node/lib/Drupal/node/NodeFormController.php +++ b/core/modules/node/lib/Drupal/node/NodeFormController.php @@ -369,9 +369,9 @@ protected function submitNodeLanguage(array $form, array &$form_state) { /** * Form submission handler for the 'preview' action. * - * @param $form + * @param array $form * An associative array containing the structure of the form. - * @param $form_state + * @param array $form_state * A reference to a keyed array containing the current state of the form. */ public function preview(array $form, array &$form_state) { diff --git a/core/modules/node/lib/Drupal/node/NodeStorageController.php b/core/modules/node/lib/Drupal/node/NodeStorageController.php index 3993d24..1d999c3 100644 --- a/core/modules/node/lib/Drupal/node/NodeStorageController.php +++ b/core/modules/node/lib/Drupal/node/NodeStorageController.php @@ -66,9 +66,8 @@ protected function buildQuery($ids, $revision_id = FALSE) { // alias timestamp to revision_timestamp and add revision_uid. $query = parent::buildQuery($ids, $revision_id); $fields =& $query->getFields(); - unset($fields['timestamp']); $query->addField('revision', 'timestamp', 'revision_timestamp'); - $fields['uid']['table'] = 'base'; + $fields['uid']['table'] = 'data'; $query->addField('revision', 'uid', 'revision_uid'); return $query; } @@ -80,7 +79,7 @@ protected function invokeHook($hook, EntityInterface $node) { if ($hook == 'insert' || $hook == 'update') { node_invoke($node, $hook); } - else if ($hook == 'predelete') { + elseif ($hook == 'predelete') { // 'delete' is triggered in 'predelete' is here to preserve hook ordering // from Drupal 7. node_invoke($node, 'delete'); @@ -95,32 +94,19 @@ protected function invokeHook($hook, EntityInterface $node) { protected function preSave(EntityInterface $node) { // Before saving the node, set changed and revision times. $node->changed = REQUEST_TIME; + + // Make sure the default language is set. + if (empty($node->default_langcode)) { + $node->default_langcode = $node->langcode; + } } /** * Overrides Drupal\Core\Entity\DatabaseStorageController::preSaveRevision(). */ protected function preSaveRevision(array &$record, EntityInterface $entity) { - if ($entity->isNewRevision()) { - // When inserting either a new node or a new node revision, $node->log - // must be set because {node_revision}.log is a text column and therefore - // cannot have a default value. However, it might not be set at this - // point (for example, if the user submitting a node form does not have - // permission to create revisions), so we ensure that it is at least an - // empty string in that case. - // @todo: Make the {node_revision}.log column nullable so that we can - // remove this check. - if (!isset($record['log'])) { - $record['log'] = ''; - } - } - elseif (!isset($record['log']) || $record['log'] === '') { - // If we are updating an existing node without adding a new revision, we - // need to make sure $node->log is unset whenever it is empty. As long as - // $node->log is unset, drupal_write_record() will not attempt to update - // the existing database column when re-saving the revision; therefore, - // this code allows us to avoid clobbering an existing log entry with an - // empty one. + // Make sure an existing log entry isn't overwritten unnecessarily. + if (empty($record['log'])) { unset($record['log']); } @@ -133,7 +119,9 @@ protected function preSaveRevision(array &$record, EntityInterface $entity) { /** * Overrides Drupal\Core\Entity\DatabaseStorageController::postSave(). */ - function postSave(EntityInterface $node, $update) { + protected function postSave(EntityInterface $node, $update) { + parent::postSave($node, $update); + // Update the node access table for this node, but only if it is the // default revision. There's no need to delete existing records if the node // is new. @@ -144,7 +132,7 @@ function postSave(EntityInterface $node, $update) { /** * Overrides Drupal\Core\Entity\DatabaseStorageController::preDelete(). */ - function preDelete($entities) { + protected function preDelete($entities) { if (module_exists('search')) { foreach ($entities as $id => $entity) { search_reindex($entity->nid, 'node'); diff --git a/core/modules/node/lib/Drupal/node/Tests/NodeAccessBaseTableTest.php b/core/modules/node/lib/Drupal/node/Tests/NodeAccessBaseTableTest.php index a8e36b2..b6aaf1c 100644 --- a/core/modules/node/lib/Drupal/node/Tests/NodeAccessBaseTableTest.php +++ b/core/modules/node/lib/Drupal/node/Tests/NodeAccessBaseTableTest.php @@ -82,7 +82,7 @@ function testNodeAccessBasic() { } $this->drupalPost('node/add/article', $edit, t('Save')); - $nid = db_query('SELECT nid FROM {node} WHERE title = :title', array(':title' => $edit['title']))->fetchField(); + $nid = db_query('SELECT nid FROM {node_property_data} WHERE title = :title', array(':title' => $edit['title']))->fetchField(); $private_status = db_query('SELECT private FROM {node_access_test} where nid = :nid', array(':nid' => $nid))->fetchField(); $this->assertTrue($is_private == $private_status, 'The private status of the node was properly set in the node_access_test table.'); if ($is_private) { diff --git a/core/modules/node/lib/Drupal/node/Tests/NodeAdminTest.php b/core/modules/node/lib/Drupal/node/Tests/NodeAdminTest.php index 3c02765..de68f7e 100644 --- a/core/modules/node/lib/Drupal/node/Tests/NodeAdminTest.php +++ b/core/modules/node/lib/Drupal/node/Tests/NodeAdminTest.php @@ -43,8 +43,9 @@ function testContentAdminSort() { } // Test that the default sort by node.changed DESC actually fires properly. - $nodes_query = db_select('node', 'n') - ->fields('n', array('nid')) + $nodes_query = db_select('node_property_data', 'npd') + ->distinct(TRUE) + ->fields('npd', array('nid')) ->orderBy('changed', 'DESC') ->execute() ->fetchCol(); @@ -58,8 +59,9 @@ function testContentAdminSort() { // Compare the rendered HTML node list to a query for the nodes ordered by // title to account for possible database-dependent sort order. - $nodes_query = db_select('node', 'n') - ->fields('n', array('nid')) + $nodes_query = db_select('node_property_data', 'npd') + ->distinct(TRUE) + ->fields('npd', array('nid')) ->orderBy('title') ->execute() ->fetchCol(); diff --git a/core/modules/node/lib/Drupal/node/Tests/NodeBlockFunctionalTest.php b/core/modules/node/lib/Drupal/node/Tests/NodeBlockFunctionalTest.php index edcb007..30c204b 100644 --- a/core/modules/node/lib/Drupal/node/Tests/NodeBlockFunctionalTest.php +++ b/core/modules/node/lib/Drupal/node/Tests/NodeBlockFunctionalTest.php @@ -72,13 +72,13 @@ function testRecentNodeBlock() { $node3 = $this->drupalCreateNode($default_settings); // Change the changed time for node so that we can test ordering. - db_update('node') + db_update('node_property_data') ->fields(array( 'changed' => $node1->changed + 100, )) ->condition('nid', $node2->nid) ->execute(); - db_update('node') + db_update('node_property_data') ->fields(array( 'changed' => $node1->changed + 200, )) diff --git a/core/modules/node/lib/Drupal/node/Tests/NodeFieldMultilingualTestCase.php b/core/modules/node/lib/Drupal/node/Tests/NodeFieldMultilingualTestCase.php index 9c3dd06..c512825 100644 --- a/core/modules/node/lib/Drupal/node/Tests/NodeFieldMultilingualTestCase.php +++ b/core/modules/node/lib/Drupal/node/Tests/NodeFieldMultilingualTestCase.php @@ -141,4 +141,5 @@ function testMultilingualDisplaySettings() { )); $this->assertEqual(current($body), $node->body['en'][0]['value'], 'Node body found.'); } + } diff --git a/core/modules/node/lib/Drupal/node/Tests/NodePropertyMultilingualTestCase.php b/core/modules/node/lib/Drupal/node/Tests/NodePropertyMultilingualTestCase.php new file mode 100644 index 0000000..22437f9 --- /dev/null +++ b/core/modules/node/lib/Drupal/node/Tests/NodePropertyMultilingualTestCase.php @@ -0,0 +1,196 @@ + 'Multilingual properties', + 'description' => 'Test multilingual support for properties.', + 'group' => 'Node', + ); + } + + public function setUp() { + parent::setUp(); + + // Create Basic page node type. + $this->drupalCreateContentType(array('type' => 'page', 'name' => 'Basic page')); + + // Setup users. + $admin_user = $this->drupalCreateUser(array( + 'administer languages', + 'administer content types', + 'access administration pages', + 'create page content', + 'edit own page content', + )); + $this->drupalLogin($admin_user); + + // Add a new languages. + $language = new Language(array( + 'langcode' => 'it', + 'name' => 'Italian', + )); + language_save($language); + $language = new Language(array( + 'langcode' => 'de', + 'name' => 'German', + )); + language_save($language); + + // Enable URL language detection and selection. + $edit = array('language_interface[enabled][language-url]' => '1'); + $this->drupalPost('admin/config/regional/language/detection', $edit, t('Save settings')); + + // Set "Basic page" content type to use multilingual support. + $edit = array( + 'language_configuration[language_hidden]' => FALSE, + ); + $this->drupalPost('admin/structure/types/manage/page', $edit, t('Save content type')); + $this->assertRaw(t('The content type %type has been updated.', array('%type' => 'Basic page')), 'Basic page content type has been updated.'); + + // Make node body translatable. + $field = field_info_field('body'); + $field['translatable'] = TRUE; + field_update_field($field); + } + + /** + * Test property translation. + */ + public function testMultilingualProperties() { + $available_langcodes = array('en', 'it', 'de'); + // Create different nodes with different default langcodes. + foreach ($available_langcodes as $default_langcode) { + // Create base node. + $title_values = array( + $default_langcode => $this->randomName(8), + ); + $node = $this->drupalCreateNode(array( + 'title' => $title_values[$default_langcode], + 'body' => array($default_langcode => array(array($this->randomString(30)))), + 'langcode' => $default_langcode, + )); + + $property_langcodes = array_diff($available_langcodes, array($default_langcode)); + + // Create properties in different languages. + $property_count = 1; + $expected_languages = array($default_langcode); + foreach ($property_langcodes as $langcode) { + $property_count++; + $expected_languages[] = $langcode; + $title_values[$langcode] = $this->randomName(8); + + // Save properties in a different language. + $node->title = $title_values[$langcode]; + $node->langcode = $langcode; + $node->save(); + + // Check consistency of property data. + $node_property_data = db_select('node_property_data') + ->fields('node_property_data') + ->condition('nid', $node->id()) + ->execute() + ->fetchAll(); + $this->assertEqual($property_count, count($node_property_data), 'Expected ' . $property_count . ' property rows found.'); + $found_property_languages = array(); + $found_property_default_languages = array(); + foreach ($node_property_data as $node_property_data_raw) { + $found_property_languages[] = $node_property_data_raw->langcode; + $found_property_default_languages[$node_property_data_raw->default_langcode] = $node_property_data_raw->default_langcode; + $this->assertEqual($title_values[$node_property_data_raw->langcode], $node_property_data_raw->title, $node_property_data_raw->langcode . ': Language specific title found.'); + } + $this->assertEqual($expected_languages, $found_property_languages, 'Expected property languages found.'); + $this->assertEqual(array($default_langcode => $default_langcode), $found_property_default_languages, 'Default language of properties is consistent.'); + + // Check consistency of revisions. + $node_property_revision = db_select('node_property_revision') + ->fields('node_property_revision') + ->condition('nid', $node->id()) + ->orderBy('vid') + ->execute() + ->fetchAll(); + $this->assertEqual($property_count, count($node_property_revision), 'Expected ' . $property_count . ' property revision rows found.'); + $found_revision_languages = array(); + $found_revision_default_languages = array(); + foreach ($node_property_revision as $node_property_revision_raw) { + $found_revision_languages[] = $node_property_revision_raw->langcode; + $found_revision_default_languages[$node_property_revision_raw->default_langcode] = $node_property_revision_raw->default_langcode; + $this->assertEqual($title_values[$node_property_revision_raw->langcode], $node_property_revision_raw->title, $node_property_revision_raw->langcode . ': Language specific title found.'); + } + $this->assertEqual($expected_languages, $found_revision_languages, 'Expected property revision languages found.'); + $this->assertEqual(array($default_langcode => $default_langcode), $found_revision_default_languages, 'Default language of revisions is consistent.'); + } + + // Check revisioning. + $title_revision_values = array(); + foreach ($available_langcodes as $langcode) { + $title_revision_values[$langcode] = $this->randomName(8); + // Create new revision in the same language. + $old_revision_id = $node->getRevisionId(); + $node->title = $title_revision_values[$langcode]; + $node->langcode = $langcode; + $node->setNewRevision(); + $node->save(); + + // Check consistency of property data. + $node_property_data = db_select('node_property_data') + ->fields('node_property_data') + ->condition('nid', $node->id()) + ->condition('langcode', $node->langcode) + ->execute() + ->fetchAll(); + if ($this->assertEqual(1, count($node_property_data), 'Two property rows found.')) { + $this->assertNotEqual($old_revision_id, $node_property_data[0]->vid, $node_property_data_raw->langcode . ': New Revision id set.'); + $this->assertEqual($title_revision_values[$langcode], $node_property_data[0]->title, $node_property_data_raw->langcode . ': Revisioned title found.'); + } + + $node_property_revision = db_select('node_property_revision') + ->fields('node_property_revision') + ->condition('nid', $node->id()) + ->condition('langcode', $node->langcode) + ->orderBy('vid') + ->execute() + ->fetchAll(); + $this->assertEqual(2, count($node_property_revision), 'Two property revision rows found.'); + foreach ($node_property_revision as $node_property_revision_raw) { + if ($node_property_revision_raw->vid == $node->getRevisionId()) { + $this->assertEqual($title_revision_values[$langcode], $node_property_revision_raw->title, $node_property_revision_raw->langcode . ': title of new revision found.'); + } + else { + $this->assertEqual($title_values[$langcode], $node_property_revision_raw->title, $node_property_revision_raw->langcode . ': title of old revision found.'); + } + } + } + + // Check if the default language is untouched. + $node_raw_data = db_select('node') + ->fields('node') + ->condition('nid', $node->id()) + ->execute() + ->fetchAll(); + $this->assertEqual($default_langcode, $node_raw_data[0]->langcode, 'Default language unchanged.'); + } + } +} diff --git a/core/modules/node/lib/Drupal/node/Tests/NodeRevisionsTest.php b/core/modules/node/lib/Drupal/node/Tests/NodeRevisionsTest.php index 6bb752f..1543f0f 100644 --- a/core/modules/node/lib/Drupal/node/Tests/NodeRevisionsTest.php +++ b/core/modules/node/lib/Drupal/node/Tests/NodeRevisionsTest.php @@ -97,12 +97,12 @@ function testRevisions() { $this->assertRaw(t('Revision from %revision-date of @type %title has been deleted.', array('%revision-date' => format_date($nodes[1]->revision_timestamp), '@type' => 'Basic page', '%title' => $nodes[1]->label())), 'Revision deleted.'); - $this->assertTrue(db_query('SELECT COUNT(vid) FROM {node_revision} WHERE nid = :nid and vid = :vid', array(':nid' => $node->nid, ':vid' => $nodes[1]->vid))->fetchField() == 0, 'Revision not found.'); + $this->assertTrue(db_query('SELECT COUNT(vid) FROM {node_property_revision} WHERE nid = :nid and vid = :vid', array(':nid' => $node->nid, ':vid' => $nodes[1]->vid))->fetchField() == 0, 'Revision not found.'); // Set the revision timestamp to an older date to make sure that the // confirmation message correctly displays the stored revision date. $old_revision_date = REQUEST_TIME - 86400; - db_update('node_revision') + db_update('node_property_revision') ->condition('vid', $nodes[2]->vid) ->fields(array( 'timestamp' => $old_revision_date, diff --git a/core/modules/node/node.admin.inc b/core/modules/node/node.admin.inc index 2ca2d3e..ba9f4c4 100644 --- a/core/modules/node/node.admin.inc +++ b/core/modules/node/node.admin.inc @@ -136,12 +136,16 @@ function node_build_filter_query(SelectInterface $query) { foreach ($filter_data as $index => $filter) { list($key, $value) = $filter; switch ($key) { + case 'type': + $query->condition('n.' . $key, $value); + break; + + // Part of node property data table. case 'status': // Note: no exploitable hole as $key/$value have already been checked when submitted list($key, $value) = explode('-', $value, 2); - case 'type': case 'language': - $query->condition('n.' . $key, $value); + $query->condition('npd.' . $key, $value); break; } } @@ -464,7 +468,7 @@ function node_admin_nodes() { $header = array( 'title' => array( 'data' => t('Title'), - 'field' => 'n.title', + 'field' => 'npd.title', ), 'type' => array( 'data' => t('Content type'), @@ -477,42 +481,43 @@ function node_admin_nodes() { ), 'status' => array( 'data' => t('Status'), - 'field' => 'n.status', + 'field' => 'npd.status', ), 'changed' => array( 'data' => t('Updated'), - 'field' => 'n.changed', + 'field' => 'npd.changed', 'sort' => 'desc', 'class' => array(RESPONSIVE_PRIORITY_LOW) ,) ); if ($multilingual) { - $header['language_name'] = array('data' => t('Language'), 'field' => 'n.langcode', 'class' => array(RESPONSIVE_PRIORITY_LOW)); + $header['language_name'] = array('data' => t('Language'), 'field' => 'npd.langcode', 'class' => array(RESPONSIVE_PRIORITY_LOW)); } $header['operations'] = array('data' => t('Operations')); $query = db_select('node', 'n') ->extend('Drupal\Core\Database\Query\PagerSelectExtender') ->extend('Drupal\Core\Database\Query\TableSortExtender'); + $query->innerJoin('node_property_data', 'npd', 'npd.nid = n.nid'); node_build_filter_query($query); if (!user_access('bypass node access')) { // If the user is able to view their own unpublished nodes, allow them // to see these in addition to published nodes. Check that they actually // have some unpublished nodes to view before adding the condition. - if (user_access('view own unpublished content') && $own_unpublished = db_query('SELECT nid FROM {node} WHERE uid = :uid AND status = :status', array(':uid' => $GLOBALS['user']->uid, ':status' => 0))->fetchCol()) { + if (user_access('view own unpublished content') && $own_unpublished = db_query('SELECT DISTINCT nid FROM {node_property_data} WHERE uid = :uid AND status = :status', array(':uid' => $GLOBALS['user']->uid, ':status' => 0))->fetchCol()) { $query->condition(db_or() - ->condition('n.status', 1) - ->condition('n.nid', $own_unpublished, 'IN') + ->condition('npd.status', 1) + ->condition('npd.nid', $own_unpublished, 'IN') ); } else { // If not, restrict the query to published nodes. - $query->condition('n.status', 1); + $query->condition('npd.status', 1); } } $nids = $query - ->fields('n',array('nid')) + ->fields('npd', array('nid')) ->limit(50) ->orderByHeader($header) ->addTag('node_access') @@ -677,14 +682,14 @@ function node_admin_nodes_submit($form, &$form_state) { */ function node_multiple_delete_confirm($form, &$form_state, $nodes) { $form['nodes'] = array('#prefix' => '', '#tree' => TRUE); + $node_entities = node_load_multiple(array_keys($nodes)); // array_filter returns only elements with TRUE values foreach ($nodes as $nid => $value) { - $title = db_query('SELECT title FROM {node} WHERE nid = :nid', array(':nid' => $nid))->fetchField(); $form['nodes'][$nid] = array( '#type' => 'hidden', '#value' => $nid, '#prefix' => '
  • ', - '#suffix' => check_plain($title) . "
  • \n", + '#suffix' => check_plain($node_entities[$nid]->label()) . "\n", ); } $form['operation'] = array('#type' => 'hidden', '#value' => 'delete'); diff --git a/core/modules/node/node.install b/core/modules/node/node.install index a9c2c36..c2df993 100644 --- a/core/modules/node/node.install +++ b/core/modules/node/node.install @@ -27,7 +27,7 @@ function node_schema() { // Defaults to NULL in order to avoid a brief period of potential // deadlocks on the index. 'vid' => array( - 'description' => 'The current {node_revision}.vid version identifier.', + 'description' => 'The current {node_property_revision}.vid version identifier.', 'type' => 'int', 'unsigned' => TRUE, 'not null' => FALSE, @@ -47,6 +47,68 @@ function node_schema() { 'not null' => TRUE, 'default' => '', ), + 'tnid' => array( + 'description' => 'The translation set id for this node, which equals the node id of the source post in each set.', + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'default' => 0, + ), + 'translate' => array( + 'description' => 'A boolean indicating whether this translation page needs to be updated.', + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + ), + ), + 'indexes' => array( + 'node_type' => array(array('type', 4)), + 'tnid' => array('tnid'), + 'translate' => array('translate'), + ), + 'unique keys' => array( + 'vid' => array('vid'), + 'uuid' => array('uuid'), + ), + 'foreign keys' => array( + 'node_revision' => array( + 'table' => 'node_revision', + 'columns' => array('vid' => 'vid'), + ), + ), + 'primary key' => array('nid'), + ); + + // Node property storage. + $schema['node_property_data'] = array( + 'description' => 'Base table for node properties.', + 'fields' => array( + 'nid' => array( + 'description' => 'The primary identifier for a node.', + 'type' => 'serial', + 'unsigned' => TRUE, + 'not null' => TRUE, + ), + 'vid' => array( + 'description' => 'The current {node_property_revision}.vid version identifier.', + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + ), + 'langcode' => array( + 'description' => 'The {language}.langcode of this node.', + 'type' => 'varchar', + 'length' => 12, + 'not null' => TRUE, + 'default' => '', + ), + 'default_langcode' => array( + 'description' => 'The default {language}.langcode of this node.', + 'type' => 'varchar', + 'length' => 12, + 'not null' => TRUE, + 'default' => '', + ), 'title' => array( 'description' => 'The title of this node, always treated as non-markup plain text.', 'type' => 'varchar', @@ -97,187 +159,187 @@ function node_schema() { 'not null' => TRUE, 'default' => 0, ), - 'tnid' => array( - 'description' => 'The translation set id for this node, which equals the node id of the source post in each set.', - 'type' => 'int', - 'unsigned' => TRUE, - 'not null' => TRUE, - 'default' => 0, - ), - 'translate' => array( - 'description' => 'A boolean indicating whether this translation page needs to be updated.', - 'type' => 'int', - 'not null' => TRUE, - 'default' => 0, - ), ), 'indexes' => array( - 'node_changed' => array('changed'), - 'node_created' => array('created'), - 'node_frontpage' => array('promote', 'status', 'sticky', 'created'), - 'node_status_type' => array('status', 'type', 'nid'), - 'node_title_type' => array('title', array('type', 4)), - 'node_type' => array(array('type', 4)), - 'uid' => array('uid'), - 'tnid' => array('tnid'), - 'translate' => array('translate'), + 'node_created' => array('created'), + 'node_changed' => array('changed'), + 'node_frontpage' => array('promote', 'status', 'sticky', 'changed'), + 'node_status' => array('status', 'nid'), + 'node_title' => array('title'), + 'uid' => array('uid'), ), 'unique keys' => array( 'vid' => array('vid'), - 'uuid' => array('uuid'), ), 'foreign keys' => array( - 'node_revision' => array( - 'table' => 'node_revision', - 'columns' => array('vid' => 'vid'), + 'node_base' => array( + 'table' => 'node', + 'columns' => array('nid' => 'nid'), ), 'node_author' => array( 'table' => 'users', 'columns' => array('uid' => 'uid'), ), ), - 'primary key' => array('nid'), + 'primary key' => array('nid', 'vid', 'langcode'), ); - $schema['node_access'] = array( - 'description' => 'Identifies which realm/grant pairs a user must possess in order to view, update, or delete specific nodes.', + // Node property revision storage. + $schema['node_property_revision'] = array( + 'description' => 'Stores information about each saved version of a {node}.', 'fields' => array( 'nid' => array( - 'description' => 'The {node}.nid this record affects.', + 'description' => 'The {node} this version belongs to.', 'type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, - 'default' => 0, ), - 'gid' => array( - 'description' => "The grant ID a user must possess in the specified realm to gain this row's privileges on the node.", - 'type' => 'int', + 'vid' => array( + 'description' => 'The primary identifier for this version.', + 'type' => 'serial', 'unsigned' => TRUE, 'not null' => TRUE, - 'default' => 0, ), - 'realm' => array( - 'description' => 'The realm in which the user must possess the grant ID. Each node access node can define one or more realms.', + 'langcode' => array( + 'description' => 'The {language}.langcode of this version.', + 'type' => 'varchar', + 'length' => 12, + 'not null' => TRUE, + 'default' => '', + ), + 'default_langcode' => array( + 'description' => 'The default {language}.langcode of this version.', + 'type' => 'varchar', + 'length' => 12, + 'not null' => TRUE, + 'default' => '', + ), + 'title' => array( + 'description' => 'The title of this version, always treated as non-markup plain text.', 'type' => 'varchar', 'length' => 255, 'not null' => TRUE, 'default' => '', ), - 'grant_view' => array( - 'description' => 'Boolean indicating whether a user with the realm/grant pair can view this node.', + 'uid' => array( + 'description' => 'The {users}.uid that created this version.', 'type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, 'default' => 0, - 'size' => 'tiny', ), - 'grant_update' => array( - 'description' => 'Boolean indicating whether a user with the realm/grant pair can edit this node.', + 'status' => array( + 'description' => 'Boolean indicating whether the node (at the time of this revision) is published (visible to non-administrators).', + 'type' => 'int', + 'not null' => TRUE, + 'default' => 1, + ), + 'comment' => array( + 'description' => 'Whether comments are allowed on this node (at the time of this revision): 0 = no, 1 = closed (read only), 2 = open (read/write).', 'type' => 'int', - 'unsigned' => TRUE, 'not null' => TRUE, 'default' => 0, - 'size' => 'tiny', ), - 'grant_delete' => array( - 'description' => 'Boolean indicating whether a user with the realm/grant pair can delete this node.', + 'promote' => array( + 'description' => 'Boolean indicating whether the node (at the time of this revision) should be displayed on the front page.', + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + ), + 'sticky' => array( + 'description' => 'Boolean indicating whether the node (at the time of this revision) should be displayed at the top of lists in which it appears.', + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + ), + 'log' => array( + 'description' => 'The log entry explaining the changes in this version.', + 'type' => 'text', + 'not null' => FALSE, + 'size' => 'big', + ), + 'timestamp' => array( + 'description' => 'The Unix timestamp when this revision was saved.', 'type' => 'int', - 'unsigned' => TRUE, 'not null' => TRUE, 'default' => 0, - 'size' => 'tiny', ), ), - 'primary key' => array('nid', 'gid', 'realm'), + 'indexes' => array( + 'nid' => array('nid'), + 'uid' => array('uid'), + ), + 'unique keys' => array( + 'vid' => array('vid'), + ), 'foreign keys' => array( - 'affected_node' => array( + 'versioned_node' => array( 'table' => 'node', 'columns' => array('nid' => 'nid'), ), - ), + 'version_author' => array( + 'table' => 'users', + 'columns' => array('uid' => 'uid'), + ), + ), + 'primary key' => array('nid', 'vid', 'langcode'), ); - $schema['node_revision'] = array( - 'description' => 'Stores information about each saved version of a {node}.', + $schema['node_access'] = array( + 'description' => 'Identifies which realm/grant pairs a user must possess in order to view, update, or delete specific nodes.', 'fields' => array( 'nid' => array( - 'description' => 'The {node} this version belongs to.', + 'description' => 'The {node}.nid this record affects.', 'type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, 'default' => 0, ), - 'vid' => array( - 'description' => 'The primary identifier for this version.', - 'type' => 'serial', - 'unsigned' => TRUE, - 'not null' => TRUE, - ), - 'uid' => array( - 'description' => 'The {users}.uid that created this version.', + 'gid' => array( + 'description' => "The grant ID a user must possess in the specified realm to gain this row's privileges on the node.", 'type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, 'default' => 0, ), - 'title' => array( - 'description' => 'The title of this version.', + 'realm' => array( + 'description' => 'The realm in which the user must possess the grant ID. Each node access node can define one or more realms.', 'type' => 'varchar', 'length' => 255, 'not null' => TRUE, 'default' => '', ), - 'log' => array( - 'description' => 'The log entry explaining the changes in this version.', - 'type' => 'text', - 'not null' => TRUE, - 'size' => 'big', - ), - 'timestamp' => array( - 'description' => 'A Unix timestamp indicating when this version was created.', - 'type' => 'int', - 'not null' => TRUE, - 'default' => 0, - ), - 'status' => array( - 'description' => 'Boolean indicating whether the node (at the time of this revision) is published (visible to non-administrators).', - 'type' => 'int', - 'not null' => TRUE, - 'default' => 1, - ), - 'comment' => array( - 'description' => 'Whether comments are allowed on this node (at the time of this revision): 0 = no, 1 = closed (read only), 2 = open (read/write).', + 'grant_view' => array( + 'description' => 'Boolean indicating whether a user with the realm/grant pair can view this node.', 'type' => 'int', + 'unsigned' => TRUE, 'not null' => TRUE, 'default' => 0, + 'size' => 'tiny', ), - 'promote' => array( - 'description' => 'Boolean indicating whether the node (at the time of this revision) should be displayed on the front page.', + 'grant_update' => array( + 'description' => 'Boolean indicating whether a user with the realm/grant pair can edit this node.', 'type' => 'int', + 'unsigned' => TRUE, 'not null' => TRUE, 'default' => 0, + 'size' => 'tiny', ), - 'sticky' => array( - 'description' => 'Boolean indicating whether the node (at the time of this revision) should be displayed at the top of lists in which it appears.', + 'grant_delete' => array( + 'description' => 'Boolean indicating whether a user with the realm/grant pair can delete this node.', 'type' => 'int', + 'unsigned' => TRUE, 'not null' => TRUE, 'default' => 0, + 'size' => 'tiny', ), ), - 'indexes' => array( - 'nid' => array('nid'), - 'uid' => array('uid'), - ), - 'primary key' => array('vid'), + 'primary key' => array('nid', 'gid', 'realm'), 'foreign keys' => array( - 'versioned_node' => array( + 'affected_node' => array( 'table' => 'node', 'columns' => array('nid' => 'nid'), ), - 'version_author' => array( - 'table' => 'users', - 'columns' => array('uid' => 'uid'), - ), ), ); @@ -365,7 +427,7 @@ function node_schema() { 'type' => 'int', 'not null' => TRUE, 'default' => 0, - 'size' => 'tiny' + 'size' => 'tiny', ), 'orig_type' => array( 'description' => 'The original machine-readable name of this node type. This may be different from the current type name if the locked field is 0.', @@ -692,6 +754,160 @@ function node_update_8007() { } /** + * Add dedicated tables for node properties. + */ +function node_update_8008() { + $schema = node_schema(); + // Create property table if necessary. + if (!db_table_exists('node_property_data')) { + db_create_table('node_property_data', $schema['node_property_data']); + } + + // Create property revision table if necessary. + if (!db_table_exists('node_property_revision')) { + db_create_table('node_property_revision', $schema['node_property_revision']); + } +} + +/** + * Move property data to dedicated table. + */ +function node_update_8009(&$sandbox) { + if (!isset($sandbox['progress'])) { + $sandbox['progress'] = 0; + $sandbox['last'] = (int) db_query('SELECT npd.nid FROM {node_property_data npd} ORDER BY nid DESC')->fetchField(); + $sandbox['max'] = db_query('SELECT COUNT(*) FROM {node} n LEFT JOIN {node_property_data} npd ON npd.nid = n.nid WHERE npd.nid IS NULL')->fetchField(); + } + + // Create initial property data set if necessary. + if (!empty($sandbox['max'])) { + $source_query = db_select('node') + ->fields('node', array( + 'nid', + 'vid', + 'langcode', + 'title', + 'uid', + 'status', + 'created', + 'changed', + 'comment', + 'promote', + 'sticky', + )) + ->range(0, 10) + ->condition('nid', $sandbox['last'], '>') + ->orderBy('nid'); + $source_query->addField('node', 'langcode', 'default_langcode'); + db_insert('node_property_data')->from($source_query)->execute(); + $sandbox['last'] = db_query('SELECT npd.nid FROM {node_property_data} npd ORDER BY nid DESC')->fetchField(); + $sandbox['progress'] += 10; + } + + $sandbox['#finished'] = empty($sandbox['max']) ? 1 : ($sandbox['progress'] / $sandbox['max']); +} + +/** + * Move property revisions to dedicated table. + */ +function node_update_8010(&$sandbox) { + if (!isset($sandbox['progress'])) { + $sandbox['progress'] = 0; + $sandbox['last'] = (int) db_query('SELECT npr.vid FROM {node_property_revision} npr ORDER BY vid DESC')->fetchField(); + $sandbox['max'] = db_query('SELECT COUNT(*) FROM {node_revision} nr LEFT JOIN {node_property_revision} npr ON npr.vid = nr.vid WHERE npr.vid IS NULL')->fetchField(); + } + + // Create initial revision set if necessary. + if (!empty($sandbox['max'])) { + $source_query = db_select('node_revision', 'nr') + ->fields('nr', array( + 'nid', + 'vid', + 'uid', + 'title', + 'status', + 'comment', + 'promote', + 'sticky', + 'log', + 'timestamp', + )) + ->range(0, 10) + ->condition('nr.vid', $sandbox['last'], '>') + ->orderBy('nr.vid'); + $source_query->innerJoin('node_property_data', 'npd', 'npd.nid = nr.nid'); + $source_query->addField('npd', 'langcode'); + $source_query->addField('npd', 'default_langcode'); + + db_insert('node_property_revision')->from($source_query)->execute(); + $sandbox['last'] = db_query('SELECT npr.vid FROM {node_property_revision} npr ORDER BY vid DESC')->fetchField(); + $sandbox['progress'] += 10; + } + + $sandbox['#finished'] = empty($sandbox['max']) ? 1 : ($sandbox['progress'] / $sandbox['max']); +} + +/** + * Cleanup old tables after finishing switch to dedicated property tables. + */ +function node_update_8011() { + $old_base_table_fields = array( + 'title', + 'uid', + 'status', + 'created', + 'changed', + 'comment', + 'promote', + 'sticky', + ); + $old_base_table_indexes = array( + 'node_created', + 'node_changed', + 'node_frontpage', + 'node_status_type', + 'node_title_type', + 'node_type', + 'uid', + ); + + $node_count = db_select('node')->countQuery()->execute()->fetchColumn(); + $node_revision_count = db_select('node_revision')->countQuery()->execute()->fetchColumn(); + $node_property_count = db_select('node_property_data')->countQuery()->execute()->fetchColumn(); + $node_property_revision_count = db_select('node_property_revision')->countQuery()->execute()->fetchColumn(); + + // Modify original tables if possible and necessary. + if ($node_property_count == $node_count) { + // Drop deprecated indexes. + foreach ($old_base_table_indexes as $old_index) { + if (db_index_exists('node', $old_index)) { + db_drop_index('node', $old_index); + } + } + // Recreate index. + if (!db_index_exists('node', 'node_type')) { + db_add_index('node', 'node_type', array(array('type', 4))); + } + // Drop deprecated fields. + foreach ($old_base_table_fields as $deprecated_base_field) { + if (db_field_exists('node', $deprecated_base_field)) { + db_drop_field('node', $deprecated_base_field); + } + } + } + else { + throw new DrupalUpdateException('The data migration from the node to the new node_property_data table seems to be inconsistent.'); + } + + if (db_table_exists('node_revision') && $node_revision_count == $node_property_revision_count) { + db_drop_table('node_revision'); + } + else { + throw new DrupalUpdateException('The data migration from the node_revision to the new node_property_revision table seems to be inconsistent.'); + } +} + +/** * @} End of "addtogroup updates-7.x-to-8.x" * The next series of updates should start at 9000. */ diff --git a/core/modules/node/node.module b/core/modules/node/node.module index 55989de..d76a752 100644 --- a/core/modules/node/node.module +++ b/core/modules/node/node.module @@ -203,7 +203,8 @@ function node_entity_info() { 'default' => 'Drupal\node\NodeFormController', ), 'base table' => 'node', - 'revision table' => 'node_revision', + 'data table' => 'node_property_data', + 'revision table' => 'node_property_revision', 'uri callback' => 'node_uri', 'fieldable' => TRUE, 'entity keys' => array( @@ -1407,16 +1408,17 @@ function node_search_execute($keys = NULL, $conditions = NULL) { ->extend('Drupal\search\SearchQuery') ->extend('Drupal\Core\Database\Query\PagerSelectExtender'); $query->join('node', 'n', 'n.nid = i.sid'); + $query->join('node_property_data', 'npd', 'npd.nid = n.nid'); $query - ->condition('n.status', 1) + ->condition('npd.status', 1) ->addTag('node_access') ->searchExpression($keys, 'node'); // Insert special keywords. $query->setOption('type', 'n.type'); - $query->setOption('langcode', 'n.langcode'); + $query->setOption('langcode', 'npd.langcode'); if ($query->setOption('term', 'ti.tid')) { - $query->join('taxonomy_index', 'ti', 'n.nid = ti.nid'); + $query->join('taxonomy_index', 'ti', 'npd.nid = ti.nid'); } // Only continue if the first pass query matches. if (!$query->executeFirstPass()) { @@ -1478,12 +1480,12 @@ function node_ranking() { 'sticky' => array( 'title' => t('Content is sticky at top of lists'), // The sticky flag is either 0 or 1, which is automatically normalized. - 'score' => 'n.sticky', + 'score' => 'npd.sticky', ), 'promote' => array( 'title' => t('Content is promoted to the front page'), // The promote flag is either 0 or 1, which is automatically normalized. - 'score' => 'n.promote', + 'score' => 'npd.promote', ), ); @@ -1492,7 +1494,7 @@ function node_ranking() { $ranking['recent'] = array( 'title' => t('Recently posted'), // Exponential decay with half-life of 6 months, starting at last indexed node - 'score' => 'POW(2.0, (GREATEST(n.created, n.changed) - :node_cron_last) * 6.43e-8)', + 'score' => 'POW(2.0, (GREATEST(npd.created, npd.changed) - :node_cron_last) * 6.43e-8)', 'arguments' => array(':node_cron_last' => $node_cron_last), ); } @@ -1507,8 +1509,9 @@ function node_user_cancel($edit, $account, $method) { case 'user_cancel_block_unpublish': // Unpublish nodes (current revisions). module_load_include('inc', 'node', 'node.admin'); - $nodes = db_select('node', 'n') - ->fields('n', array('nid')) + $nodes = db_select('node_property_data', 'npd') + ->distinct(TRUE) + ->fields('npd', array('nid')) ->condition('uid', $account->uid) ->execute() ->fetchCol(); @@ -1518,14 +1521,15 @@ function node_user_cancel($edit, $account, $method) { case 'user_cancel_reassign': // Anonymize nodes (current revisions). module_load_include('inc', 'node', 'node.admin'); - $nodes = db_select('node', 'n') - ->fields('n', array('nid')) + $nodes = db_select('node_property_data', 'npd') + ->distinct(TRUE) + ->fields('npd', array('nid')) ->condition('uid', $account->uid) ->execute() ->fetchCol(); node_mass_update($nodes, array('uid' => 0)); // Anonymize old revisions. - db_update('node_revision') + db_update('node_property_revision') ->fields(array('uid' => 0)) ->condition('uid', $account->uid) ->execute(); @@ -1543,14 +1547,15 @@ function node_user_cancel($edit, $account, $method) { function node_user_predelete($account) { // Delete nodes (current revisions). // @todo Introduce node_mass_delete() or make node_mass_update() more flexible. - $nodes = db_select('node', 'n') - ->fields('n', array('nid')) + $nodes = db_select('node_property_data', 'npd') + ->distinct(TRUE) + ->fields('npd', array('nid')) ->condition('uid', $account->uid) ->execute() ->fetchCol(); node_delete_multiple($nodes); // Delete old revisions. - $revisions = db_query('SELECT vid FROM {node_revision} WHERE uid = :uid', array(':uid' => $account->uid))->fetchCol(); + $revisions = db_query('SELECT vid FROM {node_property_revision} WHERE uid = :uid', array(':uid' => $account->uid))->fetchCol(); foreach ($revisions as $revision) { node_revision_delete($revision); } @@ -1649,7 +1654,7 @@ function _node_revision_access(Node $node, $op = 'view', $account = NULL, $langc // different revisions so there is no need for a separate database check. // Also, if you try to revert to or delete the default revision, that's // not good. - if ($node->isDefaultRevision() && (db_query('SELECT COUNT(vid) FROM {node_revision} WHERE nid = :nid', array(':nid' => $node->nid))->fetchField() == 1 || $op == 'update' || $op == 'delete')) { + if ($node->isDefaultRevision() && (db_query('SELECT COUNT(vid) FROM {node_property_revision} WHERE nid = :nid', array(':nid' => $node->nid))->fetchField() == 1 || $op == 'update' || $op == 'delete')) { $access[$cid] = FALSE; } elseif (user_access('administer nodes', $account)) { @@ -1920,7 +1925,7 @@ function node_page_title(Node $node) { * A unix timestamp indicating the last time the node was changed. */ function node_last_changed($nid) { - return db_query('SELECT changed FROM {node} WHERE nid = :nid', array(':nid' => $nid))->fetch()->changed; + return db_query('SELECT changed FROM {node_property_data} WHERE nid = :nid ORDER BY changed DESC LIMIT 1', array(':nid' => $nid))->fetch()->changed; } /** @@ -1934,7 +1939,15 @@ function node_last_changed($nid) { */ function node_revision_list(Node $node) { $revisions = array(); - $result = db_query('SELECT r.vid, r.title, r.log, r.uid, n.vid AS current_vid, r.timestamp, u.name FROM {node_revision} r LEFT JOIN {node} n ON n.vid = r.vid INNER JOIN {users} u ON u.uid = r.uid WHERE r.nid = :nid ORDER BY r.vid DESC', array(':nid' => $node->nid)); + $result = db_query('SELECT npr.vid, npr.title, npr.log, npr.uid, n.vid AS current_vid, npr.timestamp, u.name ' . + 'FROM {node_property_revision} npr ' . + 'LEFT JOIN {node} n ON n.vid = npr.vid ' . + 'LEFT JOIN {node_property_data} npd ON npd.nid = n.nid ' . + 'INNER JOIN {users} u ON u.uid = npr.uid ' . + 'WHERE npr.nid = :nid ' . + 'ORDER BY npr.vid DESC', + array(':nid' => $node->nid) + ); foreach ($result as $revision) { $revisions[$revision->vid] = $revision; } @@ -2025,26 +2038,26 @@ function node_block_save($delta = '', $edit = array()) { * nodes visible to the current user. */ function node_get_recent($number = 10) { - $query = db_select('node', 'n'); + $query = db_select('node_property_data', 'npd'); if (!user_access('bypass node access')) { // If the user is able to view their own unpublished nodes, allow them // to see these in addition to published nodes. Check that they actually // have some unpublished nodes to view before adding the condition. - if (user_access('view own unpublished content') && $own_unpublished = db_query('SELECT nid FROM {node} WHERE uid = :uid AND status = :status', array(':uid' => $GLOBALS['user']->uid, ':status' => NODE_NOT_PUBLISHED))->fetchCol()) { + if (user_access('view own unpublished content') && $own_unpublished = db_query('SELECT DISTINCT nid FROM {node_property_data} WHERE uid = :uid AND status = :status', array(':uid' => $GLOBALS['user']->uid, ':status' => NODE_NOT_PUBLISHED))->fetchCol()) { $query->condition(db_or() - ->condition('n.status', NODE_PUBLISHED) - ->condition('n.nid', $own_unpublished, 'IN') + ->condition('npd.status', NODE_PUBLISHED) + ->condition('npd.nid', $own_unpublished, 'IN') ); } else { // If not, restrict the query to published nodes. - $query->condition('n.status', NODE_PUBLISHED); + $query->condition('npd.status', NODE_PUBLISHED); } } $nids = $query - ->fields('n', array('nid')) - ->orderBy('n.changed', 'DESC') + ->fields('npd', array('nid')) + ->orderBy('npd.changed', 'DESC') ->range(0, $number) ->addTag('node_access') ->execute() @@ -2296,11 +2309,12 @@ function node_feed($nids = FALSE, $channel = array()) { $rss_config = config('system.rss'); if ($nids === FALSE) { - $nids = db_select('node', 'n') - ->fields('n', array('nid', 'created')) - ->condition('n.promote', 1) - ->condition('n.status', 1) - ->orderBy('n.created', 'DESC') + $nids = db_select('node_property_data', 'npd') + ->fields('npd', array('nid', 'created')) + ->condition('npd.promote', 1) + ->condition('npd.status', 1) + ->groupBy('npd.nid') + ->orderBy('npd.created', 'DESC') ->range(0, $rss_config->get('items.limit')) ->addTag('node_access') ->execute() @@ -2407,12 +2421,13 @@ function node_view_multiple($nodes, $view_mode = 'teaser', $langcode = NULL) { */ function node_page_default() { $site_config = config('system.site'); - $select = db_select('node', 'n') - ->fields('n', array('nid', 'sticky', 'created')) - ->condition('n.promote', 1) - ->condition('n.status', 1) - ->orderBy('n.sticky', 'DESC') - ->orderBy('n.created', 'DESC') + $select = db_select('node_property_data', 'npd') + ->fields('npd', array('nid', 'sticky', 'created')) + ->condition('npd.promote', 1) + ->condition('npd.status', 1) + ->groupBy('npd.nid') + ->orderBy('npd.sticky', 'DESC') + ->orderBy('npd.created', 'DESC') ->extend('Drupal\Core\Database\Query\PagerSelectExtender') ->limit(variable_get('default_nodes_main', 10)) ->addTag('node_access'); @@ -3140,7 +3155,7 @@ function _node_query_node_access_alter($query, $type) { if (!($table_info instanceof SelectInterface)) { $table = $table_info['table']; // If the node table is in the query, it wins immediately. - if ($table == 'node') { + if ($table == 'node' || $table == 'node_property_data') { $base_table = $table; break; } diff --git a/core/modules/node/node.pages.inc b/core/modules/node/node.pages.inc index a3afa7f..4efec1a 100644 --- a/core/modules/node/node.pages.inc +++ b/core/modules/node/node.pages.inc @@ -380,7 +380,7 @@ function node_revision_delete_confirm_submit($form, &$form_state) { watchdog('content', '@type: deleted %title revision %revision.', array('@type' => $node_revision->type, '%title' => $node_revision->label(), '%revision' => $node_revision->vid)); drupal_set_message(t('Revision from %revision-date of @type %title has been deleted.', array('%revision-date' => format_date($node_revision->revision_timestamp), '@type' => node_get_type_label($node_revision), '%title' => $node_revision->label()))); $form_state['redirect'] = 'node/' . $node_revision->nid; - if (db_query('SELECT COUNT(vid) FROM {node_revision} WHERE nid = :nid', array(':nid' => $node_revision->nid))->fetchField() > 1) { + if (db_query('SELECT COUNT(vid) FROM {node_property_revision} WHERE nid = :nid', array(':nid' => $node_revision->nid))->fetchField() > 1) { $form_state['redirect'] .= '/revisions'; } } diff --git a/core/modules/poll/lib/Drupal/poll/Tests/PollExpirationTest.php b/core/modules/poll/lib/Drupal/poll/Tests/PollExpirationTest.php index 2cfe9e5..385d993 100644 --- a/core/modules/poll/lib/Drupal/poll/Tests/PollExpirationTest.php +++ b/core/modules/poll/lib/Drupal/poll/Tests/PollExpirationTest.php @@ -58,8 +58,8 @@ function testAutoExpire() { // Test expiration. Since REQUEST_TIME is a constant and we don't // want to keep SimpleTest waiting until the moment of expiration arrives, // we forcibly change the expiration date in the database. - $created = db_query('SELECT created FROM {node} WHERE nid = :nid', array(':nid' => $poll_nid))->fetchField(); - db_update('node') + $created = db_query('SELECT created FROM {node_property_data} WHERE nid = :nid', array(':nid' => $poll_nid))->fetchField(); + db_update('node_property_data') ->fields(array('created' => $created - ($poll_expiration * 1.01))) ->condition('nid', $poll_nid) ->execute(); diff --git a/core/modules/poll/poll.module b/core/modules/poll/poll.module index c85f121..0ac65ee 100644 --- a/core/modules/poll/poll.module +++ b/core/modules/poll/poll.module @@ -137,12 +137,13 @@ function poll_block_info() { function poll_block_view($delta = '') { if (user_access('access content')) { // Retrieve the latest poll. - $select = db_select('node', 'n'); - $select->join('poll', 'p', 'p.nid = n.nid'); - $select->fields('n', array('nid')) - ->condition('n.status', 1) + $select = db_select('node_property_data', 'npd'); + $select->join('poll', 'p', 'p.nid = npd.nid'); + $select->fields('npd', array('nid')) + ->distinct(TRUE) + ->condition('npd.status', 1) ->condition('p.active', 1) - ->orderBy('n.created', 'DESC') + ->orderBy('npd.created', 'DESC') ->range(0, 1) ->addTag('node_access'); @@ -165,7 +166,7 @@ function poll_block_view($delta = '') { * Closes polls that have exceeded their allowed runtime. */ function poll_cron() { - $nids = db_query('SELECT p.nid FROM {poll} p INNER JOIN {node} n ON p.nid = n.nid WHERE (n.created + p.runtime) < :request_time AND p.active = :active AND p.runtime <> :runtime', array(':request_time' => REQUEST_TIME, ':active' => 1, ':runtime' => 0))->fetchCol(); + $nids = db_query('SELECT p.nid FROM {poll} p INNER JOIN {node_property_data} npd ON p.nid = npd.nid WHERE (npd.created + p.runtime) < :request_time AND p.active = :active AND p.runtime <> :runtime', array(':request_time' => REQUEST_TIME, ':active' => 1, ':runtime' => 0))->fetchCol(); if (!empty($nids)) { db_update('poll') ->fields(array('active' => 0)) diff --git a/core/modules/poll/poll.pages.inc b/core/modules/poll/poll.pages.inc index b67d8f9..da1138c 100644 --- a/core/modules/poll/poll.pages.inc +++ b/core/modules/poll/poll.pages.inc @@ -16,24 +16,24 @@ function poll_page() { $polls_per_page = 15; - $count_select = db_select('node', 'n'); + $count_select = db_select('node_property_data', 'npd'); $count_select->addExpression('COUNT(*)', 'expression'); - $count_select->join('poll', 'p', 'p.nid = n.nid'); - $count_select->condition('n.status', 1); + $count_select->join('poll', 'p', 'p.nid = npd.nid'); + $count_select->condition('npd.status', 1); + $count_select->groupBy('npd.nid'); // List all polls. - $select = db_select('node', 'n'); - $select->join('poll', 'p', 'p.nid = n.nid'); - $select->join('poll_choice', 'c', 'c.nid = n.nid'); + $select = db_select('node_property_data', 'npd'); + $select->join('poll', 'p', 'p.nid = npd.nid'); + $select->join('poll_choice', 'c', 'c.nid = npd.nid'); $select->addExpression('SUM(c.chvotes)', 'votes'); - $select = $select->fields('n', array('nid', 'title', 'created')) + $select = $select->fields('npd', array('nid', 'title', 'created')) ->fields('p', array('active')) - ->condition('n.status', 1) - ->orderBy('n.created', 'DESC') - ->groupBy('n.nid') - ->groupBy('n.title') + ->condition('npd.status', 1) + ->orderBy('npd.created', 'DESC') + ->groupBy('npd.nid') ->groupBy('p.active') - ->groupBy('n.created') + ->groupBy('npd.created') ->extend('Drupal\Core\Database\Query\PagerSelectExtender') ->limit($polls_per_page) ->addTag('node_access'); @@ -41,10 +41,11 @@ function poll_page() { $queried_nodes = $select->execute() ->fetchAllAssoc('nid'); + $node_entities = node_load_multiple(array_keys($queried_nodes)); + $output = ''; $output .= theme('pager'); diff --git a/core/modules/search/search.api.php b/core/modules/search/search.api.php index 0dd0a84..a643b03 100644 --- a/core/modules/search/search.api.php +++ b/core/modules/search/search.api.php @@ -117,8 +117,8 @@ function hook_search_reset() { * @ingroup search */ function hook_search_status() { - $total = db_query('SELECT COUNT(*) FROM {node} WHERE status = 1')->fetchField(); - $remaining = db_query("SELECT COUNT(*) FROM {node} n LEFT JOIN {search_dataset} d ON d.type = 'node' AND d.sid = n.nid WHERE n.status = 1 AND d.sid IS NULL OR d.reindex <> 0")->fetchField(); + $total = db_query('SELECT COUNT(*) FROM {example} WHERE status = 1')->fetchField(); + $remaining = db_query("SELECT COUNT(*) FROM {example} e LEFT JOIN {search_dataset} d ON d.type = 'example' AND d.sid = e.id WHERE e.status = 1 AND d.sid IS NULL OR d.reindex <> 0")->fetchField(); return array('remaining' => $remaining, 'total' => $total); } @@ -198,17 +198,17 @@ function hook_search_execute($keys = NULL, $conditions = NULL) { $query = db_select('search_index', 'i', array('target' => 'slave')) ->extend('Drupal\search\SearchQuery') ->extend('Drupal\Core\Database\Query\PagerSelectExtender'); - $query->join('node', 'n', 'n.nid = i.sid'); + $query->join('example', 'e', 'e.id = i.sid'); $query - ->condition('n.status', 1) - ->addTag('node_access') - ->searchExpression($keys, 'node'); + ->condition('e.status', 1) + ->addTag('example_access') + ->searchExpression($keys, 'example'); // Insert special keywords. - $query->setOption('type', 'n.type'); - $query->setOption('langcode', 'n.langcode'); + $query->setOption('type', 'e.type'); + $query->setOption('langcode', 'e.langcode'); if ($query->setOption('term', 'ti.tid')) { - $query->join('taxonomy_index', 'ti', 'n.nid = ti.nid'); + $query->join('taxonomy_index', 'ti', 'e.id = ti.id'); } // Only continue if the first pass query matches. if (!$query->executeFirstPass()) { @@ -225,29 +225,29 @@ function hook_search_execute($keys = NULL, $conditions = NULL) { $results = array(); foreach ($find as $item) { // Render the node. - $node = node_load($item->sid); - $build = node_view($node, 'search_result', $item->langcode); + $example = example_load($item->sid); + $build = example_view($example, 'search_result', $item->langcode); unset($build['#theme']); - $node->rendered = drupal_render($build); + $example->rendered = drupal_render($build); // Fetch comments for snippet. - $node->rendered .= ' ' . module_invoke('comment', 'node_update_index', $node, $item->langcode); + $example->rendered .= ' ' . module_invoke('comment', 'node_update_index', $example, $item->langcode); - $extra = module_invoke_all('node_search_result', $node, $item->langcode); + $extra = module_invoke_all('node_search_result', $example, $item->langcode); $language = language_load($item->langcode); - $uri = $node->uri(); + $uri = $example->uri(); $results[] = array( 'link' => url($uri['path'], array_merge($uri['options'], array('absolute' => TRUE, 'language' => $language))), - 'type' => check_plain(node_get_type_label($node)), - 'title' => $node->label($item->langcode), - 'user' => theme('username', array('account' => $node)), - 'date' => $node->get('changed', $item->langcode), - 'node' => $node, + 'type' => check_plain(node_get_type_label($example)), + 'title' => $example->label($item->langcode), + 'user' => theme('username', array('account' => $example)), + 'date' => $example->get('changed', $item->langcode), + 'node' => $example, 'extra' => $extra, 'score' => $item->calculated_score, - 'snippet' => search_excerpt($keys, $node->rendered, $item->langcode), - 'langcode' => $node->langcode, + 'snippet' => search_excerpt($keys, $example->rendered, $item->langcode), + 'langcode' => $example->langcode, ); } return $results; diff --git a/core/modules/search/search.module b/core/modules/search/search.module index 315ae7a..5747283 100644 --- a/core/modules/search/search.module +++ b/core/modules/search/search.module @@ -647,10 +647,9 @@ function search_index($sid, $module, $text, $langcode) { if (preg_match('!(?:node|book)/(?:view/)?([0-9]+)!i', $path, $match)) { $linknid = $match[1]; if ($linknid > 0) { - $node = db_query('SELECT title, nid, vid FROM {node} WHERE nid = :nid', array(':nid' => $linknid), array('target' => 'slave'))->fetchObject(); $link = TRUE; - // Do not use $node->label(), $node comes from the database. - $linktitle = $node->title; + $node = node_load($linknid); + $linktitle = $node->label(); } } } diff --git a/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php b/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php index c5dbd0b..c38415d 100644 --- a/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php +++ b/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php @@ -240,7 +240,7 @@ protected function drupalCreateNode($settings = array()) { $node->save(); // Small hack to link revisions to our test user. - db_update('node_revision') + db_update('node_property_revision') ->fields(array('uid' => $node->uid)) ->condition('vid', $node->vid) ->execute(); diff --git a/core/modules/statistics/statistics.module b/core/modules/statistics/statistics.module index 8469e52..f653fb3 100644 --- a/core/modules/statistics/statistics.module +++ b/core/modules/statistics/statistics.module @@ -263,17 +263,18 @@ function statistics_cron() { */ function statistics_title_list($dbfield, $dbrows) { if (in_array($dbfield, array('totalcount', 'daycount', 'timestamp'))) { - $query = db_select('node', 'n'); + $query = db_select('node_property_data', 'npd'); $query->addTag('node_access'); - $query->join('node_counter', 's', 'n.nid = s.nid'); - $query->join('users', 'u', 'n.uid = u.uid'); + $query->join('node_counter', 's', 'npd.nid = s.nid'); + $query->join('users', 'u', 'npd.uid = u.uid'); return $query - ->fields('n', array('nid', 'title')) + ->fields('npd', array('nid', 'title')) ->fields('u', array('uid', 'name')) ->condition($dbfield, 0, '<>') - ->condition('n.status', 1) + ->condition('npd.status', 1) ->orderBy($dbfield, 'DESC') + ->groupBy('npd.nid') ->range(0, $dbrows) ->execute(); } diff --git a/core/modules/system/lib/Drupal/system/Tests/Database/RegressionTest.php b/core/modules/system/lib/Drupal/system/Tests/Database/RegressionTest.php index 4e4f364..df0e530 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Database/RegressionTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/Database/RegressionTest.php @@ -64,7 +64,7 @@ function testDBFieldExists() { * Tests the db_index_exists() function. */ function testDBIndexExists() { - $this->assertIdentical(TRUE, db_index_exists('node', 'node_created'), 'Returns true for existent index.'); + $this->assertIdentical(TRUE, db_index_exists('node', 'node_type'), 'Returns true for existent index.'); $this->assertIdentical(FALSE, db_index_exists('node', 'nosuchindex'), 'Returns false for nonexistent index.'); } } diff --git a/core/modules/tracker/tracker.module b/core/modules/tracker/tracker.module index 7060a87..8ecbb1e 100644 --- a/core/modules/tracker/tracker.module +++ b/core/modules/tracker/tracker.module @@ -83,7 +83,7 @@ function tracker_cron() { $batch_size = config('tracker.settings')->get('cron_index_limit'); if ($max_nid > 0) { $last_nid = FALSE; - $result = db_query_range('SELECT nid, uid, status FROM {node} WHERE nid <= :max_nid ORDER BY nid DESC', 0, $batch_size, array(':max_nid' => $max_nid), array('target' => 'slave')); + $result = db_query_range('SELECT nid, uid, status FROM {node_property_data} WHERE nid <= :max_nid GROUP BY nid ORDER BY nid DESC', 0, $batch_size, array(':max_nid' => $max_nid), array('target' => 'slave')); $count = 0; @@ -268,7 +268,7 @@ function tracker_comment_delete($comment) { * The node updated timestamp or comment timestamp. */ function _tracker_add($nid, $uid, $changed) { - $node = db_query('SELECT nid, status, uid, changed FROM {node} WHERE nid = :nid', array(':nid' => $nid))->fetchObject(); + $node = db_query('SELECT nid, status, uid, changed FROM {node_property_data} WHERE nid = :nid ORDER BY changed DESC, status DESC LIMIT 1', array(':nid' => $nid))->fetchObject(); // Adding a comment can only increase the changed timestamp, so our // calculation here is simple. @@ -307,7 +307,7 @@ function _tracker_add($nid, $uid, $changed) { * is the greatest. */ function _tracker_calculate_changed($nid) { - $changed = db_query('SELECT changed FROM {node} WHERE nid = :nid', array(':nid' => $nid), array('target' => 'slave'))->fetchField(); + $changed = db_query('SELECT changed FROM {node_property_data} WHERE nid = :nid ORDER BY changed DESC LIMIT 1', array(':nid' => $nid), array('target' => 'slave'))->fetchField(); $latest_comment = db_query_range('SELECT cid, changed FROM {comment} WHERE nid = :nid AND status = :status ORDER BY changed DESC', 0, 1, array( ':nid' => $nid, ':status' => COMMENT_PUBLISHED, @@ -329,7 +329,7 @@ function _tracker_calculate_changed($nid) { * The last changed timestamp of the node. */ function _tracker_remove($nid, $uid = NULL, $changed = NULL) { - $node = db_query('SELECT nid, status, uid, changed FROM {node} WHERE nid = :nid', array(':nid' => $nid))->fetchObject(); + $node = db_query('SELECT nid, status, uid, changed FROM {node_property_data} WHERE nid = :nid ORDER BY changed DESC, status DESC LIMIT 1', array(':nid' => $nid))->fetchObject(); // The user only keeps his or her subscription if both of the following are true: // (1) The node exists. diff --git a/core/modules/tracker/tracker.pages.inc b/core/modules/tracker/tracker.pages.inc index 853f50d..131eba7 100644 --- a/core/modules/tracker/tracker.pages.inc +++ b/core/modules/tracker/tracker.pages.inc @@ -50,9 +50,20 @@ function tracker_page($account = NULL, $set_title = FALSE) { $rows = array(); if (!empty($nodes)) { // Now, get the data and put into the placeholder array. - $result = db_query('SELECT n.nid, n.title, n.type, n.changed, n.uid, u.name, l.comment_count FROM {node} n INNER JOIN {node_comment_statistics} l ON n.nid = l.nid INNER JOIN {users} u ON n.uid = u.uid WHERE n.nid IN (:nids)', array(':nids' => array_keys($nodes)), array('target' => 'slave')); + $result = db_query('SELECT npd.nid, npd.title, n.type, npd.changed, npd.uid, u.name, l.comment_count ' . + 'FROM {node_property_data} npd ' . + 'INNER JOIN {node} n ON n.nid = npd.nid ' . + 'INNER JOIN {node_comment_statistics} l ON npd.nid = l.nid ' . + 'INNER JOIN {users} u ON npd.uid = u.uid ' . + 'WHERE npd.nid IN (:nids) ' . + 'GROUP BY npd.nid ' . + 'ORDER BY npd.changed DESC ', + array(':nids' => array_keys($nodes)), array('target' => 'slave') + ); + $node_entities = node_load_multiple(array_keys($nodes)); foreach ($result as $node) { $node->last_activity = $nodes[$node->nid]->changed; + $node->title = $node_entities[$node->nid]->label(); $nodes[$node->nid] = $node; } diff --git a/core/modules/translation/translation.module b/core/modules/translation/translation.module index e7f22fd..e47cb4f 100644 --- a/core/modules/translation/translation.module +++ b/core/modules/translation/translation.module @@ -486,11 +486,13 @@ function translation_node_get_translations($tnid) { if (!isset($translations[$tnid])) { $translations[$tnid] = array(); - $result = db_select('node', 'n') - ->fields('n', array('nid', 'type', 'uid', 'status', 'title', 'langcode')) + $query = db_select('node_property_data', 'npd'); + $query->innerJoin('node', 'n', 'n.nid = npd.nid AND n.langcode = npd.langcode'); + $query->fields('npd', array('nid', 'uid', 'status', 'title', 'langcode')) + ->fields('n', array('type')) ->condition('n.tnid', $tnid) - ->addTag('node_access') - ->execute(); + ->addTag('node_access'); + $result = $query->execute(); foreach ($result as $node) { $translations[$tnid][$node->langcode] = $node; diff --git a/core/modules/user/user.api.php b/core/modules/user/user.api.php index 0c3002c..ec4bdea 100644 --- a/core/modules/user/user.api.php +++ b/core/modules/user/user.api.php @@ -104,8 +104,8 @@ function hook_user_cancel($edit, $account, $method) { case 'user_cancel_block_unpublish': // Unpublish nodes (current revisions). module_load_include('inc', 'node', 'node.admin'); - $nodes = db_select('node', 'n') - ->fields('n', array('nid')) + $nodes = db_select('node_property_data', 'npd') + ->fields('npd', array('nid')) ->condition('uid', $account->uid) ->execute() ->fetchCol(); @@ -115,14 +115,14 @@ function hook_user_cancel($edit, $account, $method) { case 'user_cancel_reassign': // Anonymize nodes (current revisions). module_load_include('inc', 'node', 'node.admin'); - $nodes = db_select('node', 'n') - ->fields('n', array('nid')) + $nodes = db_select('node_property_data', 'npd') + ->fields('npd', array('nid')) ->condition('uid', $account->uid) ->execute() ->fetchCol(); node_mass_update($nodes, array('uid' => 0)); // Anonymize old revisions. - db_update('node_revision') + db_update('node_property_revision') ->fields(array('uid' => 0)) ->condition('uid', $account->uid) ->execute();