diff --git a/core/modules/views_ui/src/ViewUI.php b/core/modules/views_ui/src/ViewUI.php index 6fced03dd17fe637acc3aa23deb5906bae6208ef..4f7fe4d19bf98ae4a0caa580f36aec3927a70875 100644 --- a/core/modules/views_ui/src/ViewUI.php +++ b/core/modules/views_ui/src/ViewUI.php @@ -13,8 +13,8 @@ use Drupal\Core\Entity\EntityStorageInterface; use Drupal\views\ViewExecutable; use Drupal\Core\Database\Database; +use Drupal\Core\Database\Query\SelectInterface; use Drupal\Core\Session\AccountInterface; -use Drupal\views\Plugin\views\query\Sql; use Drupal\views\Entity\View; use Drupal\views\ViewEntityInterface; use Drupal\Core\Routing\RouteObjectInterface; @@ -524,6 +524,55 @@ public function endQueryCapture() { $this->additionalQueries = $queries; } + /** + * Gets the EXPLAIN output for a query. + * + * @param \Drupal\Core\Database\Connection $connection + * The database connection. + * @param string|\Drupal\views\Plugin\views\query\Sql $query_string + * The query string or SQL query object. + * + * @return array + * Renderable array containing the explain output. + */ + protected function getExplainOutput($connection, $query_string) { + $args = []; + if ($query_string instanceof SelectInterface) { + $args = $query_string->getArguments(); + } + + try { + $explain_results = $connection->query('EXPLAIN ' . $query_string, $args)->fetchAll(); + } + catch (\Exception $e) { + return [ + '#markup' => t('Unable to execute EXPLAIN: @message', ['@message' => $e->getMessage()]), + ]; + } + if (empty($explain_results)) { + return [ + '#markup' => t('No EXPLAIN results available.'), + ]; + } + + $headers = array_keys((array) $explain_results[0]); + + // Format rows + $table_rows = []; + foreach ($explain_results as $row) { + $table_rows[] = array_values((array) $row); + } + + return [ + '#theme' => 'table', + '#header' => $headers, + '#rows' => $table_rows, + '#attributes' => [ + 'class' => ['explain-query-table'], + ], + ]; + } + public function renderPreview($display_id, $args = []) { // Save the current path so it can be restored before returning from this function. $request_stack = \Drupal::requestStack(); @@ -630,14 +679,16 @@ public function renderPreview($display_id, $args = []) { if ($show_info || $show_query || $show_stats) { // Get information from the preview for display. if (!empty($executable->build_info['query'])) { + $connection = Database::getConnection(); + if ($show_query) { $query_string = $executable->build_info['query']; + // Only the sql default class has a method getArguments. $quoted = []; - if ($executable->query instanceof Sql) { + if ($query_string instanceof SelectInterface) { $quoted = $query_string->getArguments(); - $connection = Database::getConnection(); foreach ($quoted as $key => $val) { if (is_array($val)) { $quoted[$key] = implode(', ', array_map([$connection, 'quote'], $val)); @@ -662,6 +713,25 @@ public function renderPreview($display_id, $args = []) { ], ], ]; + + $explain_output = $this->getExplainOutput($connection, $query_string); + + $rows['query'][] = [ + [ + 'data' => [ + '#type' => 'inline_template', + '#template' => "<strong>{% trans 'Explain' %}</strong>", + ], + ], + [ + 'data' => [ + '#type' => 'inline_template', + '#template' => '<pre>{{ explain }}</pre>', + '#context' => ['explain' => $explain_output], + ], + ], + ]; + if (!empty($this->additionalQueries)) { $queries[] = [ '#prefix' => '<strong>', diff --git a/core/modules/views_ui/tests/src/Functional/PreviewTest.php b/core/modules/views_ui/tests/src/Functional/PreviewTest.php index e0baf9193c053916d737f7d6d287d38646c65c49..889e7058124621ebe0e1fc638f41750648a33bcb 100644 --- a/core/modules/views_ui/tests/src/Functional/PreviewTest.php +++ b/core/modules/views_ui/tests/src/Functional/PreviewTest.php @@ -115,6 +115,7 @@ public function testPreviewUI(): void { $this->assertSession()->pageTextContains('Query execute time'); $this->assertSession()->pageTextContains('View render time'); $this->assertSession()->responseNotContains('<strong>Query</strong>'); + $this->assertSession()->responseNotContains('<strong>Explain</strong>'); // Statistics and query. $settings->set('ui.show.sql_query.enabled', TRUE)->save(); @@ -130,6 +131,7 @@ public function testPreviewUI(): void { WHERE (views_test_data.id = '100') SQL; $this->assertSession()->assertEscaped($query_string); + $this->assertSession()->responseContains('<strong>Explain</strong>'); // Test that the statistics and query are rendered above the preview. $this->assertLessThan(strpos($this->getSession()->getPage()->getContent(), 'js-view-dom-id'), strpos($this->getSession()->getPage()->getContent(), 'views-query-info'));