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