From a0f2f83056fa10e8c20a53b9611e31c4e56dc76d Mon Sep 17 00:00:00 2001 From: Fabian Franz <fabian@tag1consulting.com> Date: Tue, 14 Jan 2025 00:21:34 +0100 Subject: [PATCH 1/4] Add explain for views_ui --- core/modules/views_ui/src/ViewUI.php | 59 +++++++++++++++++++++++++++- 1 file changed, 57 insertions(+), 2 deletions(-) diff --git a/core/modules/views_ui/src/ViewUI.php b/core/modules/views_ui/src/ViewUI.php index 6fced03dd17f..e5fad716733a 100644 --- a/core/modules/views_ui/src/ViewUI.php +++ b/core/modules/views_ui/src/ViewUI.php @@ -630,14 +630,15 @@ 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 Sql) { $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 +663,60 @@ public function renderPreview($display_id, $args = []) { ], ], ]; + + $explain_output = ''; + + // Execute EXPLAIN + try { + $explain_results = $connection->query('EXPLAIN ' . $query_string, $query_string->getArguments())->fetchAll(); + + // Get headers from first row + if (!empty($explain_results)) { + $headers = array_keys((array) reset($explain_results)); + + // Format rows + $table_rows = []; + foreach ($explain_results as $row) { + $table_rows[] = array_values((array) $row); + } + + $explain_output = [ + '#theme' => 'table', + '#header' => $headers, + '#rows' => $table_rows, + '#attributes' => [ + 'class' => ['explain-query-table'], + ], + ]; + } + else { + $explain_output = [ + '#markup' => $this->t('No EXPLAIN results available.'), + ]; + } + } + catch (\Exception $e) { + $explain_output = [ + '#markup' => $this->t('Unable to execute EXPLAIN: @message', ['@message' => $e->getMessage()]), + ]; + } + + $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>', -- GitLab From d843564e69e3f172f98d05d68263907de1ad2e95 Mon Sep 17 00:00:00 2001 From: Fabian Franz <fabian@tag1consulting.com> Date: Tue, 14 Jan 2025 17:19:01 +0100 Subject: [PATCH 2/4] Ensure explain output has it's own helper function. --- core/modules/views_ui/src/ViewUI.php | 91 ++++++++++++++++------------ 1 file changed, 53 insertions(+), 38 deletions(-) diff --git a/core/modules/views_ui/src/ViewUI.php b/core/modules/views_ui/src/ViewUI.php index e5fad716733a..0908234ad2be 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(); @@ -634,10 +683,11 @@ public function renderPreview($display_id, $args = []) { if ($show_query) { $query_string = $executable->build_info['query']; + // Only the sql default class has a method getArguments. $quoted = []; - if ($query_string instanceof Sql) { + if ($query_string instanceof SelectInterface) { $quoted = $query_string->getArguments(); foreach ($quoted as $key => $val) { if (is_array($val)) { @@ -664,42 +714,7 @@ public function renderPreview($display_id, $args = []) { ], ]; - $explain_output = ''; - - // Execute EXPLAIN - try { - $explain_results = $connection->query('EXPLAIN ' . $query_string, $query_string->getArguments())->fetchAll(); - - // Get headers from first row - if (!empty($explain_results)) { - $headers = array_keys((array) reset($explain_results)); - - // Format rows - $table_rows = []; - foreach ($explain_results as $row) { - $table_rows[] = array_values((array) $row); - } - - $explain_output = [ - '#theme' => 'table', - '#header' => $headers, - '#rows' => $table_rows, - '#attributes' => [ - 'class' => ['explain-query-table'], - ], - ]; - } - else { - $explain_output = [ - '#markup' => $this->t('No EXPLAIN results available.'), - ]; - } - } - catch (\Exception $e) { - $explain_output = [ - '#markup' => $this->t('Unable to execute EXPLAIN: @message', ['@message' => $e->getMessage()]), - ]; - } + $explain_output = $this->getExplainOutput($connection, $query_string); $rows['query'][] = [ [ -- GitLab From ee30a3f7fea5cadf47df26b1d3f097594b95076c Mon Sep 17 00:00:00 2001 From: Fabian Franz <fabian@tag1consulting.com> Date: Thu, 16 Jan 2025 01:50:12 +0100 Subject: [PATCH 3/4] Fix code standards violation --- core/modules/views_ui/src/ViewUI.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/modules/views_ui/src/ViewUI.php b/core/modules/views_ui/src/ViewUI.php index 0908234ad2be..4f7fe4d19bf9 100644 --- a/core/modules/views_ui/src/ViewUI.php +++ b/core/modules/views_ui/src/ViewUI.php @@ -727,7 +727,7 @@ public function renderPreview($display_id, $args = []) { 'data' => [ '#type' => 'inline_template', '#template' => '<pre>{{ explain }}</pre>', - '#context' => ['explain' => $explain_output ], + '#context' => ['explain' => $explain_output], ], ], ]; -- GitLab From 02fb3cb26b9314e2b67cf953c0987d9dd4e5acb4 Mon Sep 17 00:00:00 2001 From: Lennard Swaneveld <lennard@ezcompany.nl> Date: Mon, 20 Jan 2025 16:55:31 +0100 Subject: [PATCH 4/4] Add some test coverage --- core/modules/views_ui/tests/src/Functional/PreviewTest.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/modules/views_ui/tests/src/Functional/PreviewTest.php b/core/modules/views_ui/tests/src/Functional/PreviewTest.php index e0baf9193c05..889e70581246 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')); -- GitLab