This is an automated email from the ASF dual-hosted git repository.

epugh pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/solr.git


The following commit(s) were added to refs/heads/main by this push:
     new cdc1707abb4 SOLR-16640: Admin UI's SQL screen shows an error instead 
of crashing (#5057)
cdc1707abb4 is described below

commit cdc1707abb4d1071b880236fe418cb17d7729e50
Author: Eric Pugh <[email protected]>
AuthorDate: Sat Oct 10 07:49:58 2026 -0400

    SOLR-16640: Admin UI's SQL screen shows an error instead of crashing (#5057)
---
 .../solr-16640-admin-ui-sql-error-handling.yml     |  8 ++
 .../java/org/apache/solr/cli/RunExampleTool.java   |  2 +-
 .../apache/solr/webapp/AdminUiSqlScreenTest.java   | 52 +++++++++++--
 solr/webapp/web/js/angular/controllers/sqlquery.js | 85 ++++++++++++++--------
 solr/webapp/web/partials/sqlquery.html             |  5 +-
 5 files changed, 114 insertions(+), 38 deletions(-)

diff --git a/changelog/unreleased/solr-16640-admin-ui-sql-error-handling.yml 
b/changelog/unreleased/solr-16640-admin-ui-sql-error-handling.yml
new file mode 100644
index 00000000000..9a628d567e5
--- /dev/null
+++ b/changelog/unreleased/solr-16640-admin-ui-sql-error-handling.yml
@@ -0,0 +1,8 @@
+# See https://github.com/apache/solr/blob/main/dev-docs/changelog.adoc
+title: Admin UI's SQL screen no longer crashes with an uncaught JS error (and 
silently shows a blank result grid) when the response isn't a SQL result-set - 
e.g. when the sql module/handler isn't installed. It now shows the server's 
error message instead.
+type: fixed
+authors:
+  - name: Eric Pugh
+links:
+  - name: SOLR-16640
+    url: https://issues.apache.org/jira/browse/SOLR-16640
diff --git a/solr/core/src/java/org/apache/solr/cli/RunExampleTool.java 
b/solr/core/src/java/org/apache/solr/cli/RunExampleTool.java
index 9dddc9938d3..66828271c06 100644
--- a/solr/core/src/java/org/apache/solr/cli/RunExampleTool.java
+++ b/solr/core/src/java/org/apache/solr/cli/RunExampleTool.java
@@ -1026,7 +1026,7 @@ public class RunExampleTool extends ToolBase {
 
     final var syspropArg =
         ("techproducts".equals(params.example()))
-            ? "-Dsolr.modules=clustering,extraction,langid,ltr,scripting 
-Dsolr.ltr.enabled=true -Dsolr.clustering.enabled=true"
+            ? "-Dsolr.modules=clustering,extraction,langid,ltr,scripting,sql 
-Dsolr.ltr.enabled=true -Dsolr.clustering.enabled=true"
             : "";
 
     String startCmdStr =
diff --git 
a/solr/webapp/src/test/org/apache/solr/webapp/AdminUiSqlScreenTest.java 
b/solr/webapp/src/test/org/apache/solr/webapp/AdminUiSqlScreenTest.java
index c49871834ce..837ddb5439e 100644
--- a/solr/webapp/src/test/org/apache/solr/webapp/AdminUiSqlScreenTest.java
+++ b/solr/webapp/src/test/org/apache/solr/webapp/AdminUiSqlScreenTest.java
@@ -16,17 +16,16 @@
  */
 package org.apache.solr.webapp;
 
+import java.util.Locale;
 import org.apache.solr.client.solrj.SolrClient;
 import org.apache.solr.common.SolrInputDocument;
 import org.junit.BeforeClass;
 import org.junit.Test;
 import org.openqa.selenium.By;
+import org.openqa.selenium.JavascriptExecutor;
 import org.openqa.selenium.WebElement;
 
-/**
- * Tests the SQL screen: executing a SQL query through the form. Requires the 
sql module on the
- * server classpath, provided by the webapp test dependencies.
- */
+/** Browser tests for the SQL screen. The test classpath includes the sql 
module. */
 public class AdminUiSqlScreenTest extends AdminUiTestBase {
 
   private static final String COLLECTION = "sqlcoll";
@@ -51,10 +50,53 @@ public class AdminUiSqlScreenTest extends AdminUiTestBase {
     stmt.sendKeys("SELECT id FROM " + COLLECTION + " LIMIT 10");
     click(By.xpath("//div[@id='sqlquery']//button[@type='submit']"));
 
-    // the result grid lists all documents
     for (int i = 1; i <= 3; i++) {
       waitForPageContains("sql-doc-" + i);
     }
     assertNoSevereConsoleErrors();
   }
+
+  @Test
+  public void testFailedRequestShowsErrorInsteadOfCrashing() {
+    // A missing collection produces an error response without a result-set.
+    openPage("nonexistentcoll/sqlquery", By.id("sqlquery"));
+    WebElement stmt = waitFor(By.id("sqlexp"));
+    stmt.clear();
+    stmt.sendKeys("SELECT id FROM " + COLLECTION + " LIMIT 10");
+    click(By.xpath("//div[@id='sqlquery']//button[@type='submit']"));
+    waitForTextContains(By.id("sql-response"), "no handler, collection, or 
core");
+    assertNoSevereConsoleErrors("404 (Not Found)");
+  }
+
+  @Test
+  public void testSqlModuleNotEnabledShowsFriendlyMessage() {
+    // Use a captured missing-module response because the test classpath 
includes the sql module.
+    openPage(COLLECTION + "/sqlquery", By.id("sqlquery"));
+    waitFor(By.id("sqlexp"));
+    String classNotFoundResponse =
+        
"{\"error\":{\"metadata\":{\"error-class\":\"org.apache.solr.common.SolrException\","
+            + "\"root-error-class\":\"java.lang.ClassNotFoundException\"},"
+            + "\"errorClass\":\"org.apache.solr.common.SolrException\","
+            + "\"msg\":\" Error loading class 
'solr.SQLHandler'\",\"code\":500}}";
+    String sqlError =
+        (String)
+            ((JavascriptExecutor) driver)
+                .executeScript(
+                    "var scope = 
angular.element(document.getElementById('sqlquery')).scope();"
+                        + "scope.showResult(arguments[0]);"
+                        + "scope.$apply();"
+                        + "return scope.sqlError;",
+                    classNotFoundResponse);
+    assertTrue(
+        "should show a friendly message, got: " + sqlError,
+        sqlError != null && sqlError.toLowerCase(Locale.ROOT).contains("sql 
module"));
+    assertTrue(
+        "should say how to fix it: " + sqlError,
+        sqlError.toLowerCase(Locale.ROOT).contains("enable"));
+    WebElement documentation = waitFor(By.cssSelector("#sql-response span a"));
+    assertEquals(
+        "https://solr.apache.org/guide/solr/latest/query-guide/sql-query.html";,
+        documentation.getDomAttribute("href"));
+    assertEquals("_out", documentation.getDomAttribute("target"));
+  }
 }
diff --git a/solr/webapp/web/js/angular/controllers/sqlquery.js 
b/solr/webapp/web/js/angular/controllers/sqlquery.js
index 7aabc887d5e..b8057dd8e60 100644
--- a/solr/webapp/web/js/angular/controllers/sqlquery.js
+++ b/solr/webapp/web/js/angular/controllers/sqlquery.js
@@ -31,6 +31,55 @@ solrAdminApp.controller('SQLQueryController',
         }
     };
     $scope.hostPortContext = 
$location.absUrl().substr(0,$location.absUrl().indexOf("#")); // For display 
only
+
+    // The global interceptor can route errors to the success callback, so 
handle both here.
+    $scope.showResult = function(raw) {
+      $scope.lang = "json";
+      $scope.sqlError = null;
+      $scope.sqlModuleMissing = false;
+      $scope.sqlData = [];
+
+      var jsonData;
+      try {
+        jsonData = JSON.parse(raw);
+      } catch (e) {
+        $scope.sqlError = raw;
+        return;
+      }
+
+      var docs = jsonData && jsonData['result-set'] && 
jsonData['result-set'].docs;
+      if (!docs) {
+        var err = jsonData && jsonData.error;
+        if (err && err.metadata && err.metadata['root-error-class'] === 
'java.lang.ClassNotFoundException'
+            && err.msg && err.msg.indexOf('SQLHandler') !== -1) {
+          $scope.sqlModuleMissing = true;
+          $scope.sqlError = "The sql module doesn't appear to be enabled on 
this Solr node.";
+        } else {
+          $scope.sqlError = (jsonData && jsonData.message) || (err && err.msg) 
|| raw;
+        }
+        return;
+      }
+
+      for (var i = 0; i < docs.length; i++) {
+          var doc = docs[i]
+          if(doc.hasOwnProperty("EOF")){
+              if(doc.hasOwnProperty("EXCEPTION")){
+                  $scope.sqlError = doc.EXCEPTION
+              }
+          } else {
+              $scope.gridOptions.data.push(doc);
+          }
+      }
+      // Build grid columns from the result fields.
+      var fields = $scope.gridOptions.data[1];
+      for (var property in fields) {
+          if (fields.hasOwnProperty(property)) {
+              $scope.gridOptions.columnDefs.push({"name":property, "type":{}})
+          }
+      }
+      $scope.gridApi.core.notifyDataChange
+    };
+
     $scope.doQuery = function() {
 
       var params = {};
@@ -47,41 +96,15 @@ solrAdminApp.controller('SQLQueryController',
 
       $scope.lang = "json";
       $scope.response = null;
-      $scope.url = "";
       $scope.gridOptions.data =[]
       $scope.gridOptions.columnDefs = []
 
-      var url = Query.url(params);
-      Query.query(params, function(data) {
+      $scope.url = Query.url(params);
 
-        var jsonData = JSON.parse(data.toJSON().data);
-        $scope.lang = "json";
-        $scope.url = url;
-        $scope.sqlError = null;
-        $scope.sqlData = [];
-          if(jsonData != undefined){
-              var docs = jsonData['result-set'].docs
-              //get all docs
-              for (var i = 0; i < docs.length; i++) {
-                  var doc = docs[i]
-                  //get all the properties
-                  if(doc.hasOwnProperty("EOF")){
-                      if(doc.hasOwnProperty("EXCEPTION")){
-                          $scope.sqlError = doc.EXCEPTION
-                      }
-                  } else {
-                      $scope.gridOptions.data.push(doc);
-                  }
-              }
-          }
-          //Build the columnFields from data
-          var fields = $scope.gridOptions.data[1];
-          for (var property in fields) {
-              if (fields.hasOwnProperty(property)) {
-                  $scope.gridOptions.columnDefs.push({"name":property, 
"type":{}})
-              }
-          }
-          $scope.gridApi.core.notifyDataChange
+      Query.query(params, function(data) {
+        $scope.showResult(data.toJSON().data);
+      }, function(rejection) {
+        $scope.showResult((rejection.data && rejection.data.data) || ("HTTP " 
+ rejection.status + " " + rejection.statusText));
       });
     };
   }
diff --git a/solr/webapp/web/partials/sqlquery.html 
b/solr/webapp/web/partials/sqlquery.html
index c0254d13ac3..76a2506c061 100644
--- a/solr/webapp/web/partials/sqlquery.html
+++ b/solr/webapp/web/partials/sqlquery.html
@@ -28,7 +28,10 @@ limitations under the License.
   </div>
   <div id="sql-response">
     <a ng-show="sqlData" id="url" class="address-bar" 
ng-href="{{url}}">{{hostPortContext}}{{url}}</a>
-    <div ng-show="sqlError"> {{sqlError}}</div>
+    <div ng-show="sqlError">
+      {{sqlError}}
+      <span ng-if="sqlModuleMissing">See <a 
href="https://solr.apache.org/guide/solr/latest/query-guide/sql-query.html"; 
target="_out">SQL documentation</a> for how to enable it.</span>
+    </div>
     <div ng-show="!sqlError" class="grid" ui-grid="gridOptions" 
id="sql-response"></div>
 
   </div>

Reply via email to