Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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
2 changes: 1 addition & 1 deletion solr/core/src/java/org/apache/solr/cli/RunExampleTool.java
Original file line number Diff line number Diff line change
Expand Up @@ -814,7 +814,7 @@ Map<String, Object> startSolr(

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"
Comment thread
janhoy marked this conversation as resolved.
: "";

String startCmdStr =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -51,10 +50,53 @@ public void testSqlQueryViaUi() {
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"));
}
}
85 changes: 54 additions & 31 deletions solr/webapp/web/js/angular/controllers/sqlquery.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {};
Expand All @@ -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));
});
};
}
Expand Down
5 changes: 4 additions & 1 deletion solr/webapp/web/partials/sqlquery.html
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,10 @@
</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>
Expand Down
Loading