You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by da...@apache.org on 2018/12/12 10:01:51 UTC

[1/8] lucene-solr:jira/http2: LUCENE-8599: Add hasNoValue bitset to ramBytesUsed calculation

Repository: lucene-solr
Updated Branches:
  refs/heads/jira/http2 d34e04534 -> ca9df4587


LUCENE-8599: Add hasNoValue bitset to ramBytesUsed calculation


Project: http://git-wip-us.apache.org/repos/asf/lucene-solr/repo
Commit: http://git-wip-us.apache.org/repos/asf/lucene-solr/commit/61e44866
Tree: http://git-wip-us.apache.org/repos/asf/lucene-solr/tree/61e44866
Diff: http://git-wip-us.apache.org/repos/asf/lucene-solr/diff/61e44866

Branch: refs/heads/jira/http2
Commit: 61e448666d56c20c37dfb825ec14ffe697d000ff
Parents: ade7546
Author: Simon Willnauer <si...@apache.org>
Authored: Tue Dec 11 22:35:17 2018 +0100
Committer: Simon Willnauer <si...@apache.org>
Committed: Tue Dec 11 22:36:12 2018 +0100

----------------------------------------------------------------------
 .../src/java/org/apache/lucene/index/DocValuesFieldUpdates.java    | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/61e44866/lucene/core/src/java/org/apache/lucene/index/DocValuesFieldUpdates.java
----------------------------------------------------------------------
diff --git a/lucene/core/src/java/org/apache/lucene/index/DocValuesFieldUpdates.java b/lucene/core/src/java/org/apache/lucene/index/DocValuesFieldUpdates.java
index c514ad0..464a7f2 100644
--- a/lucene/core/src/java/org/apache/lucene/index/DocValuesFieldUpdates.java
+++ b/lucene/core/src/java/org/apache/lucene/index/DocValuesFieldUpdates.java
@@ -487,7 +487,7 @@ abstract class DocValuesFieldUpdates implements Accountable {
 
     @Override
     public long ramBytesUsed() {
-      return super.ramBytesUsed() + bitSet.ramBytesUsed();
+      return super.ramBytesUsed() + bitSet.ramBytesUsed() + (hasNoValue == null ? 0 : hasNoValue.ramBytesUsed());
     }
 
     @Override


[2/8] lucene-solr:jira/http2: SOLR-13058 Fix synchronized block

Posted by da...@apache.org.
SOLR-13058 Fix synchronized block


Project: http://git-wip-us.apache.org/repos/asf/lucene-solr/repo
Commit: http://git-wip-us.apache.org/repos/asf/lucene-solr/commit/a7859ff6
Tree: http://git-wip-us.apache.org/repos/asf/lucene-solr/tree/a7859ff6
Diff: http://git-wip-us.apache.org/repos/asf/lucene-solr/diff/a7859ff6

Branch: refs/heads/jira/http2
Commit: a7859ff62ae03d1cffa9d66018528d367b8523cd
Parents: 61e4486
Author: Gus Heck <gu...@apache.org>
Authored: Tue Dec 11 18:45:42 2018 -0500
Committer: Gus Heck <gu...@apache.org>
Committed: Tue Dec 11 18:45:42 2018 -0500

----------------------------------------------------------------------
 solr/CHANGES.txt                                                | 5 +++++
 .../src/java/org/apache/solr/cloud/OverseerTaskProcessor.java   | 2 +-
 2 files changed, 6 insertions(+), 1 deletion(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/a7859ff6/solr/CHANGES.txt
----------------------------------------------------------------------
diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt
index 62370de..c7ad02a 100644
--- a/solr/CHANGES.txt
+++ b/solr/CHANGES.txt
@@ -72,6 +72,11 @@ New Features
 
 * SOLR-12593: The default configSet now includes an "ignored_*" dynamic field.  (David Smiley)
 
+Bug Fixes
+----------------------
+
+* SOLR-13058: Fix block that was synchronizing on the wrong collection in OverseerTaskProcessor (Gus Heck)
+
 Improvements
 ----------------------
 

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/a7859ff6/solr/core/src/java/org/apache/solr/cloud/OverseerTaskProcessor.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/cloud/OverseerTaskProcessor.java b/solr/core/src/java/org/apache/solr/cloud/OverseerTaskProcessor.java
index 3b53a54..d5557e8 100644
--- a/solr/core/src/java/org/apache/solr/cloud/OverseerTaskProcessor.java
+++ b/solr/core/src/java/org/apache/solr/cloud/OverseerTaskProcessor.java
@@ -345,7 +345,7 @@ public class OverseerTaskProcessor implements Runnable, Closeable {
     synchronized (completedTasks) {
       for (String id : completedTasks.keySet()) {
         workQueue.remove(completedTasks.get(id));
-        synchronized (runningTasks) {
+        synchronized (runningZKTasks) {
           runningZKTasks.remove(id);
         }
       }


[3/8] lucene-solr:jira/http2: SOLR-13060: set suite timeout on non-terminating HDFS Nightly tests to one hour

Posted by da...@apache.org.
SOLR-13060: set suite timeout on non-terminating HDFS Nightly tests to one hour


Project: http://git-wip-us.apache.org/repos/asf/lucene-solr/repo
Commit: http://git-wip-us.apache.org/repos/asf/lucene-solr/commit/ec1bd0da
Tree: http://git-wip-us.apache.org/repos/asf/lucene-solr/tree/ec1bd0da
Diff: http://git-wip-us.apache.org/repos/asf/lucene-solr/diff/ec1bd0da

Branch: refs/heads/jira/http2
Commit: ec1bd0da2f784a39fbe5dc21d78349c41bfdaec2
Parents: a7859ff
Author: Steve Rowe <sa...@apache.org>
Authored: Tue Dec 11 18:49:06 2018 -0800
Committer: Steve Rowe <sa...@apache.org>
Committed: Tue Dec 11 18:49:06 2018 -0800

----------------------------------------------------------------------
 solr/core/src/test/org/apache/solr/cloud/MoveReplicaHDFSTest.java | 3 +++
 .../api/collections/HdfsCollectionsAPIDistributedZkTest.java      | 3 +++
 .../cloud/autoscaling/HdfsAutoAddReplicasIntegrationTest.java     | 3 +++
 3 files changed, 9 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/ec1bd0da/solr/core/src/test/org/apache/solr/cloud/MoveReplicaHDFSTest.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/cloud/MoveReplicaHDFSTest.java b/solr/core/src/test/org/apache/solr/cloud/MoveReplicaHDFSTest.java
index b01b34a..380841b 100644
--- a/solr/core/src/test/org/apache/solr/cloud/MoveReplicaHDFSTest.java
+++ b/solr/core/src/test/org/apache/solr/cloud/MoveReplicaHDFSTest.java
@@ -19,7 +19,9 @@ package org.apache.solr.cloud;
 import com.carrotsearch.randomizedtesting.ThreadFilter;
 import com.carrotsearch.randomizedtesting.annotations.Nightly;
 import com.carrotsearch.randomizedtesting.annotations.ThreadLeakFilters;
+import com.carrotsearch.randomizedtesting.annotations.TimeoutSuite;
 import org.apache.hadoop.hdfs.MiniDFSCluster;
+import org.apache.lucene.util.TimeUnits;
 import org.apache.solr.cloud.hdfs.HdfsTestUtil;
 import org.apache.solr.common.cloud.ZkConfigManager;
 import org.apache.solr.util.BadHdfsThreadsFilter;
@@ -35,6 +37,7 @@ import org.junit.Test;
     MoveReplicaHDFSTest.ForkJoinThreadsFilter.class
 })
 @Nightly // test is too long for non nightly
+@TimeoutSuite(millis = TimeUnits.HOUR)
 public class MoveReplicaHDFSTest extends MoveReplicaTest {
 
   private static MiniDFSCluster dfsCluster;

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/ec1bd0da/solr/core/src/test/org/apache/solr/cloud/api/collections/HdfsCollectionsAPIDistributedZkTest.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/cloud/api/collections/HdfsCollectionsAPIDistributedZkTest.java b/solr/core/src/test/org/apache/solr/cloud/api/collections/HdfsCollectionsAPIDistributedZkTest.java
index 20706ef..558f784 100644
--- a/solr/core/src/test/org/apache/solr/cloud/api/collections/HdfsCollectionsAPIDistributedZkTest.java
+++ b/solr/core/src/test/org/apache/solr/cloud/api/collections/HdfsCollectionsAPIDistributedZkTest.java
@@ -26,10 +26,12 @@ import java.util.stream.Collectors;
 
 import com.carrotsearch.randomizedtesting.annotations.Nightly;
 import com.carrotsearch.randomizedtesting.annotations.ThreadLeakFilters;
+import com.carrotsearch.randomizedtesting.annotations.TimeoutSuite;
 import com.codahale.metrics.Counter;
 import com.codahale.metrics.Metric;
 import org.apache.hadoop.hdfs.MiniDFSCluster;
 import org.apache.lucene.util.LuceneTestCase.Slow;
+import org.apache.lucene.util.TimeUnits;
 import org.apache.solr.client.solrj.SolrServerException;
 import org.apache.solr.client.solrj.embedded.JettySolrRunner;
 import org.apache.solr.client.solrj.impl.CloudSolrClient;
@@ -53,6 +55,7 @@ import org.junit.Test;
 @ThreadLeakFilters(defaultFilters = true, filters = {
     BadHdfsThreadsFilter.class // hdfs currently leaks thread(s)
 })
+@TimeoutSuite(millis = TimeUnits.HOUR)
 //commented 23-AUG-2018  @LuceneTestCase.BadApple(bugUrl="https://issues.apache.org/jira/browse/SOLR-12028") // 12-Jun-2018
 public class HdfsCollectionsAPIDistributedZkTest extends CollectionsAPIDistributedZkTest {
 

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/ec1bd0da/solr/core/src/test/org/apache/solr/cloud/autoscaling/HdfsAutoAddReplicasIntegrationTest.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/cloud/autoscaling/HdfsAutoAddReplicasIntegrationTest.java b/solr/core/src/test/org/apache/solr/cloud/autoscaling/HdfsAutoAddReplicasIntegrationTest.java
index 72d3c32..0a33a85 100644
--- a/solr/core/src/test/org/apache/solr/cloud/autoscaling/HdfsAutoAddReplicasIntegrationTest.java
+++ b/solr/core/src/test/org/apache/solr/cloud/autoscaling/HdfsAutoAddReplicasIntegrationTest.java
@@ -19,8 +19,10 @@ package org.apache.solr.cloud.autoscaling;
 
 import com.carrotsearch.randomizedtesting.annotations.Nightly;
 import com.carrotsearch.randomizedtesting.annotations.ThreadLeakFilters;
+import com.carrotsearch.randomizedtesting.annotations.TimeoutSuite;
 import org.apache.hadoop.hdfs.MiniDFSCluster;
 import org.apache.lucene.util.LuceneTestCase;
+import org.apache.lucene.util.TimeUnits;
 import org.apache.solr.cloud.MoveReplicaHDFSTest;
 import org.apache.solr.cloud.hdfs.HdfsTestUtil;
 import org.apache.solr.common.cloud.ZkConfigManager;
@@ -35,6 +37,7 @@ import org.junit.BeforeClass;
 })
 //commented 23-AUG-2018 @LuceneTestCase.BadApple(bugUrl="https://issues.apache.org/jira/browse/SOLR-12028") // added 20-Jul-2018
 @Nightly
+@TimeoutSuite(millis = TimeUnits.HOUR)
 public class HdfsAutoAddReplicasIntegrationTest extends AutoAddReplicasIntegrationTest {
 
   private static MiniDFSCluster dfsCluster;


[5/8] lucene-solr:jira/http2: SOLR-13040: Fix TestSQLHandler, remove delete core calls.

Posted by da...@apache.org.
SOLR-13040: Fix TestSQLHandler, remove delete core calls.


Project: http://git-wip-us.apache.org/repos/asf/lucene-solr/repo
Commit: http://git-wip-us.apache.org/repos/asf/lucene-solr/commit/bcdc6dad
Tree: http://git-wip-us.apache.org/repos/asf/lucene-solr/tree/bcdc6dad
Diff: http://git-wip-us.apache.org/repos/asf/lucene-solr/diff/bcdc6dad

Branch: refs/heads/jira/http2
Commit: bcdc6dadbb0dabfac6fc21c341c0adb35694b335
Parents: ec1bd0d
Author: markrmiller <ma...@apache.org>
Authored: Wed Dec 12 02:01:24 2018 -0600
Committer: markrmiller <ma...@apache.org>
Committed: Wed Dec 12 02:06:26 2018 -0600

----------------------------------------------------------------------
 .../org/apache/solr/handler/TestSQLHandler.java | 4040 +++++++++---------
 1 file changed, 1967 insertions(+), 2073 deletions(-)
----------------------------------------------------------------------



[7/8] lucene-solr:jira/http2: SOLR-7896: Add a login page to Admin UI, with initial support for Basic Auth

Posted by da...@apache.org.
SOLR-7896: Add a login page to Admin UI, with initial support for Basic Auth


Project: http://git-wip-us.apache.org/repos/asf/lucene-solr/repo
Commit: http://git-wip-us.apache.org/repos/asf/lucene-solr/commit/280f6792
Tree: http://git-wip-us.apache.org/repos/asf/lucene-solr/tree/280f6792
Diff: http://git-wip-us.apache.org/repos/asf/lucene-solr/diff/280f6792

Branch: refs/heads/jira/http2
Commit: 280f67927e7590c40b1d5f2960b9c6c7d21d6b5c
Parents: 5affe74
Author: Jan Høydahl <ja...@apache.org>
Authored: Wed Dec 12 10:35:15 2018 +0100
Committer: Jan Høydahl <ja...@apache.org>
Committed: Wed Dec 12 10:37:23 2018 +0100

----------------------------------------------------------------------
 solr/CHANGES.txt                                |   2 +
 solr/NOTICE.txt                                 |   6 +
 .../apache/solr/security/BasicAuthPlugin.java   |  45 +++-
 .../security/Sha256AuthenticationProvider.java  |   7 +-
 .../apache/solr/servlet/SolrDispatchFilter.java |  40 ++--
 ...uthentication-and-authorization-plugins.adoc |  10 +
 .../src/basic-authentication-plugin.adoc        |  45 +++-
 solr/webapp/web/WEB-INF/web.xml                 |   2 +-
 solr/webapp/web/css/angular/login.css           | 103 +++++++++
 solr/webapp/web/css/angular/menu.css            |   2 +
 solr/webapp/web/index.html                      | 160 +++++++-------
 solr/webapp/web/js/angular/app.js               |  45 +++-
 solr/webapp/web/js/angular/controllers/login.js | 146 +++++++++++++
 solr/webapp/web/js/angular/services.js          |  24 +-
 solr/webapp/web/libs/angular-utf8-base64.js     | 217 +++++++++++++++++++
 solr/webapp/web/libs/angular-utf8-base64.min.js |  45 ++++
 solr/webapp/web/partials/login.html             |  80 +++++++
 17 files changed, 860 insertions(+), 119 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/280f6792/solr/CHANGES.txt
----------------------------------------------------------------------
diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt
index 9ab80f4..bfa3666 100644
--- a/solr/CHANGES.txt
+++ b/solr/CHANGES.txt
@@ -155,6 +155,8 @@ New Features
 * SOLR-12839: JSON 'terms' Faceting now supports a 'prelim_sort' option to use when initially selecting 
   the top ranking buckets, prior to the final 'sort' option used after refinement.  (hossman)
 
+* SOLR-7896: Add a login page to Admin UI, with initial support for Basic Auth (janhoy)
+
 Bug Fixes
 ----------------------
 

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/280f6792/solr/NOTICE.txt
----------------------------------------------------------------------
diff --git a/solr/NOTICE.txt b/solr/NOTICE.txt
index ad72eab..9e9a93f 100644
--- a/solr/NOTICE.txt
+++ b/solr/NOTICE.txt
@@ -46,6 +46,12 @@ Copyright (c) 2008-2014, Ryan McGeary, https://github.com/rmm5t/jquery-timeago
 This product includes require.js Javascript library created by James Burke
 Copyright (C) 2010-2014 James Burke, https://github.com/jrburke/requirejs
 
+This product includes angular-utf8-base64.js Javascript library created by Andrey Bezyazychniy
+Copyright (c) 2014 Andrey Bezyazychniy, https://github.com/stranger82/angular-utf8-base64
+
+This product includes code copied and modified from the www-authenticate Javascript library 
+Copyright (c) 2013 Randy McLaughlin, MIT-license, https://github.com/randymized/www-authenticate
+
 This product includes fugue icons created by Yusuke Kamiyamane
 Copyright (C) 2013-2014 Yusuke Kamiyamane, https://github.com/yusukekamiyamane/fugue-icons
 

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/280f6792/solr/core/src/java/org/apache/solr/security/BasicAuthPlugin.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/security/BasicAuthPlugin.java b/solr/core/src/java/org/apache/solr/security/BasicAuthPlugin.java
index eab89e3..1212452 100644
--- a/solr/core/src/java/org/apache/solr/security/BasicAuthPlugin.java
+++ b/solr/core/src/java/org/apache/solr/security/BasicAuthPlugin.java
@@ -34,12 +34,13 @@ import java.util.StringTokenizer;
 import com.google.common.collect.ImmutableSet;
 import org.apache.commons.codec.binary.Base64;
 import org.apache.http.Header;
+import org.apache.http.HttpHeaders;
 import org.apache.http.auth.BasicUserPrincipal;
 import org.apache.http.message.BasicHeader;
 import org.apache.solr.common.SolrException;
-import org.apache.solr.common.util.ValidatingJsonMap;
-import org.apache.solr.common.util.CommandOperation;
 import org.apache.solr.common.SpecProvider;
+import org.apache.solr.common.util.CommandOperation;
+import org.apache.solr.common.util.ValidatingJsonMap;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -47,6 +48,7 @@ public class BasicAuthPlugin extends AuthenticationPlugin implements ConfigEdita
   private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
   private AuthenticationProvider authenticationProvider;
   private final static ThreadLocal<Header> authHeader = new ThreadLocal<>();
+  private static final String X_REQUESTED_WITH_HEADER = "X-Requested-With";
   private boolean blockUnknown = false;
 
   public boolean authenticate(String username, String pwd) {
@@ -55,7 +57,7 @@ public class BasicAuthPlugin extends AuthenticationPlugin implements ConfigEdita
 
   @Override
   public void init(Map<String, Object> pluginConfig) {
-    Object o = pluginConfig.get(BLOCK_UNKNOWN);
+    Object o = pluginConfig.get(PROPERTY_BLOCK_UNKNOWN);
     if (o != null) {
       try {
         blockUnknown = Boolean.parseBoolean(o.toString());
@@ -94,9 +96,18 @@ public class BasicAuthPlugin extends AuthenticationPlugin implements ConfigEdita
     return provider;
   }
 
-  private void authenticationFailure(HttpServletResponse response, String message) throws IOException {
+  private void authenticationFailure(HttpServletResponse response, boolean isAjaxRequest, String message) throws IOException {
     for (Map.Entry<String, String> entry : authenticationProvider.getPromptHeaders().entrySet()) {
-      response.setHeader(entry.getKey(), entry.getValue());
+      String value = entry.getValue();
+      // Prevent browser from intercepting basic authentication header when reqeust from Admin UI
+      if (isAjaxRequest && HttpHeaders.WWW_AUTHENTICATE.equalsIgnoreCase(entry.getKey()) && value != null) {
+        if (value.startsWith("Basic ")) {
+          value = "x" + value;
+          log.debug("Prefixing {} header for Basic Auth with 'x' to prevent browser basic auth popup", 
+              HttpHeaders.WWW_AUTHENTICATE);
+        }
+      }
+      response.setHeader(entry.getKey(), value);
     }
     response.sendError(401, message);
   }
@@ -108,6 +119,8 @@ public class BasicAuthPlugin extends AuthenticationPlugin implements ConfigEdita
     HttpServletResponse response = (HttpServletResponse) servletResponse;
 
     String authHeader = request.getHeader("Authorization");
+    boolean isAjaxRequest = isAjaxRequest(request);
+    
     if (authHeader != null) {
       BasicAuthPlugin.authHeader.set(new BasicHeader("Authorization", authHeader));
       StringTokenizer st = new StringTokenizer(authHeader);
@@ -122,7 +135,7 @@ public class BasicAuthPlugin extends AuthenticationPlugin implements ConfigEdita
               String pwd = credentials.substring(p + 1).trim();
               if (!authenticate(username, pwd)) {
                 log.debug("Bad auth credentials supplied in Authorization header");
-                authenticationFailure(response, "Bad credentials");
+                authenticationFailure(response, isAjaxRequest, "Bad credentials");
               } else {
                 HttpServletRequestWrapper wrapper = new HttpServletRequestWrapper(request) {
                   @Override
@@ -135,7 +148,7 @@ public class BasicAuthPlugin extends AuthenticationPlugin implements ConfigEdita
               }
 
             } else {
-              authenticationFailure(response, "Invalid authentication token");
+              authenticationFailure(response, isAjaxRequest, "Invalid authentication token");
             }
           } catch (UnsupportedEncodingException e) {
             throw new Error("Couldn't retrieve authentication", e);
@@ -144,7 +157,7 @@ public class BasicAuthPlugin extends AuthenticationPlugin implements ConfigEdita
       }
     } else {
       if (blockUnknown) {
-        authenticationFailure(response, "require authentication");
+        authenticationFailure(response, isAjaxRequest, "require authentication");
       } else {
         request.setAttribute(AuthenticationPlugin.class.getName(), authenticationProvider.getPromptHeaders());
         filterChain.doFilter(request, response);
@@ -180,8 +193,16 @@ public class BasicAuthPlugin extends AuthenticationPlugin implements ConfigEdita
     return blockUnknown;
   }
 
-  public static final String BLOCK_UNKNOWN = "blockUnknown";
-  private static final Set<String> PROPS = ImmutableSet.of(BLOCK_UNKNOWN);
-
-
+  public static final String PROPERTY_BLOCK_UNKNOWN = "blockUnknown";
+  public static final String PROPERTY_REALM = "realm";
+  private static final Set<String> PROPS = ImmutableSet.of(PROPERTY_BLOCK_UNKNOWN, PROPERTY_REALM);
+
+  /**
+   * Check if the request is an AJAX request, i.e. from the Admin UI or other SPA front 
+   * @param request the servlet request
+   * @return true if the request is AJAX request
+   */
+  private boolean isAjaxRequest(HttpServletRequest request) {
+    return "XMLHttpRequest".equalsIgnoreCase(request.getHeader(X_REQUESTED_WITH_HEADER));
+  }
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/280f6792/solr/core/src/java/org/apache/solr/security/Sha256AuthenticationProvider.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/security/Sha256AuthenticationProvider.java b/solr/core/src/java/org/apache/solr/security/Sha256AuthenticationProvider.java
index 4b85c45..e8cc87a 100644
--- a/solr/core/src/java/org/apache/solr/security/Sha256AuthenticationProvider.java
+++ b/solr/core/src/java/org/apache/solr/security/Sha256AuthenticationProvider.java
@@ -64,8 +64,11 @@ public class Sha256AuthenticationProvider implements ConfigEditablePlugin,  Basi
 
   @Override
   public void init(Map<String, Object> pluginConfig) {
-    if (pluginConfig.get("realm") != null) this.realm = (String) pluginConfig.get("realm");
-    else this.realm = "solr";
+    if (pluginConfig.containsKey(BasicAuthPlugin.PROPERTY_REALM)) {
+      this.realm = (String) pluginConfig.get(BasicAuthPlugin.PROPERTY_REALM);
+    } else {
+      this.realm = "solr";
+    }
     
     promptHeader = Collections.unmodifiableMap(Collections.singletonMap("WWW-Authenticate", "Basic realm=\"" + realm + "\""));
     credentials = new LinkedHashMap<>();

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/280f6792/solr/core/src/java/org/apache/solr/servlet/SolrDispatchFilter.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/servlet/SolrDispatchFilter.java b/solr/core/src/java/org/apache/solr/servlet/SolrDispatchFilter.java
index 9e6523b..1a8b14e 100644
--- a/solr/core/src/java/org/apache/solr/servlet/SolrDispatchFilter.java
+++ b/solr/core/src/java/org/apache/solr/servlet/SolrDispatchFilter.java
@@ -360,18 +360,6 @@ public class SolrDispatchFilter extends BaseSolrFilter {
         }
       }
 
-      AtomicReference<HttpServletRequest> wrappedRequest = new AtomicReference<>();
-      if (!authenticateRequest(request, response, wrappedRequest)) { // the response and status code have already been sent
-        return;
-      }
-      if (wrappedRequest.get() != null) {
-        request = wrappedRequest.get();
-      }
-
-      if (cores.getAuthenticationPlugin() != null) {
-        log.debug("User principal: {}", request.getUserPrincipal());
-      }
-
       // No need to even create the HttpSolrCall object if this path is excluded.
       if (excludePatterns != null) {
         String requestPath = request.getServletPath();
@@ -389,6 +377,18 @@ public class SolrDispatchFilter extends BaseSolrFilter {
         }
       }
 
+      AtomicReference<HttpServletRequest> wrappedRequest = new AtomicReference<>();
+      if (!authenticateRequest(request, response, wrappedRequest)) { // the response and status code have already been sent
+        return;
+      }
+      if (wrappedRequest.get() != null) {
+        request = wrappedRequest.get();
+      }
+
+      if (cores.getAuthenticationPlugin() != null) {
+        log.debug("User principal: {}", request.getUserPrincipal());
+      }
+
       HttpSolrCall call = getHttpSolrCall(request, response, retry);
       ExecutorUtil.setServerThreadFlag(Boolean.TRUE);
       try {
@@ -458,8 +458,20 @@ public class SolrDispatchFilter extends BaseSolrFilter {
       // /admin/info/key must be always open. see SOLR-9188
       // tests work only w/ getPathInfo
       //otherwise it's just enough to have getServletPath()
-      if (PublicKeyHandler.PATH.equals(request.getServletPath()) ||
-          PublicKeyHandler.PATH.equals(request.getPathInfo())) return true;
+      String requestPath = request.getPathInfo();
+      if (requestPath == null) 
+        requestPath = request.getServletPath();
+      if (PublicKeyHandler.PATH.equals(requestPath)) {
+        if (log.isDebugEnabled())
+          log.debug("Pass through PKI authentication endpoint");
+        return true;
+      }
+      // /solr/ (Admin UI) must be always open to allow displaying Admin UI with login page  
+      if ("/solr/".equals(requestPath) || "/".equals(requestPath)) {
+        if (log.isDebugEnabled())
+          log.debug("Pass through Admin UI entry point");
+        return true;
+      }
       String header = request.getHeader(PKIAuthenticationPlugin.HEADER);
       if (header != null && cores.getPkiAuthenticationPlugin() != null)
         authenticationPlugin = cores.getPkiAuthenticationPlugin();

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/280f6792/solr/solr-ref-guide/src/authentication-and-authorization-plugins.adoc
----------------------------------------------------------------------
diff --git a/solr/solr-ref-guide/src/authentication-and-authorization-plugins.adoc b/solr/solr-ref-guide/src/authentication-and-authorization-plugins.adoc
index 971dbcd..dabd869 100644
--- a/solr/solr-ref-guide/src/authentication-and-authorization-plugins.adoc
+++ b/solr/solr-ref-guide/src/authentication-and-authorization-plugins.adoc
@@ -157,6 +157,16 @@ Solr has one implementation of an authorization plugin:
 
 * <<rule-based-authorization-plugin.adoc#rule-based-authorization-plugin,Rule-Based Authorization Plugin>>
 
+== Authenticating in the Admin UI
+
+Whenever an authentication plugin is enabled, authentication is also required for all or some operations in the Admin UI. The Admin UI is an AngularJS application running inside your browser, and is treated as any other external client by Solr.
+
+When authentication is required the Admin UI will presented you with a login dialogue. The authentication plugins currently supported by the Admin UI are:
+
+* `BasicAuthPlugin`
+ 
+If your plugin of choice is not supported, you will have to interact with Solr sending HTTP requests instead of through the graphical user interface of the Admin UI. All operations supported by Admin UI can be performed through Solr's RESTful APIs.
+
 == Securing Inter-Node Requests
 
 There are a lot of requests that originate from the Solr nodes itself. For example, requests from overseer to nodes, recovery threads, etc. Each Authentication plugin declares whether it is capable of securing inter-node requests or not. If not, Solr will fall back to using a special internode authentication mechanism where each Solr node is a super user and is fully trusted by other Solr nodes, described below.

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/280f6792/solr/solr-ref-guide/src/basic-authentication-plugin.adoc
----------------------------------------------------------------------
diff --git a/solr/solr-ref-guide/src/basic-authentication-plugin.adoc b/solr/solr-ref-guide/src/basic-authentication-plugin.adoc
index 88e8a0c..308c5a2 100644
--- a/solr/solr-ref-guide/src/basic-authentication-plugin.adoc
+++ b/solr/solr-ref-guide/src/basic-authentication-plugin.adoc
@@ -36,13 +36,14 @@ An example `security.json` showing both sections is shown below to show how thes
 "authentication":{ <1>
    "blockUnknown": true, <2>
    "class":"solr.BasicAuthPlugin",
-   "credentials":{"solr":"IV0EHq1OnNrj6gvRCwvFwTrZ1+z1oBbnQdiVC3otuq0= Ndd7LKvVBAaZIF0QAVi1ekCfAJXr1GGfLtRUXhgrF8c="} <3>
+   "credentials":{"solr":"IV0EHq1OnNrj6gvRCwvFwTrZ1+z1oBbnQdiVC3otuq0= Ndd7LKvVBAaZIF0QAVi1ekCfAJXr1GGfLtRUXhgrF8c="}, <3>
+   "realm":"My Solr users" <4>
 },
 "authorization":{
    "class":"solr.RuleBasedAuthorizationPlugin",
    "permissions":[{"name":"security-edit",
-      "role":"admin"}], <4>
-   "user-role":{"solr":"admin"} <5>
+      "role":"admin"}], <5>
+   "user-role":{"solr":"admin"} <6>
 }}
 ----
 
@@ -51,13 +52,16 @@ There are several things defined in this file:
 <1> Basic authentication and rule-based authorization plugins are enabled.
 <2> The parameter `"blockUnknown":true` means that unauthenticated requests are not allowed to pass through.
 <3> A user called 'solr', with a password `'SolrRocks'` has been defined.
-<4> The 'admin' role has been defined, and it has permission to edit security settings.
-<5> The 'solr' user has been defined to the 'admin' role.
+<4> We override the `realm` property to display another text on the login prompt.
+<5> The 'admin' role has been defined, and it has permission to edit security settings.
+<6> The 'solr' user has been defined to the 'admin' role.
 
 Save your settings to a file called `security.json` locally. If you are using Solr in standalone mode, you should put this file in `$SOLR_HOME`.
 
 If `blockUnknown` does not appear in the `security.json` file, it will default to `false`. This has the effect of not requiring authentication at all. In some cases, you may want this; for example, if you want to have `security.json` in place but aren't ready to enable authentication. However, you will want to ensure that this parameter is set to `true` in order for authentication to be truly enabled in your system.
 
+If `realm` is not defined, it will default to `solr`.
+
 If you are using SolrCloud, you must upload `security.json` to ZooKeeper. You can use this example command, ensuring that the ZooKeeper port is correct:
 
 [source,bash]
@@ -139,11 +143,11 @@ curl --user solr:SolrRocks http://localhost:8983/api/cluster/security/authentica
 
 === Set a Property
 
-Set properties for the authentication plugin. The only currently supported property for the Basic Authentication plugin is `blockUnknown`.
+Set properties for the authentication plugin. The currently supported properties for the Basic Authentication plugin are `blockUnknown` and `realm`.
 
 [.dynamic-tabs]
 --
-[example.tab-pane#v1set-property]
+[example.tab-pane#v1set-property-blockUnknown]
 ====
 [.tab-label]*V1 API*
 
@@ -153,7 +157,7 @@ curl --user solr:SolrRocks http://localhost:8983/solr/admin/authentication -H 'C
 ----
 ====
 
-[example.tab-pane#v2set-property]
+[example.tab-pane#v2set-property-blockUnknown]
 ====
 [.tab-label]*V2 API*
 
@@ -164,6 +168,31 @@ curl --user solr:SolrRocks http://localhost:8983/api/cluster/security/authentica
 ====
 --
 
+The authentication realm defaults to `solr` and is displayed in the `WWW-Authenticate` HTTP header and in the Admin UI login page. To change the realm, set the `realm` property: 
+
+[.dynamic-tabs]
+--
+[example.tab-pane#v1set-property-realm]
+====
+[.tab-label]*V1 API*
+
+[source,bash]
+----
+curl --user solr:SolrRocks http://localhost:8983/solr/admin/authentication -H 'Content-type:application/json' -d  '{"set-property": {"realm":"My Solr users"}}'
+----
+====
+
+[example.tab-pane#v2set-property-realm]
+====
+[.tab-label]*V2 API*
+
+[source,bash]
+----
+curl --user solr:SolrRocks http://localhost:8983/api/cluster/security/authentication -H 'Content-type:application/json' -d  '{"set-property": {"realm":"My Solr users"}}'
+----
+====
+--
+
 == Using Basic Auth with SolrJ
 
 In SolrJ, the basic authentication credentials need to be set for each request as in this example:

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/280f6792/solr/webapp/web/WEB-INF/web.xml
----------------------------------------------------------------------
diff --git a/solr/webapp/web/WEB-INF/web.xml b/solr/webapp/web/WEB-INF/web.xml
index 155b08b..53ab57a 100644
--- a/solr/webapp/web/WEB-INF/web.xml
+++ b/solr/webapp/web/WEB-INF/web.xml
@@ -33,7 +33,7 @@
     -->
     <init-param>
       <param-name>excludePatterns</param-name>
-      <param-value>/partials/.+,/libs/.+,/css/.+,/js/.+,/img/.+,/tpl/.+</param-value>
+      <param-value>/partials/.+,/libs/.+,/css/.+,/js/.+,/img/.+,/templates/.+</param-value>
     </init-param>
   </filter>
 

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/280f6792/solr/webapp/web/css/angular/login.css
----------------------------------------------------------------------
diff --git a/solr/webapp/web/css/angular/login.css b/solr/webapp/web/css/angular/login.css
new file mode 100644
index 0000000..52ad4e0
--- /dev/null
+++ b/solr/webapp/web/css/angular/login.css
@@ -0,0 +1,103 @@
+/*
+
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+*/
+
+#content #login
+{
+  background-position: 0 50%;
+  padding-left: 21px;
+  vertical-align: center;
+  horiz-align: center;
+}
+
+#content #login h1,
+#content #login .h1 {
+  font-size: 2.5rem;
+}
+
+#content #login h2, 
+#content #login .h2 {
+  font-size: 2rem;
+}
+
+#content #login p
+{
+  margin-top: 0;
+  margin-bottom: 1rem;
+}
+
+#content #login .login-error
+{
+  font-size: 1rem;
+  color: red;
+  margin-top: 10px;
+  margin-bottom: 10px;
+}
+
+#content #login button {
+  border-radius: 0;
+}
+
+#content #login button:focus {
+  outline: 1px dotted;
+  outline: 5px auto -webkit-focus-ring-color;
+}
+
+#content #login .btn {
+  display: inline-block;
+  font-weight: 400;
+  text-align: center;
+  white-space: nowrap;
+  vertical-align: middle;
+  -webkit-user-select: none;
+  -moz-user-select: none;
+  -ms-user-select: none;
+  user-select: none;
+  border: 1px solid transparent;
+  padding: 0.375rem 0.75rem;
+  font-size: 1rem;
+  line-height: 1.5;
+  border-radius: 0.25rem;
+  transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
+}
+
+#content #login .form-inline .form-group {
+  display: -ms-flexbox;
+  display: flex;
+  -ms-flex: 0 0 auto;
+  flex: 0 0 auto;
+  -ms-flex-flow: row wrap;
+  flex-flow: row wrap;
+  -ms-flex-align: center;
+  align-items: center;
+  margin-bottom: 0;
+}
+
+#content #login .form-control {
+  display: block;
+  width: 80%;
+  padding: 0.375rem 0.75rem;
+  font-size: 1rem;
+  line-height: 1.5;
+  color: #495057;
+  background-color: #fff;
+  background-clip: padding-box;
+  border: 1px solid #ced4da;
+  border-radius: 0.25rem;
+  transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/280f6792/solr/webapp/web/css/angular/menu.css
----------------------------------------------------------------------
diff --git a/solr/webapp/web/css/angular/menu.css b/solr/webapp/web/css/angular/menu.css
index 71a1668..4a24399 100644
--- a/solr/webapp/web/css/angular/menu.css
+++ b/solr/webapp/web/css/angular/menu.css
@@ -247,6 +247,8 @@ limitations under the License.
 
 #menu #index.global p a { background-image: url( ../../img/ico/dashboard.png ); }
 
+#menu #login.global p a { background-image: url( ../../img/ico/users.png ); }
+
 #menu #logging.global p a { background-image: url( ../../img/ico/inbox-document-text.png ); }
 #menu #logging.global .level a { background-image: url( ../../img/ico/gear.png ); }
 

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/280f6792/solr/webapp/web/index.html
----------------------------------------------------------------------
diff --git a/solr/webapp/web/index.html b/solr/webapp/web/index.html
index 00f70ef..23b9dbd 100644
--- a/solr/webapp/web/index.html
+++ b/solr/webapp/web/index.html
@@ -34,6 +34,7 @@ limitations under the License.
   <link rel="stylesheet" type="text/css" href="css/angular/index.css?_=${version}">
   <link rel="stylesheet" type="text/css" href="css/angular/java-properties.css?_=${version}">
   <link rel="stylesheet" type="text/css" href="css/angular/logging.css?_=${version}">
+  <link rel="stylesheet" type="text/css" href="css/angular/login.css?_=${version}">
   <link rel="stylesheet" type="text/css" href="css/angular/menu.css?_=${version}">
   <link rel="stylesheet" type="text/css" href="css/angular/plugins.css?_=${version}">
   <link rel="stylesheet" type="text/css" href="css/angular/documents.css?_=${version}">
@@ -61,9 +62,11 @@ limitations under the License.
   <script src="libs/highlight.js"></script>
   <script src="libs/d3.js"></script>
   <script src="libs/jquery-ui.min.js"></script>
+  <script src="libs/angular-utf8-base64.min.js"></script>
   <script src="js/angular/app.js"></script>
   <script src="js/angular/services.js"></script>
   <script src="js/angular/controllers/index.js"></script>
+  <script src="js/angular/controllers/login.js"></script>
   <script src="js/angular/controllers/logging.js"></script>
   <script src="js/angular/controllers/cloud.js"></script>
   <script src="js/angular/controllers/collections.js"></script>
@@ -139,88 +142,91 @@ limitations under the License.
         <div>
 
           <ul id="menu">
-
-            <li id="index" class="global" ng-class="{active:page=='index'}"><p><a href="#/">Dashboard</a></p></li>
-
-            <li id="logging" class="global" ng-class="{active:page=='logging'}"><p><a href="#/~logging">Logging</a></p>
-              <ul ng-show="showingLogging">
-                <li class="level" ng-class="{active:page=='logging-levels'}"><a href="#/~logging/level">Level</a></li>
-              </ul>
-            </li>
-
-            <li id="cloud" class="global optional" ng-show="isCloudEnabled" ng-class="{active:showingCloud}"><p><a href="#/~cloud">Cloud</a></p>
-              <ul ng-show="showingCloud">
-                <li class="nodes" ng-class="{active:page=='cloud-nodes'}"><a href="#/~cloud?view=nodes">Nodes</a></li>
-                <li class="tree" ng-class="{active:page=='cloud-tree'}"><a href="#/~cloud?view=tree">Tree</a></li>
-                <li class="zkstatus" ng-class="{active:page=='cloud-zkstatus'}"><a href="#/~cloud?view=zkstatus">ZK Status</a></li>
-                <li class="graph" ng-class="{active:page=='cloud-graph'}"><a href="#/~cloud?view=graph">Graph</a></li>
-              </ul>
-            </li>
-
-            <li ng-show="isCloudEnabled" id="collections" class="global" ng-class="{active:page=='collections'}"><p><a href="#/~collections">Collections</a></p></li>
-            <li ng-hide="isCloudEnabled" id="cores" class="global" ng-class="{active:page=='cores'}"><p><a href="#/~cores">Core Admin</a></p></li>
-
-            <li id="java-properties" class="global" ng-class="{active:page=='java-props'}"><p><a href="#/~java-properties">Java Properties</a></li>
-
-            <li id="threads" class="global" ng-class="{active:page=='threads'}"><p><a href="#/~threads">Thread Dump</a></p></li>
-            <li ng-show="isCloudEnabled" id="cluster-suggestions" class="global" ng-class="{active:page=='cluster-suggestions'}"><p><a href="#/~cluster-suggestions">Suggestions</a></p></li>
-
+            <li id="login" class="global" ng-class="{active:page=='login'}" ng-show="http401 || currentUser"><p><a href="#/login">{{http401 ? "Login" : "Logout " + currentUser}}</a></p></li>
+            
+            <div ng-show="!http401">
+              <li id="index" class="global" ng-class="{active:page=='index'}"><p><a href="#/">Dashboard</a></p></li>
+  
+              <li id="logging" class="global" ng-class="{active:page=='logging'}"><p><a href="#/~logging">Logging</a></p>
+                <ul ng-show="showingLogging">
+                  <li class="level" ng-class="{active:page=='logging-levels'}"><a href="#/~logging/level">Level</a></li>
+                </ul>
+              </li>
+  
+              <li id="cloud" class="global optional" ng-show="isCloudEnabled" ng-class="{active:showingCloud}"><p><a href="#/~cloud">Cloud</a></p>
+                <ul ng-show="showingCloud">
+                  <li class="nodes" ng-class="{active:page=='cloud-nodes'}"><a href="#/~cloud?view=nodes">Nodes</a></li>
+                  <li class="tree" ng-class="{active:page=='cloud-tree'}"><a href="#/~cloud?view=tree">Tree</a></li>
+                  <li class="zkstatus" ng-class="{active:page=='cloud-zkstatus'}"><a href="#/~cloud?view=zkstatus">ZK Status</a></li>
+                  <li class="graph" ng-class="{active:page=='cloud-graph'}"><a href="#/~cloud?view=graph">Graph</a></li>
+                </ul>
+              </li>
+  
+              <li ng-show="isCloudEnabled" id="collections" class="global" ng-class="{active:page=='collections'}"><p><a href="#/~collections">Collections</a></p></li>
+              <li ng-hide="isCloudEnabled" id="cores" class="global" ng-class="{active:page=='cores'}"><p><a href="#/~cores">Core Admin</a></p></li>
+  
+              <li id="java-properties" class="global" ng-class="{active:page=='java-props'}"><p><a href="#/~java-properties">Java Properties</a></li>
+  
+              <li id="threads" class="global" ng-class="{active:page=='threads'}"><p><a href="#/~threads">Thread Dump</a></p></li>
+              <li ng-show="isCloudEnabled" id="cluster-suggestions" class="global" ng-class="{active:page=='cluster-suggestions'}"><p><a href="#/~cluster-suggestions">Suggestions</a></p></li>
+            </div>
           </ul>
 
-          <div id="collection-selector" ng-show="isCloudEnabled">
-            <div id="has-collections" ng-show="collections.length!=0">
-              <select data-placeholder="Collection Selector"
-                      ng-model="currentCollection"
-                      chosen
-                      ng-change="showCollection(currentCollection)"
-                      ng-options="collection.name for collection in collections"></select>
+          <div ng-show="!http401">
+            <div id="collection-selector" ng-show="isCloudEnabled">
+              <div id="has-collections" ng-show="collections.length!=0">
+                <select data-placeholder="Collection Selector"
+                        ng-model="currentCollection"
+                        chosen
+                        ng-change="showCollection(currentCollection)"
+                        ng-options="collection.name for collection in collections"></select>
+              </div>
+              <p id="has-no-collections" ng-show="collections.length==0"><a href="#/~collections">
+                No collections available
+                <span>Go and create one</span>
+              </a></p>
             </div>
-            <p id="has-no-collections" ng-show="collections.length==0"><a href="#/~collections">
-              No collections available
-              <span>Go and create one</span>
-            </a></p>
-          </div>
-          <div id="collection-menu" class="sub-menu" ng-show="currentCollection">
-            <ul>
-              <li class="overview" ng-class="{active:page=='collection-overview'}"><a href="#/{{currentCollection.name}}/collection-overview"><span>Overview</span></a></li>
-              <li class="analysis" ng-class="{active:page=='analysis'}"><a href="#/{{currentCollection.name}}/analysis"><span>Analysis</span></a></li>
-              <li class="dataimport" ng-class="{active:page=='dataimport'}"><a href="#/{{currentCollection.name}}/dataimport"><span>Dataimport</span></a></li>
-              <li class="documents" ng-class="{active:page=='documents'}"><a href="#/{{currentCollection.name}}/documents"><span>Documents</span></a></li>
-              <li class="files" ng-class="{active:page=='files'}"><a href="#/{{currentCollection.name}}/files"><span>Files</span></a></li>
-              <li class="query" ng-class="{active:page=='query'}"><a href="#/{{currentCollection.name}}/query"><span>Query</span></a></li>
-              <li class="stream" ng-class="{active:page=='stream'}"><a href="#/{{currentCollection.name}}/stream"><span>Stream</span></a></li>
-              <li class="schema" ng-class="{active:page=='schema'}"><a href="#/{{currentCollection.name}}/schema"><span>Schema</span></a></li>
-        </ul>
-          </div>
-          <div id="core-selector">
-            <div id="has-cores" ng-show="cores.length!=0">
-              <select data-placeholder="Core Selector"
-                      ng-model="currentCore"
-                      chosen
-                      ng-change="showCore(currentCore)"
-                      ng-options="core.name for core in cores"></select>
+            <div id="collection-menu" class="sub-menu" ng-show="currentCollection">
+              <ul>
+                <li class="overview" ng-class="{active:page=='collection-overview'}"><a href="#/{{currentCollection.name}}/collection-overview"><span>Overview</span></a></li>
+                <li class="analysis" ng-class="{active:page=='analysis'}"><a href="#/{{currentCollection.name}}/analysis"><span>Analysis</span></a></li>
+                <li class="dataimport" ng-class="{active:page=='dataimport'}"><a href="#/{{currentCollection.name}}/dataimport"><span>Dataimport</span></a></li>
+                <li class="documents" ng-class="{active:page=='documents'}"><a href="#/{{currentCollection.name}}/documents"><span>Documents</span></a></li>
+                <li class="files" ng-class="{active:page=='files'}"><a href="#/{{currentCollection.name}}/files"><span>Files</span></a></li>
+                <li class="query" ng-class="{active:page=='query'}"><a href="#/{{currentCollection.name}}/query"><span>Query</span></a></li>
+                <li class="stream" ng-class="{active:page=='stream'}"><a href="#/{{currentCollection.name}}/stream"><span>Stream</span></a></li>
+                <li class="schema" ng-class="{active:page=='schema'}"><a href="#/{{currentCollection.name}}/schema"><span>Schema</span></a></li>
+              </ul>
+            </div>
+            <div id="core-selector">
+              <div id="has-cores" ng-show="cores.length!=0">
+                <select data-placeholder="Core Selector"
+                        ng-model="currentCore"
+                        chosen
+                        ng-change="showCore(currentCore)"
+                        ng-options="core.name for core in cores"></select>
+              </div>
+              <p id="has-no-cores" ng-show="cores.length==0"><a href="#/~cores">
+                No cores available
+                <span>Go and create one</span>
+              </a></p>
+            </div>
+            <div id="core-menu" class="sub-menu" ng-show="currentCore">
+              <ul>
+                <li class="overview" ng-class="{active:page=='overview'}"><a href="#/{{currentCore.name}}/core-overview"><span>Overview</span></a></li>
+                <li ng-hide="isCloudEnabled" class="analysis" ng-class="{active:page=='analysis'}"><a href="#/{{currentCore.name}}/analysis"><span>Analysis</span></a></li>
+                <li ng-hide="isCloudEnabled" class="dataimport" ng-class="{active:page=='dataimport'}"><a href="#/{{currentCore.name}}/dataimport"><span>Dataimport</span></a></li>
+                <li ng-hide="isCloudEnabled" class="documents" ng-class="{active:page=='documents'}"><a href="#/{{currentCore.name}}/documents"><span>Documents</span></a></li>
+                <li ng-hide="isCloudEnabled" class="files" ng-class="{active:page=='files'}"><a href="#/{{currentCore.name}}/files"><span>Files</span></a></li>
+                <li class="ping" ng-class="{active:page=='ping'}"><a ng-click="ping()"><span>Ping</span><small class="qtime" ng-show="showPing"> (<span>{{pingMS}}ms</span>)</small></a></li>
+                <li class="plugins" ng-class="{active:page=='plugins'}"><a href="#/{{currentCore.name}}/plugins"><span>Plugins / Stats</span></a></li>
+                <li ng-hide="isCloudEnabled" class="query" ng-class="{active:page=='query'}"><a href="#/{{currentCore.name}}/query"><span>Query</span></a></li>
+                <li ng-hide="isCloudEnabled" class="replication" ng-class="{active:page=='replication'}"><a href="#/{{currentCore.name}}/replication"><span>Replication</span></a></li>
+                <li ng-hide="isCloudEnabled" class="schema" ng-class="{active:page=='schema'}"><a href="#/{{currentCore.name}}/schema"><span>Schema</span></a></li>
+                <li class="segments" ng-class="{active:page=='segments'}"><a href="#/{{currentCore.name}}/segments"><span>Segments info</span></a></li>
+              </ul>
             </div>
-            <p id="has-no-cores" ng-show="cores.length==0"><a href="#/~cores">
-              No cores available
-              <span>Go and create one</span>
-            </a></p>
-          </div>
-          <div id="core-menu" class="sub-menu" ng-show="currentCore">
-            <ul>
-              <li class="overview" ng-class="{active:page=='overview'}"><a href="#/{{currentCore.name}}"><span>Overview</span></a></li>
-              <li ng-hide="isCloudEnabled" class="analysis" ng-class="{active:page=='analysis'}"><a href="#/{{currentCore.name}}/analysis"><span>Analysis</span></a></li>
-              <li ng-hide="isCloudEnabled" class="dataimport" ng-class="{active:page=='dataimport'}"><a href="#/{{currentCore.name}}/dataimport"><span>Dataimport</span></a></li>
-              <li ng-hide="isCloudEnabled" class="documents" ng-class="{active:page=='documents'}"><a href="#/{{currentCore.name}}/documents"><span>Documents</span></a></li>
-              <li ng-hide="isCloudEnabled" class="files" ng-class="{active:page=='files'}"><a href="#/{{currentCore.name}}/files"><span>Files</span></a></li>
-              <li class="ping" ng-class="{active:page=='ping'}"><a ng-click="ping()"><span>Ping</span><small class="qtime" ng-show="showPing"> (<span>{{pingMS}}ms</span>)</small></a></li>
-              <li class="plugins" ng-class="{active:page=='plugins'}"><a href="#/{{currentCore.name}}/plugins"><span>Plugins / Stats</span></a></li>
-              <li ng-hide="isCloudEnabled" class="query" ng-class="{active:page=='query'}"><a href="#/{{currentCore.name}}/query"><span>Query</span></a></li>
-              <li ng-hide="isCloudEnabled" class="replication" ng-class="{active:page=='replication'}"><a href="#/{{currentCore.name}}/replication"><span>Replication</span></a></li>
-              <li ng-hide="isCloudEnabled" class="schema" ng-class="{active:page=='schema'}"><a href="#/{{currentCore.name}}/schema"><span>Schema</span></a></li>
-              <li class="segments" ng-class="{active:page=='segments'}"><a href="#/{{currentCore.name}}/segments"><span>Segments info</span></a></li>
-      </ul>
           </div>
-
         </div>
       </div>
 

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/280f6792/solr/webapp/web/js/angular/app.js
----------------------------------------------------------------------
diff --git a/solr/webapp/web/js/angular/app.js b/solr/webapp/web/js/angular/app.js
index ad96ce0..cb04ba3 100644
--- a/solr/webapp/web/js/angular/app.js
+++ b/solr/webapp/web/js/angular/app.js
@@ -21,7 +21,8 @@ var solrAdminApp = angular.module("solrAdminApp", [
   "ngCookies",
   "ngtimeago",
   "solrAdminServices",
-  "localytics.directives"
+  "localytics.directives",
+  "ab-base64"
 ]);
 
 solrAdminApp.config([
@@ -31,6 +32,10 @@ solrAdminApp.config([
         templateUrl: 'partials/index.html',
         controller: 'IndexController'
       }).
+      when('/login', {
+        templateUrl: 'partials/login.html',
+        controller: 'LoginController'
+      }).
       when('/~logging', {
         templateUrl: 'partials/logging.html',
         controller: 'LoggingController'
@@ -315,7 +320,7 @@ solrAdminApp.config([
     }
   };
 })
-.factory('httpInterceptor', function($q, $rootScope, $timeout, $injector) {
+.factory('httpInterceptor', function($q, $rootScope, $location, $timeout, $injector) {
   var activeRequests = 0;
 
   var started = function(config) {
@@ -326,6 +331,9 @@ solrAdminApp.config([
       delete $rootScope.exceptions[config.url];
     }
     activeRequests++;
+    if (sessionStorage.getItem("auth.header")) {
+      config.headers['Authorization'] = sessionStorage.getItem("auth.header");
+    }
     config.timeout = 10000;
     return config || $q.when(config);
   };
@@ -343,6 +351,11 @@ solrAdminApp.config([
         $rootScope.$broadcast('connectionStatusInactive');
       },2000);
     }
+    if (!$location.path().startsWith('/login')) {
+      sessionStorage.removeItem("http401");
+      sessionStorage.removeItem("auth.state");
+      sessionStorage.removeItem("auth.statusText");
+    }
     return response || $q.when(response);
   };
 
@@ -361,16 +374,38 @@ solrAdminApp.config([
       var $http = $injector.get('$http');
       var result = $http(rejection.config);
       return result;
+    } else if (rejection.status === 401) {
+      // Authentication redirect
+      var headers = rejection.headers();
+      var wwwAuthHeader = headers['www-authenticate'];
+      sessionStorage.setItem("auth.wwwAuthHeader", wwwAuthHeader);
+      sessionStorage.setItem("auth.statusText", rejection.statusText);
+      sessionStorage.setItem("http401", "true");
+      sessionStorage.removeItem("auth.scheme");
+      sessionStorage.removeItem("auth.realm");
+      sessionStorage.removeItem("auth.username");
+      sessionStorage.removeItem("auth.header");
+      sessionStorage.removeItem("auth.state");
+      if ($location.path().includes('/login')) {
+        if (!sessionStorage.getItem("auth.location")) {
+          sessionStorage.setItem("auth.location", "/");
+        }
+      } else {
+        sessionStorage.setItem("auth.location", $location.path());
+        $location.path('/login');
+      }
     } else {
       $rootScope.exceptions[rejection.config.url] = rejection.data.error;
     }
     return $q.reject(rejection);
-  }
+  };
 
   return {request: started, response: ended, responseError: failed};
 })
 .config(function($httpProvider) {
   $httpProvider.interceptors.push("httpInterceptor");
+  // Force BasicAuth plugin to serve us a 'Authorization: xBasic xxxx' header so browser will not pop up login dialogue
+  $httpProvider.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
 })
 .directive('fileModel', function ($parse) {
     return {
@@ -441,6 +476,8 @@ solrAdminApp.controller('MainController', function($scope, $route, $rootScope, $
     $scope.showingLogging = page.lastIndexOf("logging", 0) === 0;
     $scope.showingCloud = page.lastIndexOf("cloud", 0) === 0;
     $scope.page = page;
+    $scope.currentUser = sessionStorage.getItem("auth.username");
+    $scope.http401 = sessionStorage.getItem("http401");
   };
 
   $scope.ping = function() {
@@ -456,7 +493,7 @@ solrAdminApp.controller('MainController', function($scope, $route, $rootScope, $
   }
 
   $scope.showCore = function(core) {
-    $location.url("/" + core.name);
+    $location.url("/" + core.name + "/core-overview");
   }
 
   $scope.showCollection = function(collection) {

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/280f6792/solr/webapp/web/js/angular/controllers/login.js
----------------------------------------------------------------------
diff --git a/solr/webapp/web/js/angular/controllers/login.js b/solr/webapp/web/js/angular/controllers/login.js
new file mode 100644
index 0000000..9935191
--- /dev/null
+++ b/solr/webapp/web/js/angular/controllers/login.js
@@ -0,0 +1,146 @@
+/*
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements.  See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+*/
+
+solrAdminApp.controller('LoginController',
+    ['$scope', '$routeParams', '$rootScope', '$location', '$window', 'AuthenticationService', 'Constants',
+      function ($scope, $routeParams, $rootScope, $location, $window, AuthenticationService, Constants) {
+        $scope.resetMenu("login", Constants.IS_ROOT_PAGE);
+        $scope.subPath = $routeParams.route;
+        $rootScope.exceptions = {};
+
+        // Session variables set in app.js 401 interceptor
+        var wwwAuthHeader = sessionStorage.getItem("auth.wwwAuthHeader");
+        var authScheme = sessionStorage.getItem("auth.scheme");
+        if (wwwAuthHeader) {
+          // Parse www-authenticate header
+          var wwwHeader = wwwAuthHeader.match(/(\w+)\s+(.*)/);
+          authScheme = wwwHeader[1];
+          var authParams = www_auth_parse_params(wwwHeader[2]);
+          if (typeof authParams === 'string' || authParams instanceof String) {
+            $scope.authParamsError = authParams;
+          } else {
+            $scope.authParamsError = undefined;
+          }
+          var realm = authParams['realm'];
+          sessionStorage.setItem("auth.realm", realm);
+          if (authScheme === 'Basic' || authScheme === 'xBasic') {
+            authScheme = 'Basic';
+          }
+          sessionStorage.setItem("auth.scheme", authScheme);
+        }
+
+        var supportedSchemes = ['Basic', 'Bearer'];
+        $scope.authSchemeSupported = supportedSchemes.includes(authScheme);
+        $scope.authScheme = sessionStorage.getItem("auth.scheme");
+        $scope.authRealm = sessionStorage.getItem("auth.realm");
+        $scope.wwwAuthHeader = sessionStorage.getItem("auth.wwwAuthHeader");
+        $scope.statusText = sessionStorage.getItem("auth.statusText");
+        $scope.authConfig = sessionStorage.getItem("auth.config");
+        $scope.authLocation = sessionStorage.getItem("auth.location");
+        $scope.authLoggedinUser = sessionStorage.getItem("auth.username");
+        $scope.authHeader = sessionStorage.getItem("auth.header");
+
+        $scope.login = function () {
+          AuthenticationService.SetCredentials($scope.username, $scope.password);
+          $location.path($scope.authLocation); // Redirect to the location that caused the login prompt
+        };
+
+        $scope.logout = function() {
+          // reset login status
+          AuthenticationService.ClearCredentials();
+          $location.path("/");
+        };
+
+        $scope.isLoggedIn = function() {
+          return (sessionStorage.getItem("auth.username") !== null);
+        };
+      }]);
+
+// This function is copied and adapted from MIT-licensed https://github.com/randymized/www-authenticate/blob/master/lib/parsers.js
+www_auth_parse_params= function (header) {
+  // This parser will definitely fail if there is more than one challenge
+  var params = {};
+  var tok, last_tok, _i, _len, key, value;
+  var state= 0;   //0: token,
+  var m= header.split(/([",=])/);
+  for (_i = 0, _len = m.length; _i < _len; _i++) {
+    last_tok= tok;
+    tok = m[_i];
+    if (!tok.length) continue;
+    switch (state) {
+      case 0: // token
+        key= tok.trim();
+        state= 1; // expect equals
+        continue;
+      case 1: // expect equals
+        if ('=' != tok) return 'Equal sign was expected after '+key;
+        state= 2;
+        continue;
+      case 2: // expect value
+        if ('"' == tok) {
+          value= '';
+          state= 3; // expect quoted
+          continue;
+        }
+        else {
+          params[key]= value= tok.trim();
+          state= 9; // expect comma or end
+          continue;
+        }
+      case 3: // handling quoted string
+        if ('"' == tok) {
+          state= 8; // end quoted
+          continue;
+        }
+        else {
+          value+= tok;
+          state= 3; // continue accumulating quoted string
+          continue;
+        }
+      case 8: // end quote encountered
+        if ('"' == tok) {
+          // double quoted
+          value+= '"';
+          state= 3; // back to quoted string
+          continue;
+        }
+        if (',' == tok) {
+          params[key]= value;
+          state= 0;
+          continue;
+        }
+        else {
+          return 'Unexpected token ('+tok+') after '+value+'"';
+        }
+        continue;
+      case 9: // expect commma
+        if (',' != tok) return 'Comma expected after '+value;
+        state= 0;
+        continue;
+    }
+  }
+  switch (state) {  // terminal state
+    case 0:   // Empty or ignoring terminal comma
+    case 9:   // Expecting comma or end of header
+      return params;
+    case 8:   // Last token was end quote
+      params[key]= value;
+      return params;
+    default:
+      return 'Unexpected end of www-authenticate value.';
+  }
+};

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/280f6792/solr/webapp/web/js/angular/services.js
----------------------------------------------------------------------
diff --git a/solr/webapp/web/js/angular/services.js b/solr/webapp/web/js/angular/services.js
index e3dcd3f..8eb148f 100644
--- a/solr/webapp/web/js/angular/services.js
+++ b/solr/webapp/web/js/angular/services.js
@@ -262,4 +262,26 @@ solrAdminServices.factory('System',
      return $resource(':core/config', {wt: 'json', core: '@core', _:Date.now()}, {
        get: {method: "GET"}
      })
-}]);
+}])
+.factory('AuthenticationService',
+    ['base64', function (base64) {
+        var service = {};
+
+        service.SetCredentials = function (username, password) {
+          var authdata = base64.encode(username + ':' + password);
+
+          sessionStorage.setItem("auth.header", "Basic " + authdata);
+          sessionStorage.setItem("auth.username", username);
+        };
+
+        service.ClearCredentials = function () {
+          sessionStorage.removeItem("auth.header");
+          sessionStorage.removeItem("auth.scheme");
+          sessionStorage.removeItem("auth.realm");
+          sessionStorage.removeItem("auth.username");
+          sessionStorage.removeItem("auth.wwwAuthHeader");
+          sessionStorage.removeItem("auth.statusText");
+        };
+
+        return service;
+      }]);

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/280f6792/solr/webapp/web/libs/angular-utf8-base64.js
----------------------------------------------------------------------
diff --git a/solr/webapp/web/libs/angular-utf8-base64.js b/solr/webapp/web/libs/angular-utf8-base64.js
new file mode 100755
index 0000000..a3a7358
--- /dev/null
+++ b/solr/webapp/web/libs/angular-utf8-base64.js
@@ -0,0 +1,217 @@
+/*
+The MIT License (MIT)
+
+Copyright (c) 2014 Andrey Bezyazychniy
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+*/
+'use strict';
+
+angular.module('ab-base64',[]).constant('base64', (function() {
+
+    /*
+     * Encapsulation of Vassilis Petroulias's base64.js library for AngularJS
+     * Original notice included below
+     */
+
+    /*
+     Copyright Vassilis Petroulias [DRDigit]
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+     */
+    var B64 = {
+        alphabet: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=',
+        lookup: null,
+        ie: /MSIE /.test(navigator.userAgent),
+        ieo: /MSIE [67]/.test(navigator.userAgent),
+        encode: function (s) {
+            /* jshint bitwise:false */
+            var buffer = B64.toUtf8(s),
+                position = -1,
+                result,
+                len = buffer.length,
+                nan0, nan1, nan2, enc = [, , , ];
+            
+            if (B64.ie) {
+                result = [];
+                while (++position < len) {
+                    nan0 = buffer[position];
+                    nan1 = buffer[++position];
+                    enc[0] = nan0 >> 2;
+                    enc[1] = ((nan0 & 3) << 4) | (nan1 >> 4);
+                    if (isNaN(nan1))
+                        enc[2] = enc[3] = 64;
+                    else {
+                        nan2 = buffer[++position];
+                        enc[2] = ((nan1 & 15) << 2) | (nan2 >> 6);
+                        enc[3] = (isNaN(nan2)) ? 64 : nan2 & 63;
+                    }
+                    result.push(B64.alphabet.charAt(enc[0]), B64.alphabet.charAt(enc[1]), B64.alphabet.charAt(enc[2]), B64.alphabet.charAt(enc[3]));
+                }
+                return result.join('');
+            } else {
+                result = '';
+                while (++position < len) {
+                    nan0 = buffer[position];
+                    nan1 = buffer[++position];
+                    enc[0] = nan0 >> 2;
+                    enc[1] = ((nan0 & 3) << 4) | (nan1 >> 4);
+                    if (isNaN(nan1))
+                        enc[2] = enc[3] = 64;
+                    else {
+                        nan2 = buffer[++position];
+                        enc[2] = ((nan1 & 15) << 2) | (nan2 >> 6);
+                        enc[3] = (isNaN(nan2)) ? 64 : nan2 & 63;
+                    }
+                    result += B64.alphabet[enc[0]] + B64.alphabet[enc[1]] + B64.alphabet[enc[2]] + B64.alphabet[enc[3]];
+                }
+                return result;
+            }
+        },
+        decode: function (s) {
+            /* jshint bitwise:false */
+            s = s.replace(/\s/g, '');
+            if (s.length % 4)
+                throw new Error('InvalidLengthError: decode failed: The string to be decoded is not the correct length for a base64 encoded string.');
+            if(/[^A-Za-z0-9+\/=\s]/g.test(s))
+                throw new Error('InvalidCharacterError: decode failed: The string contains characters invalid in a base64 encoded string.');
+
+            var buffer = B64.fromUtf8(s),
+                position = 0,
+                result,
+                len = buffer.length;
+
+            if (B64.ieo) {
+                result = [];
+                while (position < len) {
+                    if (buffer[position] < 128)
+                        result.push(String.fromCharCode(buffer[position++]));
+                    else if (buffer[position] > 191 && buffer[position] < 224)
+                        result.push(String.fromCharCode(((buffer[position++] & 31) << 6) | (buffer[position++] & 63)));
+                    else
+                        result.push(String.fromCharCode(((buffer[position++] & 15) << 12) | ((buffer[position++] & 63) << 6) | (buffer[position++] & 63)));
+                }
+                return result.join('');
+            } else {
+                result = '';
+                while (position < len) {
+                    if (buffer[position] < 128)
+                        result += String.fromCharCode(buffer[position++]);
+                    else if (buffer[position] > 191 && buffer[position] < 224)
+                        result += String.fromCharCode(((buffer[position++] & 31) << 6) | (buffer[position++] & 63));
+                    else
+                        result += String.fromCharCode(((buffer[position++] & 15) << 12) | ((buffer[position++] & 63) << 6) | (buffer[position++] & 63));
+                }
+                return result;
+            }
+        },
+        toUtf8: function (s) {
+            /* jshint bitwise:false */
+            var position = -1,
+                len = s.length,
+                chr, buffer = [];
+            if (/^[\x00-\x7f]*$/.test(s)) while (++position < len)
+                buffer.push(s.charCodeAt(position));
+            else while (++position < len) {
+                chr = s.charCodeAt(position);
+                if (chr < 128)
+                    buffer.push(chr);
+                else if (chr < 2048)
+                    buffer.push((chr >> 6) | 192, (chr & 63) | 128);
+                else
+                    buffer.push((chr >> 12) | 224, ((chr >> 6) & 63) | 128, (chr & 63) | 128);
+            }
+            return buffer;
+        },
+        fromUtf8: function (s) {
+            /* jshint bitwise:false */
+            var position = -1,
+                len, buffer = [],
+                enc = [, , , ];
+            if (!B64.lookup) {
+                len = B64.alphabet.length;
+                B64.lookup = {};
+                while (++position < len)
+                    B64.lookup[B64.alphabet.charAt(position)] = position;
+                position = -1;
+            }
+            len = s.length;
+            while (++position < len) {
+                enc[0] = B64.lookup[s.charAt(position)];
+                enc[1] = B64.lookup[s.charAt(++position)];
+                buffer.push((enc[0] << 2) | (enc[1] >> 4));
+                enc[2] = B64.lookup[s.charAt(++position)];
+                if (enc[2] === 64)
+                    break;
+                buffer.push(((enc[1] & 15) << 4) | (enc[2] >> 2));
+                enc[3] = B64.lookup[s.charAt(++position)];
+                if (enc[3] === 64)
+                    break;
+                buffer.push(((enc[2] & 3) << 6) | enc[3]);
+            }
+            return buffer;
+        }
+    };
+
+    var B64url = {
+        decode: function(input) {
+            // Replace non-url compatible chars with base64 standard chars
+            input = input
+                .replace(/-/g, '+')
+                .replace(/_/g, '/');
+
+            // Pad out with standard base64 required padding characters
+            var pad = input.length % 4;
+            if(pad) {
+              if(pad === 1) {
+                throw new Error('InvalidLengthError: Input base64url string is the wrong length to determine padding');
+              }
+              input += new Array(5-pad).join('=');
+            }
+
+            return B64.decode(input);
+        },
+
+        encode: function(input) {
+            var output = B64.encode(input);
+            return output
+                .replace(/\+/g, '-')
+                .replace(/\//g, '_')
+                .split('=', 1)[0];
+        }
+    };
+
+    return {
+        decode: B64.decode,
+        encode: B64.encode,
+        urldecode: B64url.decode,
+        urlencode: B64url.encode,
+    };
+})());
+

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/280f6792/solr/webapp/web/libs/angular-utf8-base64.min.js
----------------------------------------------------------------------
diff --git a/solr/webapp/web/libs/angular-utf8-base64.min.js b/solr/webapp/web/libs/angular-utf8-base64.min.js
new file mode 100755
index 0000000..e1166c6
--- /dev/null
+++ b/solr/webapp/web/libs/angular-utf8-base64.min.js
@@ -0,0 +1,45 @@
+/*
+The MIT License (MIT)
+
+Copyright (c) 2014 Andrey Bezyazychniy
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+*/
+
+/*
+ * Encapsulation of Vassilis Petroulias's base64.js library for AngularJS
+ * Original notice included below
+ */
+
+/*
+ Copyright Vassilis Petroulias [DRDigit]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+*/
+"use strict";angular.module("ab-base64",[]).constant("base64",function(){var a={alphabet:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",lookup:null,ie:/MSIE /.test(navigator.userAgent),ieo:/MSIE [67]/.test(navigator.userAgent),encode:function(b){var c,d,e,f,g=a.toUtf8(b),h=-1,i=g.length,j=[,,,];if(a.ie){for(c=[];++h<i;)d=g[h],e=g[++h],j[0]=d>>2,j[1]=(3&d)<<4|e>>4,isNaN(e)?j[2]=j[3]=64:(f=g[++h],j[2]=(15&e)<<2|f>>6,j[3]=isNaN(f)?64:63&f),c.push(a.alphabet.charAt(j[0]),a.alphabet.charAt(j[1]),a.alphabet.charAt(j[2]),a.alphabet.charAt(j[3]));return c.join("")}for(c="";++h<i;)d=g[h],e=g[++h],j[0]=d>>2,j[1]=(3&d)<<4|e>>4,isNaN(e)?j[2]=j[3]=64:(f=g[++h],j[2]=(15&e)<<2|f>>6,j[3]=isNaN(f)?64:63&f),c+=a.alphabet[j[0]]+a.alphabet[j[1]]+a.alphabet[j[2]]+a.alphabet[j[3]];return c},decode:function(b){if(b=b.replace(/\s/g,""),b.length%4)throw new Error("InvalidLengthError: decode failed: The string to be decoded is not the correct length for a base64 encoded string.");if(/[^A
 -Za-z0-9+\/=\s]/g.test(b))throw new Error("InvalidCharacterError: decode failed: The string contains characters invalid in a base64 encoded string.");var c,d=a.fromUtf8(b),e=0,f=d.length;if(a.ieo){for(c=[];f>e;)c.push(d[e]<128?String.fromCharCode(d[e++]):d[e]>191&&d[e]<224?String.fromCharCode((31&d[e++])<<6|63&d[e++]):String.fromCharCode((15&d[e++])<<12|(63&d[e++])<<6|63&d[e++]));return c.join("")}for(c="";f>e;)c+=String.fromCharCode(d[e]<128?d[e++]:d[e]>191&&d[e]<224?(31&d[e++])<<6|63&d[e++]:(15&d[e++])<<12|(63&d[e++])<<6|63&d[e++]);return c},toUtf8:function(a){var b,c=-1,d=a.length,e=[];if(/^[\x00-\x7f]*$/.test(a))for(;++c<d;)e.push(a.charCodeAt(c));else for(;++c<d;)b=a.charCodeAt(c),128>b?e.push(b):2048>b?e.push(b>>6|192,63&b|128):e.push(b>>12|224,b>>6&63|128,63&b|128);return e},fromUtf8:function(b){var c,d=-1,e=[],f=[,,,];if(!a.lookup){for(c=a.alphabet.length,a.lookup={};++d<c;)a.lookup[a.alphabet.charAt(d)]=d;d=-1}for(c=b.length;++d<c&&(f[0]=a.lookup[b.charAt(d)],f[1]=a.lookup[
 b.charAt(++d)],e.push(f[0]<<2|f[1]>>4),f[2]=a.lookup[b.charAt(++d)],64!==f[2])&&(e.push((15&f[1])<<4|f[2]>>2),f[3]=a.lookup[b.charAt(++d)],64!==f[3]);)e.push((3&f[2])<<6|f[3]);return e}},b={decode:function(b){b=b.replace(/-/g,"+").replace(/_/g,"/");var c=b.length%4;if(c){if(1===c)throw new Error("InvalidLengthError: Input base64url string is the wrong length to determine padding");b+=new Array(5-c).join("=")}return a.decode(b)},encode:function(b){var c=a.encode(b);return c.replace(/\+/g,"-").replace(/\//g,"_").split("=",1)[0]}};return{decode:a.decode,encode:a.encode,urldecode:b.decode,urlencode:b.encode}}());
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/280f6792/solr/webapp/web/partials/login.html
----------------------------------------------------------------------
diff --git a/solr/webapp/web/partials/login.html b/solr/webapp/web/partials/login.html
new file mode 100644
index 0000000..10a3caf
--- /dev/null
+++ b/solr/webapp/web/partials/login.html
@@ -0,0 +1,80 @@
+<!--
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+<div id="login" class="clearfix">
+
+  <div ng-show="authScheme === 'Basic'">
+    <h1>Basic Authentication</h1>
+    <div class="login-error" ng-show="statusText !== 'require authentication' || authParamsError !== null">
+      {{statusText}}{{authParamsError}}
+    </div>
+    <div ng-show="!isLoggedIn()">
+      <p>
+        Solr requires authentication for resource {{authLocation === '/' ? 'Dashboard' : authLocation}}.<br/>
+        Please log in with your username and password for realm {{authRealm}}.
+      </p>
+      <br/>
+      <div ng-show="error" class="alert alert-danger">{{error}}</div>
+      <form name="form" ng-submit="login()" role="form">
+        <div class="form-group">
+          <label for="username">Username</label>
+          <input type="text" name="username" id="username" class="form-control" ng-model="username" required />
+          <span ng-show="form.username.$dirty && form.username.$error.required" class="help-block">Username is required</span>
+        </div>
+        <div class="form-group">
+          <label for="password">Password</label>
+          <input type="password" name="password" id="password" class="form-control" ng-model="password" required />
+          <span ng-show="form.password.$dirty && form.password.$error.required" class="help-block">Password is required</span>
+        </div>
+        <br/>
+        <div class="form-actions">
+          <button type="submit" ng-disabled="form.$invalid" class="btn btn-danger">Login</button>
+        </div>
+      </form>
+    </div>
+
+    <div ng-show="isLoggedIn()">
+      <p>
+        Logged in as user {{authLoggedinUser}}. Realm={{authRealm}}.<br/>
+      </p>
+      <br/>
+      <form name="logoutForm" ng-submit="logout()" role="form" ng-show="isLoggedIn()">
+        <div class="form-actions">
+          <button type="submit" class="btn btn-danger">Logout</button>
+        </div>
+      </form>
+    </div>
+
+  </div>
+
+
+  <div ng-show="!authSchemeSupported">
+    <h1>Authentication scheme not supported</h1>
+
+    <div class="login-error">
+      {{statusText}}
+    </div>
+    
+    <p>Some or all Solr operations are protected by an authentication scheme that is not yet supported by this Admin UI ({{authScheme}}).</p>
+    <p>Solr returned an error response:
+    <hr/>
+    <pre>HTTP 401 {{statusText}}
+WWW-Authenticate: {{wwwAuthHeader}}</pre>
+    <hr/>
+    </p>
+    <p>A possible workaround may be to use another client that supports this scheme.</p>
+  </div>
+</div>


[4/8] lucene-solr:jira/http2: SOLR-13040: Fix TestSQLHandler, remove delete core calls.

Posted by da...@apache.org.
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bcdc6dad/solr/core/src/test/org/apache/solr/handler/TestSQLHandler.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/handler/TestSQLHandler.java b/solr/core/src/test/org/apache/solr/handler/TestSQLHandler.java
index 21c8a72..eff3a6c 100644
--- a/solr/core/src/test/org/apache/solr/handler/TestSQLHandler.java
+++ b/solr/core/src/test/org/apache/solr/handler/TestSQLHandler.java
@@ -59,7 +59,7 @@ public class TestSQLHandler extends AbstractFullDistribZkTestBase {
   protected String getCloudSolrConfig() {
     return "solrconfig-sql.xml";
   }
-  
+
   @Override
   protected String getCloudSchemaFile() {
     return schemaString;
@@ -80,14 +80,10 @@ public class TestSQLHandler extends AbstractFullDistribZkTestBase {
     resetExceptionIgnores();
   }
 
-  private void delete() throws Exception {
-    deleteCore();
-  }
-
   @Test
-  @AwaitsFix(bugUrl="https://issues.apache.org/jira/browse/SOLR-13040")
   public void doTest() throws Exception {
-    cloudClient.waitForState(DEFAULT_COLLECTION, 30, TimeUnit.SECONDS, SolrCloudTestCase.activeClusterShape(sliceCount, 4));
+    cloudClient.waitForState(DEFAULT_COLLECTION, 30, TimeUnit.SECONDS,
+        SolrCloudTestCase.activeClusterShape(sliceCount, 4));
 
     testBasicSelect();
     testWhere();
@@ -109,2073 +105,1985 @@ public class TestSQLHandler extends AbstractFullDistribZkTestBase {
   }
 
   private void testBasicSelect() throws Exception {
-    try {
-      CloudJettyRunner jetty = this.cloudJettys.get(0);
 
-      del("*:*");
+    CloudJettyRunner jetty = this.cloudJettys.get(0);
 
-      commit();
+    del("*:*");
 
-      indexDoc(sdoc("id", "1", "text", "XXXX XXXX", "str_s", "a", "field_i", "7", "field_i_p", "7",
-          "field_f_p", "7.5", "field_d_p", "7.5", "field_l_p", "7"));
-      indexDoc(sdoc("id", "2", "text", "XXXX XXXX", "str_s", "b", "field_i", "8", "field_i_p", "8",
-          "field_f_p", "8.5", "field_d_p", "8.5","field_l_p", "8" ));
-      indexDoc(sdoc("id", "3", "text", "XXXX XXXX", "str_s", "a", "field_i", "20", "field_i_p", "20",
-          "field_f_p", "20.5", "field_d_p", "20.5", "field_l_p", "20"));
-      indexDoc(sdoc("id", "4", "text", "XXXX XXXX", "str_s", "b", "field_i", "11", "field_i_p", "11",
-          "field_f_p", "11.5", "field_d_p", "11.5", "field_l_p", "11"));
-      indexDoc(sdoc("id", "5", "text", "XXXX XXXX", "str_s", "c", "field_i", "30","field_i_p", "30", "" +
-          "field_f_p", "30.5", "field_d_p", "30.5", "field_l_p", "30"));
-      indexDoc(sdoc("id", "6", "text", "XXXX XXXX", "str_s", "c", "field_i", "40", "field_i_p", "40",
-          "field_f_p", "40.5", "field_d_p", "40.5", "field_l_p", "40"));
-      indexDoc(sdoc("id", "7", "text", "XXXX XXXX", "str_s", "c", "field_i", "50", "field_i_p", "50",
-          "field_f_p", "50.5", "field_d_p", "50.5", "field_l_p", "50"));
-      indexDoc(sdoc("id", "8", "text", "XXXX XXXX", "str_s", "c", "field_i", "60", "field_i_p", "60",
-          "field_f_p", "60.5", "field_d_p", "60.5", "field_l_p", "60"));
-      commit();
+    commit();
 
+    indexDoc(sdoc("id", "1", "text", "XXXX XXXX", "str_s", "a", "field_i", "7", "field_i_p", "7",
+        "field_f_p", "7.5", "field_d_p", "7.5", "field_l_p", "7"));
+    indexDoc(sdoc("id", "2", "text", "XXXX XXXX", "str_s", "b", "field_i", "8", "field_i_p", "8",
+        "field_f_p", "8.5", "field_d_p", "8.5", "field_l_p", "8"));
+    indexDoc(sdoc("id", "3", "text", "XXXX XXXX", "str_s", "a", "field_i", "20", "field_i_p", "20",
+        "field_f_p", "20.5", "field_d_p", "20.5", "field_l_p", "20"));
+    indexDoc(sdoc("id", "4", "text", "XXXX XXXX", "str_s", "b", "field_i", "11", "field_i_p", "11",
+        "field_f_p", "11.5", "field_d_p", "11.5", "field_l_p", "11"));
+    indexDoc(sdoc("id", "5", "text", "XXXX XXXX", "str_s", "c", "field_i", "30", "field_i_p", "30", "" +
+        "field_f_p", "30.5", "field_d_p", "30.5", "field_l_p", "30"));
+    indexDoc(sdoc("id", "6", "text", "XXXX XXXX", "str_s", "c", "field_i", "40", "field_i_p", "40",
+        "field_f_p", "40.5", "field_d_p", "40.5", "field_l_p", "40"));
+    indexDoc(sdoc("id", "7", "text", "XXXX XXXX", "str_s", "c", "field_i", "50", "field_i_p", "50",
+        "field_f_p", "50.5", "field_d_p", "50.5", "field_l_p", "50"));
+    indexDoc(sdoc("id", "8", "text", "XXXX XXXX", "str_s", "c", "field_i", "60", "field_i_p", "60",
+        "field_f_p", "60.5", "field_d_p", "60.5", "field_l_p", "60"));
+    commit();
 
-      System.out.println("############# testBasicSelect() ############");
+    System.out.println("############# testBasicSelect() ############");
 
-      SolrParams sParams = mapParams(CommonParams.QT, "/sql", 
-          "stmt", "select id, field_i, str_s, field_i_p, field_f_p, field_d_p, field_l_p from collection1 where (text='(XXXX)' OR text='XXXX') AND text='XXXX' order by field_i desc");
+    SolrParams sParams = mapParams(CommonParams.QT, "/sql",
+        "stmt",
+        "select id, field_i, str_s, field_i_p, field_f_p, field_d_p, field_l_p from collection1 where (text='(XXXX)' OR text='XXXX') AND text='XXXX' order by field_i desc");
 
-      SolrStream solrStream = new SolrStream(jetty.url, sParams);
-      List<Tuple> tuples = getTuples(solrStream);
+    SolrStream solrStream = new SolrStream(jetty.url, sParams);
+    List<Tuple> tuples = getTuples(solrStream);
 
-      assert(tuples.size() == 8);
-      Tuple tuple;
+    assert (tuples.size() == 8);
+    Tuple tuple;
 
-      tuple = tuples.get(0);
-      assertEquals(tuple.getLong("id").longValue(),8);
-      assertEquals(tuple.getLong("field_i").longValue(), 60);
-      assert(tuple.get("str_s").equals("c"));
-      assertEquals(tuple.getLong("field_i_p").longValue(), 60L);
-      assertEquals(tuple.getDouble("field_f_p"), 60.5, 0.0);
-      assertEquals(tuple.getDouble("field_d_p"), 60.5, 0.0);
-      assertEquals(tuple.getLong("field_l_p").longValue(), 60);
+    tuple = tuples.get(0);
+    assertEquals(tuple.getLong("id").longValue(), 8);
+    assertEquals(tuple.getLong("field_i").longValue(), 60);
+    assert (tuple.get("str_s").equals("c"));
+    assertEquals(tuple.getLong("field_i_p").longValue(), 60L);
+    assertEquals(tuple.getDouble("field_f_p"), 60.5, 0.0);
+    assertEquals(tuple.getDouble("field_d_p"), 60.5, 0.0);
+    assertEquals(tuple.getLong("field_l_p").longValue(), 60);
+
+    tuple = tuples.get(1);
+    assertEquals(tuple.getLong("id").longValue(), 7);
+    assertEquals(tuple.getLong("field_i").longValue(), 50);
+    assert (tuple.get("str_s").equals("c"));
+    assertEquals(tuple.getLong("field_i_p").longValue(), 50);
+    assertEquals(tuple.getDouble("field_f_p"), 50.5, 0.0);
+    assertEquals(tuple.getDouble("field_d_p"), 50.5, 0.0);
+    assertEquals(tuple.getLong("field_l_p").longValue(), 50);
+
+    tuple = tuples.get(2);
+    assertEquals(tuple.getLong("id").longValue(), 6);
+    assertEquals(tuple.getLong("field_i").longValue(), 40);
+    assert (tuple.get("str_s").equals("c"));
+    assertEquals(tuple.getLong("field_i_p").longValue(), 40);
+    assertEquals(tuple.getDouble("field_f_p"), 40.5, 0.0);
+    assertEquals(tuple.getDouble("field_d_p"), 40.5, 0.0);
+    assertEquals(tuple.getLong("field_l_p").longValue(), 40);
+
+    tuple = tuples.get(3);
+    assertEquals(tuple.getLong("id").longValue(), 5);
+    assertEquals(tuple.getLong("field_i").longValue(), 30);
+    assert (tuple.get("str_s").equals("c"));
+    assertEquals(tuple.getLong("field_i_p").longValue(), 30);
+    assertEquals(tuple.getDouble("field_f_p"), 30.5, 0.0);
+    assertEquals(tuple.getDouble("field_d_p"), 30.5, 0.0);
+    assertEquals(tuple.getLong("field_l_p").longValue(), 30);
+
+    tuple = tuples.get(4);
+    assertEquals(tuple.getLong("id").longValue(), 3);
+    assertEquals(tuple.getLong("field_i").longValue(), 20);
+    assert (tuple.get("str_s").equals("a"));
+    assertEquals(tuple.getLong("field_i_p").longValue(), 20);
+    assertEquals(tuple.getDouble("field_f_p"), 20.5, 0.0);
+    assertEquals(tuple.getDouble("field_d_p"), 20.5, 0.0);
+    assertEquals(tuple.getLong("field_l_p").longValue(), 20);
+
+    tuple = tuples.get(5);
+    assertEquals(tuple.getLong("id").longValue(), 4);
+    assertEquals(tuple.getLong("field_i").longValue(), 11);
+    assert (tuple.get("str_s").equals("b"));
+    assertEquals(tuple.getLong("field_i_p").longValue(), 11);
+    assertEquals(tuple.getDouble("field_f_p"), 11.5, 0.0);
+    assertEquals(tuple.getDouble("field_d_p"), 11.5, 0.0);
+    assertEquals(tuple.getLong("field_l_p").longValue(), 11);
+
+    tuple = tuples.get(6);
+    assertEquals(tuple.getLong("id").longValue(), 2);
+    assertEquals(tuple.getLong("field_i").longValue(), 8);
+    assert (tuple.get("str_s").equals("b"));
+    assertEquals(tuple.getLong("field_i_p").longValue(), 8);
+    assertEquals(tuple.getDouble("field_f_p"), 8.5, 0.0);
+    assertEquals(tuple.getDouble("field_d_p"), 8.5, 0.0);
+    assertEquals(tuple.getLong("field_l_p").longValue(), 8);
+
+    tuple = tuples.get(7);
+    assertEquals(tuple.getLong("id").longValue(), 1);
+    assertEquals(tuple.getLong("field_i").longValue(), 7);
+    assert (tuple.get("str_s").equals("a"));
+    assertEquals(tuple.getLong("field_i_p").longValue(), 7);
+    assertEquals(tuple.getDouble("field_f_p"), 7.5, 0.0);
+    assertEquals(tuple.getDouble("field_d_p"), 7.5, 0.0);
+    assertEquals(tuple.getLong("field_l_p").longValue(), 7);
+
+    // Assert field order
+    assertResponseContains(clients.get(0), sParams,
+        "{\"docs\":[{\"id\":\"8\",\"field_i\":60,\"str_s\":\"c\",\"field_i_p\":60,\"field_f_p\":60.5,\"field_d_p\":60.5,\"field_l_p\":60}");
+
+    // Test unlimited unsorted result. Should sort on _version_ desc
+    sParams = mapParams(CommonParams.QT, "/sql", "stmt",
+        "select id, field_i, str_s from collection1 where text='XXXX'");
 
+    solrStream = new SolrStream(jetty.url, sParams);
+    tuples = getTuples(solrStream);
 
+    assert (tuples.size() == 8);
 
+    tuple = tuples.get(0);
+    assert (tuple.getLong("id") == 8);
+    assert (tuple.getLong("field_i") == 60);
+    assert (tuple.get("str_s").equals("c"));
+
+    tuple = tuples.get(1);
+    assert (tuple.getLong("id") == 7);
+    assert (tuple.getLong("field_i") == 50);
+    assert (tuple.get("str_s").equals("c"));
+
+    tuple = tuples.get(2);
+    assert (tuple.getLong("id") == 6);
+    assert (tuple.getLong("field_i") == 40);
+    assert (tuple.get("str_s").equals("c"));
+
+    tuple = tuples.get(3);
+    assert (tuple.getLong("id") == 5);
+    assert (tuple.getLong("field_i") == 30);
+    assert (tuple.get("str_s").equals("c"));
+
+    tuple = tuples.get(4);
+    assert (tuple.getLong("id") == 4);
+    assert (tuple.getLong("field_i") == 11);
+    assert (tuple.get("str_s").equals("b"));
+
+    tuple = tuples.get(5);
+    assert (tuple.getLong("id") == 3);
+    assert (tuple.getLong("field_i") == 20);
+    assert (tuple.get("str_s").equals("a"));
+
+    tuple = tuples.get(6);
+    assert (tuple.getLong("id") == 2);
+    assert (tuple.getLong("field_i") == 8);
+    assert (tuple.get("str_s").equals("b"));
+
+    tuple = tuples.get(7);
+    assert (tuple.getLong("id") == 1);
+    assert (tuple.getLong("field_i") == 7);
+    assert (tuple.get("str_s").equals("a"));
 
-      tuple = tuples.get(1);
-      assertEquals(tuple.getLong("id").longValue(), 7);
-      assertEquals(tuple.getLong("field_i").longValue(), 50);
-      assert(tuple.get("str_s").equals("c"));
-      assertEquals(tuple.getLong("field_i_p").longValue(), 50);
-      assertEquals(tuple.getDouble("field_f_p"), 50.5, 0.0);
-      assertEquals(tuple.getDouble("field_d_p"), 50.5, 0.0);
-      assertEquals(tuple.getLong("field_l_p").longValue(), 50);
+    sParams = mapParams(CommonParams.QT, "/sql",
+        "stmt", "select id, field_i, str_s from collection1 where text='XXXX' order by field_i desc limit 1");
 
+    solrStream = new SolrStream(jetty.url, sParams);
+    tuples = getTuples(solrStream);
 
+    assert (tuples.size() == 1);
 
+    tuple = tuples.get(0);
+    assert (tuple.getLong("id") == 8);
+    assert (tuple.getLong("field_i") == 60);
+    assert (tuple.get("str_s").equals("c"));
 
-      tuple = tuples.get(2);
-      assertEquals(tuple.getLong("id").longValue(),6);
-      assertEquals(tuple.getLong("field_i").longValue(), 40);
-      assert(tuple.get("str_s").equals("c"));
-      assertEquals(tuple.getLong("field_i_p").longValue(), 40);
-      assertEquals(tuple.getDouble("field_f_p"), 40.5, 0.0);
-      assertEquals(tuple.getDouble("field_d_p"), 40.5, 0.0);
-      assertEquals(tuple.getLong("field_l_p").longValue(), 40);
+    sParams = mapParams(CommonParams.QT, "/sql", "stmt",
+        "select id, field_i, str_s from collection1 where text='XXXX' AND id='(1 2 3)' order by field_i desc");
 
+    solrStream = new SolrStream(jetty.url, sParams);
+    tuples = getTuples(solrStream);
 
+    assert (tuples.size() == 3);
 
+    tuple = tuples.get(0);
+    assert (tuple.getLong("id") == 3);
+    assert (tuple.getLong("field_i") == 20);
+    assert (tuple.get("str_s").equals("a"));
 
+    tuple = tuples.get(1);
+    assert (tuple.getLong("id") == 2);
+    assert (tuple.getLong("field_i") == 8);
+    assert (tuple.get("str_s").equals("b"));
 
-      tuple = tuples.get(3);
-      assertEquals(tuple.getLong("id").longValue(), 5);
-      assertEquals(tuple.getLong("field_i").longValue(), 30);
-      assert(tuple.get("str_s").equals("c"));
-      assertEquals(tuple.getLong("field_i_p").longValue(), 30);
-      assertEquals(tuple.getDouble("field_f_p"), 30.5, 0.0);
-      assertEquals(tuple.getDouble("field_d_p"), 30.5, 0.0);
-      assertEquals(tuple.getLong("field_l_p").longValue(), 30);
+    tuple = tuples.get(2);
+    assert (tuple.getLong("id") == 1);
+    assert (tuple.getLong("field_i") == 7);
+    assert (tuple.get("str_s").equals("a"));
 
+    sParams = mapParams(CommonParams.QT, "/sql",
+        "stmt",
+        "select id as myId, field_i as myInt, str_s as myString from collection1 where text='XXXX' AND id='(1 2 3)' order by myInt desc");
 
+    solrStream = new SolrStream(jetty.url, sParams);
+    tuples = getTuples(solrStream);
 
+    assert (tuples.size() == 3);
 
-      tuple = tuples.get(4);
-      assertEquals(tuple.getLong("id").longValue(),3);
-      assertEquals(tuple.getLong("field_i").longValue(), 20);
-      assert(tuple.get("str_s").equals("a"));
-      assertEquals(tuple.getLong("field_i_p").longValue(), 20);
-      assertEquals(tuple.getDouble("field_f_p"), 20.5, 0.0);
-      assertEquals(tuple.getDouble("field_d_p"), 20.5, 0.0);
-      assertEquals(tuple.getLong("field_l_p").longValue(), 20);
+    tuple = tuples.get(0);
+    assert (tuple.getLong("myId") == 3);
+    assert (tuple.getLong("myInt") == 20);
+    assert (tuple.get("myString").equals("a"));
 
+    tuple = tuples.get(1);
+    assert (tuple.getLong("myId") == 2);
+    assert (tuple.getLong("myInt") == 8);
+    assert (tuple.get("myString").equals("b"));
 
+    tuple = tuples.get(2);
+    assert (tuple.getLong("myId") == 1);
+    assert (tuple.getLong("myInt") == 7);
+    assert (tuple.get("myString").equals("a"));
 
+    sParams = mapParams(CommonParams.QT, "/sql",
+        "stmt",
+        "select id as myId, field_i as myInt, str_s as myString from collection1 where text='XXXX' AND id='(1 2 3)' order by field_i desc");
 
-      tuple = tuples.get(5);
-      assertEquals(tuple.getLong("id").longValue(), 4);
-      assertEquals(tuple.getLong("field_i").longValue(), 11);
-      assert(tuple.get("str_s").equals("b"));
-      assertEquals(tuple.getLong("field_i_p").longValue(), 11);
-      assertEquals(tuple.getDouble("field_f_p"), 11.5, 0.0);
-      assertEquals(tuple.getDouble("field_d_p"), 11.5, 0.0);
-      assertEquals(tuple.getLong("field_l_p").longValue(), 11);
+    solrStream = new SolrStream(jetty.url, sParams);
+    tuples = getTuples(solrStream);
 
+    assert (tuples.size() == 3);
 
+    tuple = tuples.get(0);
+    assert (tuple.getLong("myId") == 3);
+    assert (tuple.getLong("myInt") == 20);
+    assert (tuple.get("myString").equals("a"));
 
+    tuple = tuples.get(1);
+    assert (tuple.getLong("myId") == 2);
+    assert (tuple.getLong("myInt") == 8);
+    assert (tuple.get("myString").equals("b"));
 
-      tuple = tuples.get(6);
-      assertEquals(tuple.getLong("id").longValue(), 2);
-      assertEquals(tuple.getLong("field_i").longValue(), 8);
-      assert(tuple.get("str_s").equals("b"));
-      assertEquals(tuple.getLong("field_i_p").longValue(), 8);
-      assertEquals(tuple.getDouble("field_f_p"), 8.5, 0.0);
-      assertEquals(tuple.getDouble("field_d_p"), 8.5, 0.0);
-      assertEquals(tuple.getLong("field_l_p").longValue(), 8);
+    tuple = tuples.get(2);
+    assert (tuple.getLong("myId") == 1);
+    assert (tuple.getLong("myInt") == 7);
+    assert (tuple.get("myString").equals("a"));
 
+    // Test after reload SOLR-9059//
+    Replica leader = getShardLeader("collection1", "shard1", 30 /* timeout secs */);
 
+    // reload collection and wait to see the core report it has been reloaded
+    boolean wasReloaded = reloadCollection(leader, "collection1");
+    assertTrue(wasReloaded);
 
+    sParams = mapParams(CommonParams.QT, "/sql",
+        "stmt",
+        "select id as myId, field_i as myInt, str_s as myString from collection1 where text='XXXX' AND id='(1 2 3)' order by field_i desc");
 
-      tuple = tuples.get(7);
-      assertEquals(tuple.getLong("id").longValue(), 1);
-      assertEquals(tuple.getLong("field_i").longValue(), 7);
-      assert(tuple.get("str_s").equals("a"));
-      assertEquals(tuple.getLong("field_i_p").longValue(), 7);
-      assertEquals(tuple.getDouble("field_f_p"), 7.5, 0.0);
-      assertEquals(tuple.getDouble("field_d_p"), 7.5, 0.0);
-      assertEquals(tuple.getLong("field_l_p").longValue(), 7);
+    solrStream = new SolrStream(jetty.url, sParams);
+    tuples = getTuples(solrStream);
 
+    assert (tuples.size() == 3);
 
+    tuple = tuples.get(0);
+    assert (tuple.getLong("myId") == 3);
+    assert (tuple.getLong("myInt") == 20);
+    assert (tuple.get("myString").equals("a"));
 
-      //Assert field order
-      assertResponseContains(clients.get(0), sParams, "{\"docs\":[{\"id\":\"8\",\"field_i\":60,\"str_s\":\"c\",\"field_i_p\":60,\"field_f_p\":60.5,\"field_d_p\":60.5,\"field_l_p\":60}");
+    tuple = tuples.get(1);
+    assert (tuple.getLong("myId") == 2);
+    assert (tuple.getLong("myInt") == 8);
+    assert (tuple.get("myString").equals("b"));
 
-      //Test unlimited unsorted result. Should sort on _version_ desc
-      sParams = mapParams(CommonParams.QT, "/sql", "stmt", "select id, field_i, str_s from collection1 where text='XXXX'");
+    tuple = tuples.get(2);
+    assert (tuple.getLong("myId") == 1);
+    assert (tuple.getLong("myInt") == 7);
+    assert (tuple.get("myString").equals("a"));
 
-      solrStream = new SolrStream(jetty.url, sParams);
-      tuples = getTuples(solrStream);
+    // SOLR-8845 - Test to make sure that 1 = 0 works for things like Spark SQL
+    sParams = mapParams(CommonParams.QT, "/sql",
+        "stmt", "select id, field_i, str_s from collection1 where 1 = 0");
 
-      assert(tuples.size() == 8);
+    solrStream = new SolrStream(jetty.url, sParams);
+    tuples = getTuples(solrStream);
 
-      tuple = tuples.get(0);
-      assert(tuple.getLong("id") == 8);
-      assert(tuple.getLong("field_i") == 60);
-      assert(tuple.get("str_s").equals("c"));
+    assertEquals(0, tuples.size());
 
-      tuple = tuples.get(1);
-      assert(tuple.getLong("id") == 7);
-      assert(tuple.getLong("field_i") == 50);
-      assert(tuple.get("str_s").equals("c"));
+  }
 
-      tuple = tuples.get(2);
-      assert(tuple.getLong("id") == 6);
-      assert(tuple.getLong("field_i") == 40);
-      assert(tuple.get("str_s").equals("c"));
+  private void testWhere() throws Exception {
 
-      tuple = tuples.get(3);
-      assert(tuple.getLong("id") == 5);
-      assert(tuple.getLong("field_i") == 30);
-      assert(tuple.get("str_s").equals("c"));
+    CloudJettyRunner jetty = this.cloudJettys.get(0);
 
-      tuple = tuples.get(4);
-      assert(tuple.getLong("id") == 4);
-      assert(tuple.getLong("field_i") == 11);
-      assert(tuple.get("str_s").equals("b"));
+    del("*:*");
 
-      tuple = tuples.get(5);
-      assert(tuple.getLong("id") == 3);
-      assert(tuple.getLong("field_i") == 20);
-      assert(tuple.get("str_s").equals("a"));
+    commit();
 
-      tuple = tuples.get(6);
-      assert(tuple.getLong("id") == 2);
-      assert(tuple.getLong("field_i") == 8);
-      assert(tuple.get("str_s").equals("b"));
+    indexDoc(sdoc("id", "1", "text", "XXXX XXXX", "str_s", "a", "field_i", "7"));
+    indexDoc(sdoc("id", "2", "text", "XXXX XXXX", "str_s", "b", "field_i", "8"));
+    indexDoc(sdoc("id", "3", "text", "XXXX XXXX", "str_s", "a", "field_i", "20"));
+    indexDoc(sdoc("id", "4", "text", "XXXX XXXX", "str_s", "b", "field_i", "11"));
+    indexDoc(sdoc("id", "5", "text", "XXXX XXXX", "str_s", "c", "field_i", "30"));
+    indexDoc(sdoc("id", "6", "text", "XXXX XXXX", "str_s", "c", "field_i", "40"));
+    indexDoc(sdoc("id", "7", "text", "XXXX XXXX", "str_s", "c", "field_i", "50"));
+    indexDoc(sdoc("id", "8", "text", "XXXX XXXX", "str_s", "c", "field_i", "60"));
+    commit();
 
-      tuple = tuples.get(7);
-      assert(tuple.getLong("id") == 1);
-      assert(tuple.getLong("field_i") == 7);
-      assert(tuple.get("str_s").equals("a"));
+    // Equals
+    SolrParams sParams = mapParams(CommonParams.QT, "/sql",
+        "stmt", "select id from collection1 where id = 1 order by id asc");
 
+    SolrStream solrStream = new SolrStream(jetty.url, sParams);
+    List<Tuple> tuples = getTuples(solrStream);
 
-      sParams = mapParams(CommonParams.QT, "/sql", 
-          "stmt", "select id, field_i, str_s from collection1 where text='XXXX' order by field_i desc limit 1");
+    assertEquals(1, tuples.size());
 
-      solrStream = new SolrStream(jetty.url, sParams);
-      tuples = getTuples(solrStream);
+    Tuple tuple = tuples.get(0);
+    assertEquals("1", tuple.get("id"));
 
-      assert(tuples.size() == 1);
+    // Not Equals <>
+    sParams = mapParams(CommonParams.QT, "/sql",
+        "stmt", "select id from collection1 where id <> 1 order by id asc limit 10");
 
-      tuple = tuples.get(0);
-      assert(tuple.getLong("id") == 8);
-      assert(tuple.getLong("field_i") == 60);
-      assert(tuple.get("str_s").equals("c"));
+    solrStream = new SolrStream(jetty.url, sParams);
+    tuples = getTuples(solrStream);
 
-      sParams = mapParams(CommonParams.QT, "/sql", "stmt", "select id, field_i, str_s from collection1 where text='XXXX' AND id='(1 2 3)' order by field_i desc");
+    assertEquals(7, tuples.size());
 
-      solrStream = new SolrStream(jetty.url, sParams);
-      tuples = getTuples(solrStream);
+    tuple = tuples.get(0);
+    assertEquals("2", tuple.get("id"));
+    tuple = tuples.get(1);
+    assertEquals("3", tuple.get("id"));
+    tuple = tuples.get(2);
+    assertEquals("4", tuple.get("id"));
+    tuple = tuples.get(3);
+    assertEquals("5", tuple.get("id"));
+    tuple = tuples.get(4);
+    assertEquals("6", tuple.get("id"));
+    tuple = tuples.get(5);
+    assertEquals("7", tuple.get("id"));
+    tuple = tuples.get(6);
+    assertEquals("8", tuple.get("id"));
+
+    // TODO requires different Calcite SQL conformance level
+    // Not Equals !=
+    // sParams = mapParams(CommonParams.QT, "/sql",
+    // "stmt", "select id from collection1 where id != 1 order by id asc limit 10");
+    //
+    // solrStream = new SolrStream(jetty.url, sParams);
+    // tuples = getTuples(solrStream);
+    //
+    // assertEquals(7, tuples.size());
+    //
+    // tuple = tuples.get(0);
+    // assertEquals(2L, tuple.get("id"));
+    // tuple = tuples.get(1);
+    // assertEquals(3L, tuple.get("id"));
+    // tuple = tuples.get(2);
+    // assertEquals(4L, tuple.get("id"));
+    // tuple = tuples.get(3);
+    // assertEquals(5L, tuple.get("id"));
+    // tuple = tuples.get(4);
+    // assertEquals(6L, tuple.get("id"));
+    // tuple = tuples.get(5);
+    // assertEquals(7L, tuple.get("id"));
+    // tuple = tuples.get(6);
+    // assertEquals(8L, tuple.get("id"));
+
+    // Less than
+    sParams = mapParams(CommonParams.QT, "/sql",
+        "stmt", "select id from collection1 where id < 2 order by id asc");
 
-      assert(tuples.size() == 3);
+    solrStream = new SolrStream(jetty.url, sParams);
+    tuples = getTuples(solrStream);
 
-      tuple = tuples.get(0);
-      assert(tuple.getLong("id") == 3);
-      assert(tuple.getLong("field_i") == 20);
-      assert(tuple.get("str_s").equals("a"));
+    assertEquals(1, tuples.size());
 
-      tuple = tuples.get(1);
-      assert(tuple.getLong("id") == 2);
-      assert(tuple.getLong("field_i") == 8);
-      assert(tuple.get("str_s").equals("b"));
+    tuple = tuples.get(0);
+    assertEquals("1", tuple.get("id"));
 
-      tuple = tuples.get(2);
-      assert(tuple.getLong("id") == 1);
-      assert(tuple.getLong("field_i") == 7);
-      assert(tuple.get("str_s").equals("a"));
+    // Less than equal
+    sParams = mapParams(CommonParams.QT, "/sql",
+        "stmt", "select id from collection1 where id <= 2 order by id asc");
 
-      sParams = mapParams(CommonParams.QT, "/sql", 
-          "stmt", "select id as myId, field_i as myInt, str_s as myString from collection1 where text='XXXX' AND id='(1 2 3)' order by myInt desc");
+    solrStream = new SolrStream(jetty.url, sParams);
+    tuples = getTuples(solrStream);
 
-      solrStream = new SolrStream(jetty.url, sParams);
-      tuples = getTuples(solrStream);
+    assertEquals(2, tuples.size());
 
-      assert(tuples.size() == 3);
+    tuple = tuples.get(0);
+    assertEquals("1", tuple.get("id"));
+    tuple = tuples.get(1);
+    assertEquals("2", tuple.get("id"));
 
-      tuple = tuples.get(0);
-      assert(tuple.getLong("myId") == 3);
-      assert(tuple.getLong("myInt") == 20);
-      assert(tuple.get("myString").equals("a"));
+    // Greater than
+    sParams = mapParams(CommonParams.QT, "/sql",
+        "stmt", "select id from collection1 where id > 7 order by id asc");
 
-      tuple = tuples.get(1);
-      assert(tuple.getLong("myId") == 2);
-      assert(tuple.getLong("myInt") == 8);
-      assert(tuple.get("myString").equals("b"));
+    solrStream = new SolrStream(jetty.url, sParams);
+    tuples = getTuples(solrStream);
 
-      tuple = tuples.get(2);
-      assert(tuple.getLong("myId") == 1);
-      assert(tuple.getLong("myInt") == 7);
-      assert(tuple.get("myString").equals("a"));
+    assertEquals(1, tuples.size());
 
-      sParams = mapParams(CommonParams.QT, "/sql",
-          "stmt", "select id as myId, field_i as myInt, str_s as myString from collection1 where text='XXXX' AND id='(1 2 3)' order by field_i desc");
+    tuple = tuples.get(0);
+    assertEquals("8", tuple.get("id"));
 
-      solrStream = new SolrStream(jetty.url, sParams);
-      tuples = getTuples(solrStream);
+    // Greater than equal
+    sParams = mapParams(CommonParams.QT, "/sql",
+        "stmt", "select id from collection1 where id >= 7 order by id asc");
 
-      assert(tuples.size() == 3);
+    solrStream = new SolrStream(jetty.url, sParams);
+    tuples = getTuples(solrStream);
 
-      tuple = tuples.get(0);
-      assert(tuple.getLong("myId") == 3);
-      assert(tuple.getLong("myInt") == 20);
-      assert(tuple.get("myString").equals("a"));
+    assertEquals(2, tuples.size());
 
-      tuple = tuples.get(1);
-      assert(tuple.getLong("myId") == 2);
-      assert(tuple.getLong("myInt") == 8);
-      assert(tuple.get("myString").equals("b"));
+    tuple = tuples.get(0);
+    assertEquals("7", tuple.get("id"));
+    tuple = tuples.get(1);
+    assertEquals("8", tuple.get("id"));
 
-      tuple = tuples.get(2);
-      assert(tuple.getLong("myId") == 1);
-      assert(tuple.getLong("myInt") == 7);
-      assert(tuple.get("myString").equals("a"));
+  }
 
-      //Test after reload SOLR-9059//
-      Replica leader = getShardLeader("collection1", "shard1", 30 /* timeout secs */);
+  private void testMixedCaseFields() throws Exception {
 
-      // reload collection and wait to see the core report it has been reloaded
-      boolean wasReloaded = reloadCollection(leader, "collection1");
-      assertTrue(wasReloaded);
+    CloudJettyRunner jetty = this.cloudJettys.get(0);
 
-      sParams = mapParams(CommonParams.QT, "/sql",
-          "stmt", "select id as myId, field_i as myInt, str_s as myString from collection1 where text='XXXX' AND id='(1 2 3)' order by field_i desc");
+    del("*:*");
 
-      solrStream = new SolrStream(jetty.url, sParams);
-      tuples = getTuples(solrStream);
+    commit();
 
-      assert(tuples.size() == 3);
+    indexDoc(sdoc("id", "1", "Text_t", "XXXX XXXX", "Str_s", "a", "Field_i", "7"));
+    indexDoc(sdoc("id", "2", "Text_t", "XXXX XXXX", "Str_s", "b", "Field_i", "8"));
+    indexDoc(sdoc("id", "3", "Text_t", "XXXX XXXX", "Str_s", "a", "Field_i", "20"));
+    indexDoc(sdoc("id", "4", "Text_t", "XXXX XXXX", "Str_s", "b", "Field_i", "11"));
+    indexDoc(sdoc("id", "5", "Text_t", "XXXX XXXX", "Str_s", "c", "Field_i", "30"));
+    indexDoc(sdoc("id", "6", "Text_t", "XXXX XXXX", "Str_s", "c", "Field_i", "40"));
+    indexDoc(sdoc("id", "7", "Text_t", "XXXX XXXX", "Str_s", "c", "Field_i", "50"));
+    indexDoc(sdoc("id", "8", "Text_t", "XXXX XXXX", "Str_s", "c", "Field_i", "60"));
+    commit();
 
-      tuple = tuples.get(0);
-      assert(tuple.getLong("myId") == 3);
-      assert(tuple.getLong("myInt") == 20);
-      assert(tuple.get("myString").equals("a"));
+    SolrParams sParams = mapParams(CommonParams.QT, "/sql", "aggregationMode", "map_reduce",
+        "stmt", "select id, Field_i, Str_s from collection1 where Text_t='XXXX' order by Field_i desc");
 
-      tuple = tuples.get(1);
-      assert(tuple.getLong("myId") == 2);
-      assert(tuple.getLong("myInt") == 8);
-      assert(tuple.get("myString").equals("b"));
+    SolrStream solrStream = new SolrStream(jetty.url, sParams);
+    List<Tuple> tuples = getTuples(solrStream);
 
-      tuple = tuples.get(2);
-      assert(tuple.getLong("myId") == 1);
-      assert(tuple.getLong("myInt") == 7);
-      assert(tuple.get("myString").equals("a"));
+    assert (tuples.size() == 8);
 
-      // SOLR-8845 - Test to make sure that 1 = 0 works for things like Spark SQL
-      sParams = mapParams(CommonParams.QT, "/sql",
-          "stmt", "select id, field_i, str_s from collection1 where 1 = 0");
+    Tuple tuple;
 
-      solrStream = new SolrStream(jetty.url, sParams);
-      tuples = getTuples(solrStream);
+    tuple = tuples.get(0);
+    assert (tuple.getLong("id") == 8);
+    assert (tuple.getLong("Field_i") == 60);
+    assert (tuple.get("Str_s").equals("c"));
+
+    tuple = tuples.get(1);
+    assert (tuple.getLong("id") == 7);
+    assert (tuple.getLong("Field_i") == 50);
+    assert (tuple.get("Str_s").equals("c"));
+
+    tuple = tuples.get(2);
+    assert (tuple.getLong("id") == 6);
+    assert (tuple.getLong("Field_i") == 40);
+    assert (tuple.get("Str_s").equals("c"));
+
+    tuple = tuples.get(3);
+    assert (tuple.getLong("id") == 5);
+    assert (tuple.getLong("Field_i") == 30);
+    assert (tuple.get("Str_s").equals("c"));
+
+    tuple = tuples.get(4);
+    assert (tuple.getLong("id") == 3);
+    assert (tuple.getLong("Field_i") == 20);
+    assert (tuple.get("Str_s").equals("a"));
+
+    tuple = tuples.get(5);
+    assert (tuple.getLong("id") == 4);
+    assert (tuple.getLong("Field_i") == 11);
+    assert (tuple.get("Str_s").equals("b"));
+
+    tuple = tuples.get(6);
+    assert (tuple.getLong("id") == 2);
+    assert (tuple.getLong("Field_i") == 8);
+    assert (tuple.get("Str_s").equals("b"));
+
+    tuple = tuples.get(7);
+    assert (tuple.getLong("id") == 1);
+    assert (tuple.getLong("Field_i") == 7);
+    assert (tuple.get("Str_s").equals("a"));
+
+    // TODO get sum(Field_i) as named one
+    sParams = mapParams(CommonParams.QT, "/sql",
+        "stmt",
+        "select Str_s, sum(Field_i) from collection1 where id='(1 8)' group by Str_s having (sum(Field_i) = 7 OR sum(Field_i) = 60) order by sum(Field_i) desc");
 
-      assertEquals(0, tuples.size());
-    } finally {
-      delete();
-    }
-  }
+    solrStream = new SolrStream(jetty.url, sParams);
+    tuples = getTuples(solrStream);
+
+    assert (tuples.size() == 2);
+
+    tuple = tuples.get(0);
+    assert (tuple.get("Str_s").equals("c"));
+    assert (tuple.getDouble("EXPR$1") == 60);
+
+    tuple = tuples.get(1);
+    assert (tuple.get("Str_s").equals("a"));
+    assert (tuple.getDouble("EXPR$1") == 7);
+
+    sParams = mapParams(CommonParams.QT, "/sql", "aggregationMode", "map_reduce",
+        "stmt",
+        "select Str_s, sum(Field_i) from collection1 where id='(1 8)' group by Str_s having (sum(Field_i) = 7 OR sum(Field_i) = 60) order by sum(Field_i) desc");
+
+    solrStream = new SolrStream(jetty.url, sParams);
+    tuples = getTuples(solrStream);
+
+    assert (tuples.size() == 2);
+
+    tuple = tuples.get(0);
+    assert (tuple.get("Str_s").equals("c"));
+    assert (tuple.getDouble("EXPR$1") == 60);
+
+    tuple = tuples.get(1);
+    assert (tuple.get("Str_s").equals("a"));
+    assert (tuple.getDouble("EXPR$1") == 7);
 
-  private void testWhere() throws Exception {
-    try {
-      CloudJettyRunner jetty = this.cloudJettys.get(0);
-
-      del("*:*");
-
-      commit();
-
-      indexDoc(sdoc("id", "1", "text", "XXXX XXXX", "str_s", "a", "field_i", "7"));
-      indexDoc(sdoc("id", "2", "text", "XXXX XXXX", "str_s", "b", "field_i", "8"));
-      indexDoc(sdoc("id", "3", "text", "XXXX XXXX", "str_s", "a", "field_i", "20"));
-      indexDoc(sdoc("id", "4", "text", "XXXX XXXX", "str_s", "b", "field_i", "11"));
-      indexDoc(sdoc("id", "5", "text", "XXXX XXXX", "str_s", "c", "field_i", "30"));
-      indexDoc(sdoc("id", "6", "text", "XXXX XXXX", "str_s", "c", "field_i", "40"));
-      indexDoc(sdoc("id", "7", "text", "XXXX XXXX", "str_s", "c", "field_i", "50"));
-      indexDoc(sdoc("id", "8", "text", "XXXX XXXX", "str_s", "c", "field_i", "60"));
-      commit();
-
-      // Equals
-      SolrParams sParams = mapParams(CommonParams.QT, "/sql",
-          "stmt", "select id from collection1 where id = 1 order by id asc");
-
-      SolrStream solrStream = new SolrStream(jetty.url, sParams);
-      List<Tuple> tuples = getTuples(solrStream);
-
-      assertEquals(1, tuples.size());
-
-      Tuple tuple = tuples.get(0);
-      assertEquals("1", tuple.get("id"));
-
-      // Not Equals <>
-      sParams = mapParams(CommonParams.QT, "/sql",
-          "stmt", "select id from collection1 where id <> 1 order by id asc limit 10");
-
-      solrStream = new SolrStream(jetty.url, sParams);
-      tuples = getTuples(solrStream);
-
-      assertEquals(7, tuples.size());
-
-      tuple = tuples.get(0);
-      assertEquals("2", tuple.get("id"));
-      tuple = tuples.get(1);
-      assertEquals("3", tuple.get("id"));
-      tuple = tuples.get(2);
-      assertEquals("4", tuple.get("id"));
-      tuple = tuples.get(3);
-      assertEquals("5", tuple.get("id"));
-      tuple = tuples.get(4);
-      assertEquals("6", tuple.get("id"));
-      tuple = tuples.get(5);
-      assertEquals("7", tuple.get("id"));
-      tuple = tuples.get(6);
-      assertEquals("8", tuple.get("id"));
-
-      // TODO requires different Calcite SQL conformance level
-      // Not Equals !=
-//      sParams = mapParams(CommonParams.QT, "/sql",
-//          "stmt", "select id from collection1 where id != 1 order by id asc limit 10");
-//
-//      solrStream = new SolrStream(jetty.url, sParams);
-//      tuples = getTuples(solrStream);
-//
-//      assertEquals(7, tuples.size());
-//
-//      tuple = tuples.get(0);
-//      assertEquals(2L, tuple.get("id"));
-//      tuple = tuples.get(1);
-//      assertEquals(3L, tuple.get("id"));
-//      tuple = tuples.get(2);
-//      assertEquals(4L, tuple.get("id"));
-//      tuple = tuples.get(3);
-//      assertEquals(5L, tuple.get("id"));
-//      tuple = tuples.get(4);
-//      assertEquals(6L, tuple.get("id"));
-//      tuple = tuples.get(5);
-//      assertEquals(7L, tuple.get("id"));
-//      tuple = tuples.get(6);
-//      assertEquals(8L, tuple.get("id"));
-
-      // Less than
-      sParams = mapParams(CommonParams.QT, "/sql",
-          "stmt", "select id from collection1 where id < 2 order by id asc");
-
-      solrStream = new SolrStream(jetty.url, sParams);
-      tuples = getTuples(solrStream);
-
-      assertEquals(1, tuples.size());
-
-      tuple = tuples.get(0);
-      assertEquals("1", tuple.get("id"));
-
-      // Less than equal
-      sParams = mapParams(CommonParams.QT, "/sql",
-          "stmt", "select id from collection1 where id <= 2 order by id asc");
-
-      solrStream = new SolrStream(jetty.url, sParams);
-      tuples = getTuples(solrStream);
-
-      assertEquals(2, tuples.size());
-
-      tuple = tuples.get(0);
-      assertEquals("1", tuple.get("id"));
-      tuple = tuples.get(1);
-      assertEquals("2", tuple.get("id"));
-
-      // Greater than
-      sParams = mapParams(CommonParams.QT, "/sql",
-          "stmt", "select id from collection1 where id > 7 order by id asc");
-
-      solrStream = new SolrStream(jetty.url, sParams);
-      tuples = getTuples(solrStream);
-
-      assertEquals(1, tuples.size());
-
-      tuple = tuples.get(0);
-      assertEquals("8", tuple.get("id"));
-
-      // Greater than equal
-      sParams = mapParams(CommonParams.QT, "/sql",
-          "stmt", "select id from collection1 where id >= 7 order by id asc");
-
-      solrStream = new SolrStream(jetty.url, sParams);
-      tuples = getTuples(solrStream);
-
-      assertEquals(2, tuples.size());
-
-      tuple = tuples.get(0);
-      assertEquals("7", tuple.get("id"));
-      tuple = tuples.get(1);
-      assertEquals("8", tuple.get("id"));
-
-    } finally {
-      delete();
-    }
   }
 
-  private void testMixedCaseFields() throws Exception {
-    try {
+  private void testSQLException() throws Exception {
 
-      CloudJettyRunner jetty = this.cloudJettys.get(0);
+    CloudJettyRunner jetty = this.cloudJettys.get(0);
 
-      del("*:*");
+    del("*:*");
 
-      commit();
+    commit();
 
-      indexDoc(sdoc("id", "1", "Text_t", "XXXX XXXX", "Str_s", "a", "Field_i", "7"));
-      indexDoc(sdoc("id", "2", "Text_t", "XXXX XXXX", "Str_s", "b", "Field_i", "8"));
-      indexDoc(sdoc("id", "3", "Text_t", "XXXX XXXX", "Str_s", "a", "Field_i", "20"));
-      indexDoc(sdoc("id", "4", "Text_t", "XXXX XXXX", "Str_s", "b", "Field_i", "11"));
-      indexDoc(sdoc("id", "5", "Text_t", "XXXX XXXX", "Str_s", "c", "Field_i", "30"));
-      indexDoc(sdoc("id", "6", "Text_t", "XXXX XXXX", "Str_s", "c", "Field_i", "40"));
-      indexDoc(sdoc("id", "7", "Text_t", "XXXX XXXX", "Str_s", "c", "Field_i", "50"));
-      indexDoc(sdoc("id", "8", "Text_t", "XXXX XXXX", "Str_s", "c", "Field_i", "60"));
-      commit();
+    indexDoc(sdoc("id", "1", "text", "XXXX XXXX", "str_s", "a", "field_i", "7"));
+    indexDoc(sdoc("id", "2", "text", "XXXX XXXX", "str_s", "b", "field_i", "8"));
+    indexDoc(sdoc("id", "3", "text", "XXXX XXXX", "str_s", "a", "field_i", "20"));
+    indexDoc(sdoc("id", "4", "text", "XXXX XXXX", "str_s", "b", "field_i", "11"));
+    indexDoc(sdoc("id", "5", "text", "XXXX XXXX", "str_s", "c", "field_i", "30"));
+    indexDoc(sdoc("id", "6", "text", "XXXX XXXX", "str_s", "c", "field_i", "40"));
+    indexDoc(sdoc("id", "7", "text", "XXXX XXXX", "str_s", "c", "field_i", "50"));
+    indexDoc(sdoc("id", "8", "text", "XXXX XXXX", "str_s", "c", "field_i", "60"));
+    commit();
 
-      SolrParams sParams = mapParams(CommonParams.QT, "/sql", "aggregationMode", "map_reduce",
-          "stmt", "select id, Field_i, Str_s from collection1 where Text_t='XXXX' order by Field_i desc");
+    SolrParams sParams = mapParams(CommonParams.QT, "/sql", "aggregationMode", "map_reduce",
+        "stmt", "select id, str_s from collection1 where text='XXXX' order by field_iff desc");
 
-      SolrStream solrStream = new SolrStream(jetty.url, sParams);
-      List<Tuple> tuples = getTuples(solrStream);
+    SolrStream solrStream = new SolrStream(jetty.url, sParams);
+    Tuple tuple = getTuple(new ExceptionStream(solrStream));
+    assert (tuple.EOF);
+    assert (tuple.EXCEPTION);
+    assert (tuple.getException().contains("Column 'field_iff' not found in any table"));
 
-      assert(tuples.size() == 8);
+    sParams = mapParams(CommonParams.QT, "/sql",
+        "stmt", "select id, field_iff, str_s from collection1 where text='XXXX' order by field_iff desc");
 
-      Tuple tuple;
-      
-      tuple = tuples.get(0);
-      assert(tuple.getLong("id") == 8);
-      assert(tuple.getLong("Field_i") == 60);
-      assert(tuple.get("Str_s").equals("c"));
+    solrStream = new SolrStream(jetty.url, sParams);
+    tuple = getTuple(new ExceptionStream(solrStream));
+    assert (tuple.EOF);
+    assert (tuple.EXCEPTION);
 
-      tuple = tuples.get(1);
-      assert(tuple.getLong("id") == 7);
-      assert(tuple.getLong("Field_i") == 50);
-      assert(tuple.get("Str_s").equals("c"));
+    assert (tuple.getException().contains("Column 'field_iff' not found in any table"));
 
-      tuple = tuples.get(2);
-      assert(tuple.getLong("id") == 6);
-      assert(tuple.getLong("Field_i") == 40);
-      assert(tuple.get("Str_s").equals("c"));
+    sParams = mapParams(CommonParams.QT, "/sql", "aggregationMode", "map_reduce",
+        "stmt",
+        "select str_s, count(*), sum(field_iff), min(field_i), max(field_i), cast(avg(1.0 * field_i) as float) from collection1 where text='XXXX' group by str_s having ((sum(field_iff) = 19) AND (min(field_i) = 8))");
 
-      tuple = tuples.get(3);
-      assert(tuple.getLong("id") == 5);
-      assert(tuple.getLong("Field_i") == 30);
-      assert(tuple.get("Str_s").equals("c"));
+    solrStream = new SolrStream(jetty.url, sParams);
+    tuple = getTuple(new ExceptionStream(solrStream));
+    assert (tuple.EOF);
+    assert (tuple.EXCEPTION);
+    assert (tuple.getException().contains("Column 'field_iff' not found in any table"));
 
-      tuple = tuples.get(4);
-      assert(tuple.getLong("id") == 3);
-      assert(tuple.getLong("Field_i") == 20);
-      assert(tuple.get("Str_s").equals("a"));
+    sParams = mapParams(CommonParams.QT, "/sql", "aggregationMode", "map_reduce",
+        "stmt",
+        "select str_s, count(*), blah(field_i), min(field_i), max(field_i), cast(avg(1.0 * field_i) as float) from collection1 where text='XXXX' group by str_s having ((sum(field_i) = 19) AND (min(field_i) = 8))");
 
-      tuple = tuples.get(5);
-      assert(tuple.getLong("id") == 4);
-      assert(tuple.getLong("Field_i") == 11);
-      assert(tuple.get("Str_s").equals("b"));
+    solrStream = new SolrStream(jetty.url, sParams);
+    tuple = getTuple(new ExceptionStream(solrStream));
+    assert (tuple.EOF);
+    assert (tuple.EXCEPTION);
+    assert (tuple.getException().contains("No match found for function signature blah"));
 
-      tuple = tuples.get(6);
-      assert(tuple.getLong("id") == 2);
-      assert(tuple.getLong("Field_i") == 8);
-      assert(tuple.get("Str_s").equals("b"));
+  }
 
-      tuple = tuples.get(7);
-      assert(tuple.getLong("id") == 1);
-      assert(tuple.getLong("Field_i") == 7);
-      assert(tuple.get("Str_s").equals("a"));
+  private void testBasicGrouping() throws Exception {
 
-      // TODO get sum(Field_i) as named one
-      sParams = mapParams(CommonParams.QT, "/sql",
-          "stmt", "select Str_s, sum(Field_i) from collection1 where id='(1 8)' group by Str_s having (sum(Field_i) = 7 OR sum(Field_i) = 60) order by sum(Field_i) desc");
+    CloudJettyRunner jetty = this.cloudJettys.get(0);
 
-      solrStream = new SolrStream(jetty.url, sParams);
-      tuples = getTuples(solrStream);
+    del("*:*");
 
-      assert(tuples.size() == 2);
+    commit();
 
-      tuple = tuples.get(0);
-      assert(tuple.get("Str_s").equals("c"));
-      assert(tuple.getDouble("EXPR$1") == 60);
+    indexr("id", "1", "text", "XXXX XXXX", "str_s", "a", "field_i", "7");
+    indexr("id", "2", "text", "XXXX XXXX", "str_s", "b", "field_i", "8");
+    indexr("id", "3", "text", "XXXX XXXX", "str_s", "a", "field_i", "20");
+    indexr("id", "4", "text", "XXXX XXXX", "str_s", "b", "field_i", "11");
+    indexr("id", "5", "text", "XXXX XXXX", "str_s", "c", "field_i", "30");
+    indexr("id", "6", "text", "XXXX XXXX", "str_s", "c", "field_i", "40");
+    indexr("id", "7", "text", "XXXX XXXX", "str_s", "c", "field_i", "50");
+    indexr("id", "8", "text", "XXXX XXXX", "str_s", "c", "field_i", "60");
+    indexr("id", "9", "text", "XXXX XXXY", "str_s", "d", "field_i", "70");
+    commit();
 
-      tuple = tuples.get(1);
-      assert(tuple.get("Str_s").equals("a"));
-      assert(tuple.getDouble("EXPR$1") == 7);
+    SolrParams sParams = mapParams(CommonParams.QT, "/sql", "aggregationMode", "map_reduce",
+        "stmt",
+        "select str_s, count(*), sum(field_i), min(field_i), max(field_i), avg(field_i) from collection1 where text='XXXX' group by str_s order by sum(field_i) asc limit 2");
 
-      sParams = mapParams(CommonParams.QT, "/sql", "aggregationMode", "map_reduce",
-        "stmt", "select Str_s, sum(Field_i) from collection1 where id='(1 8)' group by Str_s having (sum(Field_i) = 7 OR sum(Field_i) = 60) order by sum(Field_i) desc");
+    SolrStream solrStream = new SolrStream(jetty.url, sParams);
+    List<Tuple> tuples = getTuples(solrStream);
 
-      solrStream = new SolrStream(jetty.url, sParams);
-      tuples = getTuples(solrStream);
+    // Only two results because of the limit.
+    assert (tuples.size() == 2);
+    Tuple tuple;
 
-      assert(tuples.size() == 2);
+    tuple = tuples.get(0);
+    assert (tuple.get("str_s").equals("b"));
+    assert (tuple.getDouble("EXPR$1") == 2); // count(*)
+    assert (tuple.getDouble("EXPR$2") == 19); // sum(field_i)
+    assert (tuple.getDouble("EXPR$3") == 8); // min(field_i)
+    assert (tuple.getDouble("EXPR$4") == 11); // max(field_i)
+    assert (tuple.getDouble("EXPR$5") == 10); // avg(field_i)
+
+    tuple = tuples.get(1);
+    assert (tuple.get("str_s").equals("a"));
+    assert (tuple.getDouble("EXPR$1") == 2); // count(*)
+    assert (tuple.getDouble("EXPR$2") == 27); // sum(field_i)
+    assert (tuple.getDouble("EXPR$3") == 7); // min(field_i)
+    assert (tuple.getDouble("EXPR$4") == 20); // max(field_i)
+    assert (tuple.getDouble("EXPR$5") == 14); // avg(field_i)
+
+    sParams = mapParams(CommonParams.QT, "/sql", "aggregationMode", "map_reduce",
+        "stmt",
+        "select str_s, count(*), sum(field_i), min(field_i), max(field_i), cast(avg(1.0 * field_i) as float) as blah from collection1 where text='XXXX' group by str_s order by sum(field_i) asc limit 2");
 
-      tuple = tuples.get(0);
-      assert(tuple.get("Str_s").equals("c"));
-      assert(tuple.getDouble("EXPR$1") == 60);
+    solrStream = new SolrStream(jetty.url, sParams);
+    tuples = getTuples(solrStream);
 
-      tuple = tuples.get(1);
-      assert(tuple.get("Str_s").equals("a"));
-      assert(tuple.getDouble("EXPR$1") == 7);
-    } finally {
-      delete();
-    }
-  }
+    // Only two results because of the limit.
+    assert (tuples.size() == 2);
 
-  private void testSQLException() throws Exception {
-    try {
+    tuple = tuples.get(0);
+    assert (tuple.get("str_s").equals("b"));
+    assert (tuple.getDouble("EXPR$1") == 2); // count(*)
+    assert (tuple.getDouble("EXPR$2") == 19); // sum(field_i)
+    assert (tuple.getDouble("EXPR$3") == 8); // min(field_i)
+    assert (tuple.getDouble("EXPR$4") == 11); // max(field_i)
+    assert (tuple.getDouble("blah") == 9.5); // avg(field_i)
+
+    tuple = tuples.get(1);
+    assert (tuple.get("str_s").equals("a"));
+    assert (tuple.getDouble("EXPR$1") == 2); // count(*)
+    assert (tuple.getDouble("EXPR$2") == 27); // sum(field_i)
+    assert (tuple.getDouble("EXPR$3") == 7); // min(field_i)
+    assert (tuple.getDouble("EXPR$4") == 20); // max(field_i)
+    assert (tuple.getDouble("blah") == 13.5); // avg(field_i)
+
+    sParams = mapParams(CommonParams.QT, "/sql", "aggregationMode", "map_reduce",
+        "stmt",
+        "select str_s as myString, count(*), sum(field_i) as mySum, min(field_i), max(field_i), avg(field_i)  from collection1 where text='XXXX' group by str_s order by mySum asc limit 2");
 
-      CloudJettyRunner jetty = this.cloudJettys.get(0);
+    solrStream = new SolrStream(jetty.url, sParams);
+    tuples = getTuples(solrStream);
 
-      del("*:*");
+    // Only two results because of the limit.
+    assert (tuples.size() == 2);
 
-      commit();
+    tuple = tuples.get(0);
+    assert (tuple.get("myString").equals("b"));
+    assert (tuple.getDouble("EXPR$1") == 2); // count(*)
+    assert (tuple.getDouble("mySum") == 19);
+    assert (tuple.getDouble("EXPR$3") == 8); // min(field_i)
+    assert (tuple.getDouble("EXPR$4") == 11); // max(field_i)
+    assert (tuple.getDouble("EXPR$5") == 10); // avg(field_i)
+
+    tuple = tuples.get(1);
+    assert (tuple.get("myString").equals("a"));
+    assert (tuple.getDouble("EXPR$1") == 2); // count(*)
+    assert (tuple.getDouble("mySum") == 27);
+    assert (tuple.getDouble("EXPR$3") == 7); // min(field_i)
+    assert (tuple.getDouble("EXPR$4") == 20); // max(field_i)
+    assert (tuple.getDouble("EXPR$5") == 14); // avg(field_i)
+
+    sParams = mapParams(CommonParams.QT, "/sql", "aggregationMode", "map_reduce",
+        "stmt", "select str_s, count(*), sum(field_i), min(field_i), max(field_i), "
+            + "avg(field_i) from collection1 where (text='XXXX' AND NOT ((text='XXXY') AND (text='XXXY' OR text='XXXY'))) "
+            + "group by str_s order by str_s desc");
 
-      indexDoc(sdoc("id", "1", "text", "XXXX XXXX", "str_s", "a", "field_i", "7"));
-      indexDoc(sdoc("id", "2", "text", "XXXX XXXX", "str_s", "b", "field_i", "8"));
-      indexDoc(sdoc("id", "3", "text", "XXXX XXXX", "str_s", "a", "field_i", "20"));
-      indexDoc(sdoc("id", "4", "text", "XXXX XXXX", "str_s", "b", "field_i", "11"));
-      indexDoc(sdoc("id", "5", "text", "XXXX XXXX", "str_s", "c", "field_i", "30"));
-      indexDoc(sdoc("id", "6", "text", "XXXX XXXX", "str_s", "c", "field_i", "40"));
-      indexDoc(sdoc("id", "7", "text", "XXXX XXXX", "str_s", "c", "field_i", "50"));
-      indexDoc(sdoc("id", "8", "text", "XXXX XXXX", "str_s", "c", "field_i", "60"));
-      commit();
+    solrStream = new SolrStream(jetty.url, sParams);
+    tuples = getTuples(solrStream);
 
-      SolrParams sParams = mapParams(CommonParams.QT, "/sql", "aggregationMode", "map_reduce",
-          "stmt", "select id, str_s from collection1 where text='XXXX' order by field_iff desc");
+    // The sort by and order by match and no limit is applied. All the Tuples should be returned in
+    // this scenario.
 
-      SolrStream solrStream = new SolrStream(jetty.url, sParams);
-      Tuple tuple = getTuple(new ExceptionStream(solrStream));
-      assert(tuple.EOF);
-      assert(tuple.EXCEPTION);
-      assert(tuple.getException().contains("Column 'field_iff' not found in any table"));
+    assert (tuples.size() == 3);
 
-      sParams = mapParams(CommonParams.QT, "/sql",
-        "stmt", "select id, field_iff, str_s from collection1 where text='XXXX' order by field_iff desc");
+    tuple = tuples.get(0);
+    assert (tuple.get("str_s").equals("c"));
+    assert (tuple.getDouble("EXPR$1") == 4); // count(*)
+    assert (tuple.getDouble("EXPR$2") == 180); // sum(field_i)
+    assert (tuple.getDouble("EXPR$3") == 30); // min(field_i)
+    assert (tuple.getDouble("EXPR$4") == 60); // max(field_i)
+    assert (tuple.getDouble("EXPR$5") == 45); // avg(field_i)
+
+    tuple = tuples.get(1);
+    assert (tuple.get("str_s").equals("b"));
+    assert (tuple.getDouble("EXPR$1") == 2); // count(*)
+    assert (tuple.getDouble("EXPR$2") == 19); // sum(field_i)
+    assert (tuple.getDouble("EXPR$3") == 8); // min(field_i)
+    assert (tuple.getDouble("EXPR$4") == 11); // max(field_i)
+    assert (tuple.getDouble("EXPR$5") == 10D); // avg(field_i)
+
+    tuple = tuples.get(2);
+    assert (tuple.get("str_s").equals("a"));
+    assert (tuple.getDouble("EXPR$1") == 2); // count(*)
+    assert (tuple.getDouble("EXPR$2") == 27); // sum(field_i)
+    assert (tuple.getDouble("EXPR$3") == 7); // min(field_i)
+    assert (tuple.getDouble("EXPR$4") == 20); // max(field_i)
+
+    assert (tuple.getDouble("EXPR$5") == 14); // avg(field_i)
+
+    sParams = mapParams(CommonParams.QT, "/sql", "aggregationMode", "map_reduce",
+        "stmt", "select str_s as myString, count(*) as myCount, sum(field_i) as mySum, min(field_i) as myMin, "
+            + "max(field_i) as myMax, avg(field_i) as myAvg from collection1 "
+            + "where (text='XXXX' AND NOT (text='XXXY')) group by str_s order by str_s desc");
 
-      solrStream = new SolrStream(jetty.url, sParams);
-      tuple = getTuple(new ExceptionStream(solrStream));
-      assert(tuple.EOF);
-      assert(tuple.EXCEPTION);
+    solrStream = new SolrStream(jetty.url, sParams);
+    tuples = getTuples(solrStream);
 
-      assert(tuple.getException().contains("Column 'field_iff' not found in any table"));
+    // The sort by and order by match and no limit is applied. All the Tuples should be returned in
+    // this scenario.
 
-      sParams = mapParams(CommonParams.QT, "/sql", "aggregationMode", "map_reduce",
-          "stmt", "select str_s, count(*), sum(field_iff), min(field_i), max(field_i), cast(avg(1.0 * field_i) as float) from collection1 where text='XXXX' group by str_s having ((sum(field_iff) = 19) AND (min(field_i) = 8))");
+    assert (tuples.size() == 3);
 
-      solrStream = new SolrStream(jetty.url, sParams);
-      tuple = getTuple(new ExceptionStream(solrStream));
-      assert(tuple.EOF);
-      assert(tuple.EXCEPTION);
-      assert(tuple.getException().contains("Column 'field_iff' not found in any table"));
+    tuple = tuples.get(0);
+    assert (tuple.get("myString").equals("c"));
+    assert (tuple.getDouble("myCount") == 4);
+    assert (tuple.getDouble("mySum") == 180);
+    assert (tuple.getDouble("myMin") == 30);
+    assert (tuple.getDouble("myMax") == 60);
+    assert (tuple.getDouble("myAvg") == 45);
+
+    tuple = tuples.get(1);
+    assert (tuple.get("myString").equals("b"));
+    assert (tuple.getDouble("myCount") == 2);
+    assert (tuple.getDouble("mySum") == 19);
+    assert (tuple.getDouble("myMin") == 8);
+    assert (tuple.getDouble("myMax") == 11);
+    assert (tuple.getDouble("myAvg") == 10);
+
+    tuple = tuples.get(2);
+    assert (tuple.get("myString").equals("a"));
+    assert (tuple.getDouble("myCount") == 2);
+    assert (tuple.getDouble("mySum") == 27);
+    assert (tuple.getDouble("myMin") == 7);
+    assert (tuple.getDouble("myMax") == 20);
+    assert (tuple.getDouble("myAvg") == 14);
+
+    sParams = mapParams(CommonParams.QT, "/sql", "aggregationMode", "map_reduce",
+        "stmt", "select str_s, count(*), sum(field_i), min(field_i), max(field_i), avg(field_i) " +
+            "from collection1 where text='XXXX' group by str_s having sum(field_i) = 19");
 
-      sParams = mapParams(CommonParams.QT, "/sql", "aggregationMode", "map_reduce",
-          "stmt", "select str_s, count(*), blah(field_i), min(field_i), max(field_i), cast(avg(1.0 * field_i) as float) from collection1 where text='XXXX' group by str_s having ((sum(field_i) = 19) AND (min(field_i) = 8))");
+    solrStream = new SolrStream(jetty.url, sParams);
+    tuples = getTuples(solrStream);
 
-      solrStream = new SolrStream(jetty.url, sParams);
-      tuple = getTuple(new ExceptionStream(solrStream));
-      assert(tuple.EOF);
-      assert(tuple.EXCEPTION);
-      assert(tuple.getException().contains("No match found for function signature blah"));
-    } finally {
-      delete();
-    }
-  }
+    assert (tuples.size() == 1);
 
-  private void testBasicGrouping() throws Exception {
-    try {
-
-      CloudJettyRunner jetty = this.cloudJettys.get(0);
-
-      del("*:*");
-
-      commit();
-
-      indexr("id", "1", "text", "XXXX XXXX", "str_s", "a", "field_i", "7");
-      indexr("id", "2", "text", "XXXX XXXX", "str_s", "b", "field_i", "8");
-      indexr("id", "3", "text", "XXXX XXXX", "str_s", "a", "field_i", "20");
-      indexr("id", "4", "text", "XXXX XXXX", "str_s", "b", "field_i", "11");
-      indexr("id", "5", "text", "XXXX XXXX", "str_s", "c", "field_i", "30");
-      indexr("id", "6", "text", "XXXX XXXX", "str_s", "c", "field_i", "40");
-      indexr("id", "7", "text", "XXXX XXXX", "str_s", "c", "field_i", "50");
-      indexr("id", "8", "text", "XXXX XXXX", "str_s", "c", "field_i", "60");
-      indexr("id", "9", "text", "XXXX XXXY", "str_s", "d", "field_i", "70");
-      commit();
-
-      SolrParams sParams = mapParams(CommonParams.QT, "/sql", "aggregationMode", "map_reduce",
-        "stmt", "select str_s, count(*), sum(field_i), min(field_i), max(field_i), avg(field_i) from collection1 where text='XXXX' group by str_s order by sum(field_i) asc limit 2");
-
-      SolrStream solrStream = new SolrStream(jetty.url, sParams);
-      List<Tuple> tuples = getTuples(solrStream);
-
-      //Only two results because of the limit.
-      assert(tuples.size() == 2);
-      Tuple tuple;
-
-      tuple = tuples.get(0);
-      assert(tuple.get("str_s").equals("b"));
-      assert(tuple.getDouble("EXPR$1") == 2); //count(*)
-      assert(tuple.getDouble("EXPR$2") == 19); //sum(field_i)
-      assert(tuple.getDouble("EXPR$3") == 8); //min(field_i)
-      assert(tuple.getDouble("EXPR$4") == 11); //max(field_i)
-      assert(tuple.getDouble("EXPR$5") == 10); //avg(field_i)
-
-      tuple = tuples.get(1);
-      assert(tuple.get("str_s").equals("a"));
-      assert(tuple.getDouble("EXPR$1") == 2); //count(*)
-      assert(tuple.getDouble("EXPR$2") == 27); //sum(field_i)
-      assert(tuple.getDouble("EXPR$3") == 7); //min(field_i)
-      assert(tuple.getDouble("EXPR$4") == 20); //max(field_i)
-      assert(tuple.getDouble("EXPR$5") == 14); //avg(field_i)
-
-
-      sParams = mapParams(CommonParams.QT, "/sql", "aggregationMode", "map_reduce",
-          "stmt", "select str_s, count(*), sum(field_i), min(field_i), max(field_i), cast(avg(1.0 * field_i) as float) as blah from collection1 where text='XXXX' group by str_s order by sum(field_i) asc limit 2");
-
-      solrStream = new SolrStream(jetty.url, sParams);
-      tuples = getTuples(solrStream);
-
-      //Only two results because of the limit.
-      assert(tuples.size() == 2);
-
-      tuple = tuples.get(0);
-      assert(tuple.get("str_s").equals("b"));
-      assert(tuple.getDouble("EXPR$1") == 2); //count(*)
-      assert(tuple.getDouble("EXPR$2") == 19); //sum(field_i)
-      assert(tuple.getDouble("EXPR$3") == 8); //min(field_i)
-      assert(tuple.getDouble("EXPR$4") == 11); //max(field_i)
-      assert(tuple.getDouble("blah") == 9.5); //avg(field_i)
-
-      tuple = tuples.get(1);
-      assert(tuple.get("str_s").equals("a"));
-      assert(tuple.getDouble("EXPR$1") == 2); //count(*)
-      assert(tuple.getDouble("EXPR$2") == 27); //sum(field_i)
-      assert(tuple.getDouble("EXPR$3") == 7); //min(field_i)
-      assert(tuple.getDouble("EXPR$4") == 20); //max(field_i)
-      assert(tuple.getDouble("blah") == 13.5); //avg(field_i)
-
-      sParams = mapParams(CommonParams.QT, "/sql", "aggregationMode", "map_reduce",
-          "stmt", "select str_s as myString, count(*), sum(field_i) as mySum, min(field_i), max(field_i), avg(field_i)  from collection1 where text='XXXX' group by str_s order by mySum asc limit 2");
-
-      solrStream = new SolrStream(jetty.url, sParams);
-      tuples = getTuples(solrStream);
-
-      //Only two results because of the limit.
-      assert(tuples.size() == 2);
-
-      tuple = tuples.get(0);
-      assert(tuple.get("myString").equals("b"));
-      assert(tuple.getDouble("EXPR$1") == 2); //count(*)
-      assert(tuple.getDouble("mySum") == 19);
-      assert(tuple.getDouble("EXPR$3") == 8); //min(field_i)
-      assert(tuple.getDouble("EXPR$4") == 11); //max(field_i)
-      assert(tuple.getDouble("EXPR$5") == 10); //avg(field_i)
-
-      tuple = tuples.get(1);
-      assert(tuple.get("myString").equals("a"));
-      assert(tuple.getDouble("EXPR$1") == 2); //count(*)
-      assert(tuple.getDouble("mySum") == 27);
-      assert(tuple.getDouble("EXPR$3") == 7); //min(field_i)
-      assert(tuple.getDouble("EXPR$4") == 20); //max(field_i)
-      assert(tuple.getDouble("EXPR$5") == 14); //avg(field_i)
-
-      sParams = mapParams(CommonParams.QT, "/sql", "aggregationMode", "map_reduce",
-        "stmt", "select str_s, count(*), sum(field_i), min(field_i), max(field_i), "
-          + "avg(field_i) from collection1 where (text='XXXX' AND NOT ((text='XXXY') AND (text='XXXY' OR text='XXXY'))) "
-          + "group by str_s order by str_s desc");
-
-      solrStream = new SolrStream(jetty.url, sParams);
-      tuples = getTuples(solrStream);
-
-      //The sort by and order by match and no limit is applied. All the Tuples should be returned in
-      //this scenario.
-
-      assert(tuples.size() == 3);
-
-      tuple = tuples.get(0);
-      assert(tuple.get("str_s").equals("c"));
-      assert(tuple.getDouble("EXPR$1") == 4); //count(*)
-      assert(tuple.getDouble("EXPR$2") == 180); //sum(field_i)
-      assert(tuple.getDouble("EXPR$3") == 30); //min(field_i)
-      assert(tuple.getDouble("EXPR$4") == 60); //max(field_i)
-      assert(tuple.getDouble("EXPR$5") == 45); //avg(field_i)
-
-      tuple = tuples.get(1);
-      assert(tuple.get("str_s").equals("b"));
-      assert(tuple.getDouble("EXPR$1") == 2); //count(*)
-      assert(tuple.getDouble("EXPR$2") == 19); //sum(field_i)
-      assert(tuple.getDouble("EXPR$3") == 8); //min(field_i)
-      assert(tuple.getDouble("EXPR$4") == 11); //max(field_i)
-      assert(tuple.getDouble("EXPR$5") == 10D); //avg(field_i)
-
-      tuple = tuples.get(2);
-      assert(tuple.get("str_s").equals("a"));
-      assert(tuple.getDouble("EXPR$1") == 2); //count(*)
-      assert(tuple.getDouble("EXPR$2") == 27); //sum(field_i)
-      assert(tuple.getDouble("EXPR$3") == 7); //min(field_i)
-      assert(tuple.getDouble("EXPR$4") == 20); //max(field_i)
-
-      assert(tuple.getDouble("EXPR$5") == 14); //avg(field_i)
-
-      sParams = mapParams(CommonParams.QT, "/sql", "aggregationMode", "map_reduce",
-          "stmt", "select str_s as myString, count(*) as myCount, sum(field_i) as mySum, min(field_i) as myMin, "
-          + "max(field_i) as myMax, avg(field_i) as myAvg from collection1 "
-          + "where (text='XXXX' AND NOT (text='XXXY')) group by str_s order by str_s desc");
-
-      solrStream = new SolrStream(jetty.url, sParams);
-      tuples = getTuples(solrStream);
-
-      //The sort by and order by match and no limit is applied. All the Tuples should be returned in
-      //this scenario.
-
-      assert(tuples.size() == 3);
-
-      tuple = tuples.get(0);
-      assert(tuple.get("myString").equals("c"));
-      assert(tuple.getDouble("myCount") == 4);
-      assert(tuple.getDouble("mySum") == 180);
-      assert(tuple.getDouble("myMin") == 30);
-      assert(tuple.getDouble("myMax") == 60);
-      assert(tuple.getDouble("myAvg") == 45);
-
-      tuple = tuples.get(1);
-      assert(tuple.get("myString").equals("b"));
-      assert(tuple.getDouble("myCount") == 2);
-      assert(tuple.getDouble("mySum") == 19);
-      assert(tuple.getDouble("myMin") == 8);
-      assert(tuple.getDouble("myMax") == 11);
-      assert(tuple.getDouble("myAvg") == 10);
-
-      tuple = tuples.get(2);
-      assert(tuple.get("myString").equals("a"));
-      assert(tuple.getDouble("myCount") == 2);
-      assert(tuple.getDouble("mySum") == 27);
-      assert(tuple.getDouble("myMin") == 7);
-      assert(tuple.getDouble("myMax") == 20);
-      assert(tuple.getDouble("myAvg") == 14);
-
-      sParams = mapParams(CommonParams.QT, "/sql", "aggregationMode", "map_reduce",
-          "stmt", "select str_s, count(*), sum(field_i), min(field_i), max(field_i), avg(field_i) " +
-          "from collection1 where text='XXXX' group by str_s having sum(field_i) = 19");
-
-      solrStream = new SolrStream(jetty.url, sParams);
-      tuples = getTuples(solrStream);
-
-      assert(tuples.size() == 1);
-
-      tuple = tuples.get(0);
-      assert(tuple.get("str_s").equals("b"));
-      assert(tuple.getDouble("EXPR$1") == 2); //count(*)
-      assert(tuple.getDouble("EXPR$2") == 19); //sum(field_i)
-      assert(tuple.getDouble("EXPR$3") == 8); //min(field_i)
-      assert(tuple.getDouble("EXPR$4") == 11); //max(field_i)
-      assert(tuple.getDouble("EXPR$5") == 10); //avg(field_i)
-
-      sParams = mapParams(CommonParams.QT, "/sql", "aggregationMode", "map_reduce",
-          "stmt", "select str_s, count(*), sum(field_i), min(field_i), max(field_i), avg(field_i) " +
-          "from collection1 where text='XXXX' group by str_s having ((sum(field_i) = 19) AND (min(field_i) = 8))");
-
-      solrStream = new SolrStream(jetty.url, sParams);
-      tuples = getTuples(solrStream);
-
-      assert(tuples.size() == 1);
-
-      tuple = tuples.get(0);
-      assert(tuple.get("str_s").equals("b"));
-      assert(tuple.getDouble("EXPR$1") == 2); //count(*)
-      assert(tuple.getDouble("EXPR$2") == 19); //sum(field_i)
-      assert(tuple.getDouble("EXPR$3") == 8); //min(field_i)
-      assert(tuple.getDouble("EXPR$4") == 11); //max(field_i)
-      assert(tuple.getDouble("EXPR$5") == 10); //avg(field_i)
-
-      sParams = mapParams(CommonParams.QT, "/sql", "aggregationMode", "map_reduce",
-          "stmt", "select str_s, count(*), sum(field_i) as mySum, min(field_i), max(field_i), " +
-          "avg(field_i) from collection1 where text='XXXX' group by str_s " +
-          "having ((sum(field_i) = 19) AND (min(field_i) = 8))");
-
-      solrStream = new SolrStream(jetty.url, sParams);
-      tuples = getTuples(solrStream);
-
-      assert(tuples.size() == 1);
-
-      tuple = tuples.get(0);
-      assert(tuple.get("str_s").equals("b"));
-      assert(tuple.getDouble("EXPR$1") == 2); //count(*)
-      assert(tuple.getDouble("mySum") == 19);
-      assert(tuple.getDouble("EXPR$3") == 8); //min(field_i)
-      assert(tuple.getDouble("EXPR$4") == 11); //max(field_i)
-      assert(tuple.getDouble("EXPR$5") == 10); //avg(field_i)
-
-      sParams = mapParams(CommonParams.QT, "/sql", "aggregationMode", "map_reduce",
-          "stmt", "select str_s, count(*), sum(field_i), min(field_i), max(field_i), " +
-          "avg(field_i) from collection1 where text='XXXX' group by str_s " +
-          "having ((sum(field_i) = 19) AND (min(field_i) = 100))");
-
-      solrStream = new SolrStream(jetty.url, sParams);
-      tuples = getTuples(solrStream);
-
-      assert(tuples.size() == 0);
-    } finally {
-      delete();
-    }
-  }
+    tuple = tuples.get(0);
+    assert (tuple.get("str_s").equals("b"));
+    assert (tuple.getDouble("EXPR$1") == 2); // count(*)
+    assert (tuple.getDouble("EXPR$2") == 19); // sum(field_i)
+    assert (tuple.getDouble("EXPR$3") == 8); // min(field_i)
+    assert (tuple.getDouble("EXPR$4") == 11); // max(field_i)
+    assert (tuple.getDouble("EXPR$5") == 10); // avg(field_i)
 
+    sParams = mapParams(CommonParams.QT, "/sql", "aggregationMode", "map_reduce",
+        "stmt", "select str_s, count(*), sum(field_i), min(field_i), max(field_i), avg(field_i) " +
+            "from collection1 where text='XXXX' group by str_s having ((sum(field_i) = 19) AND (min(field_i) = 8))");
 
-  private void testBasicGroupingTint() throws Exception {
-    try {
+    solrStream = new SolrStream(jetty.url, sParams);
+    tuples = getTuples(solrStream);
+
+    assert (tuples.size() == 1);
+
+    tuple = tuples.get(0);
+    assert (tuple.get("str_s").equals("b"));
+    assert (tuple.getDouble("EXPR$1") == 2); // count(*)
+    assert (tuple.getDouble("EXPR$2") == 19); // sum(field_i)
+    assert (tuple.getDouble("EXPR$3") == 8); // min(field_i)
+    assert (tuple.getDouble("EXPR$4") == 11); // max(field_i)
+    assert (tuple.getDouble("EXPR$5") == 10); // avg(field_i)
+
+    sParams = mapParams(CommonParams.QT, "/sql", "aggregationMode", "map_reduce",
+        "stmt", "select str_s, count(*), sum(field_i) as mySum, min(field_i), max(field_i), " +
+            "avg(field_i) from collection1 where text='XXXX' group by str_s " +
+            "having ((sum(field_i) = 19) AND (min(field_i) = 8))");
+
+    solrStream = new SolrStream(jetty.url, sParams);
+    tuples = getTuples(solrStream);
+
+    assert (tuples.size() == 1);
 
-      CloudJettyRunner jetty = this.cloudJettys.get(0);
+    tuple = tuples.get(0);
+    assert (tuple.get("str_s").equals("b"));
+    assert (tuple.getDouble("EXPR$1") == 2); // count(*)
+    assert (tuple.getDouble("mySum") == 19);
+    assert (tuple.getDouble("EXPR$3") == 8); // min(field_i)
+    assert (tuple.getDouble("EXPR$4") == 11); // max(field_i)
+    assert (tuple.getDouble("EXPR$5") == 10); // avg(field_i)
+
+    sParams = mapParams(CommonParams.QT, "/sql", "aggregationMode", "map_reduce",
+        "stmt", "select str_s, count(*), sum(field_i), min(field_i), max(field_i), " +
+            "avg(field_i) from collection1 where text='XXXX' group by str_s " +
+            "having ((sum(field_i) = 19) AND (min(field_i) = 100))");
 
-      del("*:*");
+    solrStream = new SolrStream(jetty.url, sParams);
+    tuples = getTuples(solrStream);
 
-      commit();
+    assert (tuples.size() == 0);
 
-      indexr("id", "1", "text", "XXXX XXXX", "str_s", "a", "field_ti", "7");
-      indexr("id", "2", "text", "XXXX XXXX", "str_s", "b", "field_ti", "8");
-      indexr("id", "3", "text", "XXXX XXXX", "str_s", "a", "field_ti", "20");
-      indexr("id", "4", "text", "XXXX XXXX", "str_s", "b", "field_ti", "11");
-      indexr("id", "5", "text", "XXXX XXXX", "str_s", "c", "field_ti", "30");
-      indexr("id", "6", "text", "XXXX XXXX", "str_s", "c", "field_ti", "40");
-      indexr("id", "7", "text", "XXXX XXXX", "str_s", "c", "field_ti", "50");
-      indexr("id", "8", "text", "XXXX XXXX", "str_s", "c", "field_ti", "60");
-      indexr("id", "9", "text", "XXXX XXXY", "str_s", "d", "field_ti", "70");
-      commit();
+  }
 
-      SolrParams sParams = mapParams(CommonParams.QT, "/sql",
-          "stmt", "select str_s, count(*), sum(field_ti), min(field_ti), max(field_ti), avg(field_ti) from collection1 where text='XXXX' group by str_s order by sum(field_ti) asc limit 2");
+  private void testBasicGroupingTint() throws Exception {
 
-      SolrStream solrStream = new SolrStream(jetty.url, sParams);
-      List<Tuple> tuples = getTuples(solrStream);
+    CloudJettyRunner jetty = this.cloudJettys.get(0);
 
-      //Only two results because of the limit.
-      assert(tuples.size() == 2);
-      Tuple tuple;
+    del("*:*");
 
-      tuple = tuples.get(0);
-      assert(tuple.get("str_s").equals("b"));
-      assert(tuple.getDouble("EXPR$1") == 2); //count(*)
-      assert(tuple.getDouble("EXPR$2") == 19); //sum(field_i)
-      assert(tuple.getDouble("EXPR$3") == 8); //min(field_i)
-      assert(tuple.getDouble("EXPR$4") == 11); //max(field_i)
-      assert(tuple.getDouble("EXPR$5") == 10); //avg(field_i)
+    commit();
 
-      tuple = tuples.get(1);
-      assert(tuple.get("str_s").equals("a"));
-      assert(tuple.getDouble("EXPR$1") == 2); //count(*)
-      assert(tuple.getDouble("EXPR$2") == 27); //sum(field_i)
-      assert(tuple.getDouble("EXPR$3") == 7); //min(field_i)
-      assert(tuple.getDouble("EXPR$4") == 20); //max(field_i)
-      assert(tuple.getDouble("EXPR$5") == 14); //avg(field_i)
+    indexr("id", "1", "text", "XXXX XXXX", "str_s", "a", "field_ti", "7");
+    indexr("id", "2", "text", "XXXX XXXX", "str_s", "b", "field_ti", "8");
+    indexr("id", "3", "text", "XXXX XXXX", "str_s", "a", "field_ti", "20");
+    indexr("id", "4", "text", "XXXX XXXX", "str_s", "b", "field_ti", "11");
+    indexr("id", "5", "text", "XXXX XXXX", "str_s", "c", "field_ti", "30");
+    indexr("id", "6", "text", "XXXX XXXX", "str_s", "c", "field_ti", "40");
+    indexr("id", "7", "text", "XXXX XXXX", "str_s", "c", "field_ti", "50");
+    indexr("id", "8", "text", "XXXX XXXX", "str_s", "c", "field_ti", "60");
+    indexr("id", "9", "text", "XXXX XXXY", "str_s", "d", "field_ti", "70");
+    commit();
 
+    SolrParams sParams = mapParams(CommonParams.QT, "/sql",
+        "stmt",
+        "select str_s, count(*), sum(field_ti), min(field_ti), max(field_ti), avg(field_ti) from collection1 where text='XXXX' group by str_s order by sum(field_ti) asc limit 2");
 
+    SolrStream solrStream = new SolrStream(jetty.url, sParams);
+    List<Tuple> tuples = getTuples(solrStream);
+
+    // Only two results because of the limit.
+    assert (tuples.size() == 2);
+    Tuple tuple;
+
+    tuple = tuples.get(0);
+    assert (tuple.get("str_s").equals("b"));
+    assert (tuple.getDouble("EXPR$1") == 2); // count(*)
+    assert (tuple.getDouble("EXPR$2") == 19); // sum(field_i)
+    assert (tuple.getDouble("EXPR$3") == 8); // min(field_i)
+    assert (tuple.getDouble("EXPR$4") == 11); // max(field_i)
+    assert (tuple.getDouble("EXPR$5") == 10); // avg(field_i)
+
+    tuple = tuples.get(1);
+    assert (tuple.get("str_s").equals("a"));
+    assert (tuple.getDouble("EXPR$1") == 2); // count(*)
+    assert (tuple.getDouble("EXPR$2") == 27); // sum(field_i)
+    assert (tuple.getDouble("EXPR$3") == 7); // min(field_i)
+    assert (tuple.getDouble("EXPR$4") == 20); // max(field_i)
+    assert (tuple.getDouble("EXPR$5") == 14); // avg(field_i)
 
-    } finally {
-      delete();
-    }
   }
 
   private void testBasicGroupingIntLongPoints() throws Exception {
-    try {
-
-      Random random = random();
-      int r = random.nextInt(2);
-      String[] intOrLong = {"field_i_p", "field_l_p"};
-      String[] facetOrMap = {"facet", "map_reduce"};
-      String field = intOrLong[r];
-      r = random.nextInt(2);
-      String mode = facetOrMap[r];
-      CloudJettyRunner jetty = this.cloudJettys.get(0);
-
-      del("*:*");
-
-      commit();
-
-      indexr("id", "1", "text", "XXXX XXXX", "str_s", "a", field, "7");
-      indexr("id", "2", "text", "XXXX XXXX", "str_s", "b", field, "8");
-      indexr("id", "3", "text", "XXXX XXXX", "str_s", "a", field, "20");
-      indexr("id", "4", "text", "XXXX XXXX", "str_s", "b", field, "11");
-      indexr("id", "5", "text", "XXXX XXXX", "str_s", "c", field, "30");
-      indexr("id", "6", "text", "XXXX XXXX", "str_s", "c", field, "40");
-      indexr("id", "7", "text", "XXXX XXXX", "str_s", "c", field, "50");
-      indexr("id", "8", "text", "XXXX XXXX", "str_s", "c", field, "60");
-      indexr("id", "9", "text", "XXXX XXXY", "str_s", "d", field, "70");
-      commit();
-
-      SolrParams sParams = mapParams(CommonParams.QT, "/sql", "aggregationMode", mode,
-          "stmt", "select str_s, count(*), sum("+field+"), min("+field+"), max("+field+"), avg("+field+") from collection1 where text='XXXX' group by str_s order by sum("+field+") asc limit 2");
-
-      SolrStream solrStream = new SolrStream(jetty.url, sParams);
-      List<Tuple> tuples = getTuples(solrStream);
-
-      //Only two results because of the limit.
-      assert(tuples.size() == 2);
-      Tuple tuple;
-
-      tuple = tuples.get(0);
-      assert(tuple.get("str_s").equals("b"));
-      assert(tuple.getDouble("EXPR$1") == 2); //count(*)
-      assert(tuple.getDouble("EXPR$2") == 19); //sum(field_i)
-      assert(tuple.getDouble("EXPR$3") == 8); //min(field_i)
-      assert(tuple.getDouble("EXPR$4") == 11); //max(field_i)
-      assert(tuple.getDouble("EXPR$5") == 10); //avg(field_i)
-
-      tuple = tuples.get(1);
-      assert(tuple.get("str_s").equals("a"));
-      assert(tuple.getDouble("EXPR$1") == 2); //count(*)
-      assert(tuple.getDouble("EXPR$2") == 27); //sum(field_i)
-      assert(tuple.getDouble("EXPR$3") == 7); //min(field_i)
-      assert(tuple.getDouble("EXPR$4") == 20); //max(field_i)
-      assert(tuple.getDouble("EXPR$5") == 14); //avg(field_i)
-
-
-
-    } finally {
-      delete();
-    }
-  }
 
-  private void testBasicGroupingFloatDoublePoints() throws Exception {
-    try {
-
-      Random random = random();
-      int r = random.nextInt(2);
-      String[] intOrLong = {"field_f_p", "field_d_p"};
-      String[] facetOrMap = {"facet", "map_reduce"};
-      String field = intOrLong[r];
-      r = random.nextInt(2);
-      String mode = facetOrMap[r];
-
-      CloudJettyRunner jetty = this.cloudJettys.get(0);
-
-      del("*:*");
-
-      commit();
-
-      indexr("id", "1", "text", "XXXX XXXX", "str_s", "a", field, "7.0");
-      indexr("id", "2", "text", "XXXX XXXX", "str_s", "b", field, "8.0");
-      indexr("id", "3", "text", "XXXX XXXX", "str_s", "a", field, "20.0");
-      indexr("id", "4", "text", "XXXX XXXX", "str_s", "b", field, "11.0");
-      indexr("id", "5", "text", "XXXX XXXX", "str_s", "c", field, "30.0");
-      indexr("id", "6", "text", "XXXX XXXX", "str_s", "c", field, "40.0");
-      indexr("id", "7", "text", "XXXX XXXX", "str_s", "c", field, "50.0");
-      indexr("id", "8", "text", "XXXX XXXX", "str_s", "c", field, "60.0");
-      indexr("id", "9", "text", "XXXX XXXY", "str_s", "d", field, "70.0");
-      commit();
-
-      SolrParams sParams = mapParams(CommonParams.QT, "/sql", "aggregationMode", mode,
-          "stmt", "select str_s, count(*), sum("+field+"), min("+field+"), max("+field+"), avg("+field+") from collection1 where text='XXXX' group by str_s order by sum("+field+") asc limit 2");
-
-      SolrStream solrStream = new SolrStream(jetty.url, sParams);
-      List<Tuple> tuples = getTuples(solrStream);
-
-      //Only two results because of the limit.
-      assert(tuples.size() == 2);
-      Tuple tuple;
-
-      tuple = tuples.get(0);
-      assert(tuple.get("str_s").equals("b"));
-      assertEquals(tuple.getDouble("EXPR$1"), 2, 0.0); //count(*)
-      assertEquals(tuple.getDouble("EXPR$2"), 19, 0.0); //sum(field_i)
-      assertEquals(tuple.getDouble("EXPR$3"), 8, 0.0); //min(field_i)
-      assertEquals(tuple.getDouble("EXPR$4"), 11, 0.0); //max(field_i)
-      assertEquals(tuple.getDouble("EXPR$5"), 9.5, 0.0); //avg(field_i)
-
-      tuple = tuples.get(1);
-      assert(tuple.get("str_s").equals("a"));
-      assertEquals(tuple.getDouble("EXPR$1"), 2, 0.0); //count(*)
-      assertEquals(tuple.getDouble("EXPR$2"), 27, 0.0); //sum(field_i)
-      assertEquals(tuple.getDouble("EXPR$3"), 7, 0.0); //min(field_i)
-      assertEquals(tuple.getDouble("EXPR$4"), 20, 0.0); //max(field_i)
-      assertEquals(tuple.getDouble("EXPR$5"), 13.5, 0.0); //avg(field_i)
-
-
-
-    } finally {
-      delete();
-    }
-  }
+    Random random = random();
+    int r = random.nextInt(2);
+    String[] intOrLong = {"field_i_p", "field_l_p"};
+    String[] facetOrMap = {"facet", "map_reduce"};
+    String field = intOrLong[r];
+    r = random.nextInt(2);
+    String mode = facetOrMap[r];
+    CloudJettyRunner jetty = this.cloudJettys.get(0);
 
-  private void testSelectDistinctFacets() throws Exception {
-    try {
-      CloudJettyRunner jetty = this.cloudJettys.get(0);
+    del("*:*");
 
-      del("*:*");
+    commit();
 
-      commit();
+    indexr("id", "1", "text", "XXXX XXXX", "str_s", "a", field, "7");
+    indexr("id", "2", "text", "XXXX XXXX", "str_s", "b", field, "8");
+    indexr("id", "3", "text", "XXXX XXXX", "str_s", "a", field, "20");
+    indexr("id", "4", "text", "XXXX XXXX", "str_s", "b", field, "11");
+    indexr("id", "5", "text", "XXXX XXXX", "str_s", "c", field, "30");
+    indexr("id", "6", "text", "XXXX XXXX", "str_s", "c", field, "40");
+    indexr("id", "7", "text", "XXXX XXXX", "str_s", "c", field, "50");
+    indexr("id", "8", "text", "XXXX XXXX", "str_s", "c", field, "60");
+    indexr("id", "9", "text", "XXXX XXXY", "str_s", "d", field, "70");
+    commit();
 
-      indexr("id", "1", "text", "XXXX XXXX", "str_s", "a", "field_i", "1");
-      indexr("id", "2", "text", "XXXX XXXX", "str_s", "b", "field_i", "2");
-      indexr("id", "3", "text", "XXXX XXXX", "str_s", "a", "field_i", "20");
-      indexr("id", "4", "text", "XXXX XXXX", "str_s", "b", "field_i", "2");
-      indexr("id", "5", "text", "XXXX XXXX", "str_s", "c", "field_i", "30");
-      indexr("id", "6", "text", "XXXX XXXX", "str_s", "c", "field_i", "30");
-      indexr("id", "7", "text", "XXXX XXXX", "str_s", "c", "field_i", "50");
-      indexr("id", "8", "text", "XXXX XXXX", "str_s", "c", "field_i", "60");
-      commit();
+    SolrParams sParams = mapParams(CommonParams.QT, "/sql", "aggregationMode", mode,
+        "stmt", "select str_s, count(*), sum(" + field + "), min(" + field + "), max(" + field + "), avg(" + field
+            + ") from collection1 where text='XXXX' group by str_s order by sum(" + field + ") asc limit 2");
 
-      SolrParams sParams = mapParams(CommonParams.QT, "/sql", "aggregationMode", "facet",
-          "stmt", "select distinct str_s, field_i from collection1 order by str_s asc, field_i asc");
+    SolrStream solrStream = new SolrStream(jetty.url, sParams);
+    List<Tuple> tuples = getTuples(solrStream);
 
-      System.out.println("######## selectDistinctFacets #######");
+    // Only two results because of the limit.
+    assert (tuples.size() == 2);
+    Tuple tuple;
 
-      SolrStream solrStream = new SolrStream(jetty.url, sParams);
-      List<Tuple> tuples = getTuples(solrStream);
+    tuple = tuples.get(0);
+    assert (tuple.get("str_s").equals("b"));
+    assert (tuple.getDouble("EXPR$1") == 2); // count(*)
+    assert (tuple.getDouble("EXPR$2") == 19); // sum(field_i)
+    assert (tuple.getDouble("EXPR$3") == 8); // min(field_i)
+    assert (tuple.getDouble("EXPR$4") == 11); // max(field_i)
+    assert (tuple.getDouble("EXPR$5") == 10); // avg(field_i)
+
+    tuple = tuples.get(1);
+    assert (tuple.get("str_s").equals("a"));
+    assert (tuple.getDouble("EXPR$1") == 2); // count(*)
+    assert (tuple.getDouble("EXPR$2") == 27); // sum(field_i)
+    assert (tuple.getDouble("EXPR$3") == 7); // min(field_i)
+    assert (tuple.getDouble("EXPR$4") == 20); // max(field_i)
+    assert (tuple.getDouble("EXPR$5") == 14); // avg(field_i)
 
-      //assert(false);
-      assert(tuples.size() == 6);
+  }
 
-      Tuple tuple;
+  private void testBasicGroupingFloatDoublePoints() throws Exception {
 
-      tuple = tuples.get(0);
-      assert(tuple.get("str_s").equals("a"));
-      assert(tuple.getLong("field_i") == 1);
+    Random random = random();
+    int r = random.nextInt(2);
+    String[] intOrLong = {"field_f_p", "field_d_p"};
+    String[] facetOrMap = {"facet", "map_reduce"};
+    String field = intOrLong[r];
+    r = random.nextInt(2);
+    String mode = facetOrMap[r];
 
-      tuple = tuples.get(1);
-      assert(tuple.get("str_s").equals("a"));
-      assert(tuple.getLong("field_i") == 20);
+    CloudJettyRunner jetty = this.cloudJettys.get(0);
 
-      tuple = tuples.get(2);
-      assert(tuple.get("str_s").equals("b"));
-      assert(tuple.getLong("field_i") == 2);
+    del("*:*");
 
-      tuple = tuples.get(3);
-      assert(tuple.get("str_s").equals("c"));
-      assert(tuple.getLong("field_i") == 30);
+    commit();
 
-      tuple = tuples.get(4);
-      assert(tuple.get("str_s").equals("c"));
-      assert(tuple.getLong("field_i") == 50);
+    indexr("id", "1", "text", "XXXX XXXX", "str_s", "a", field, "7.0");
+    indexr("id", "2", "text", "XXXX XXXX", "str_s", "b", field, "8.0");
+    indexr("id", "3", "text", "XXXX XXXX", "str_s", "a", field, "20.0");
+    indexr("id", "4", "text", "XXXX XXXX", "str_s", "b", field, "11.0");
+    indexr("id", "5", "text", "XXXX XXXX", "str_s", "c", field, "30.0");
+    indexr("id", "6", "text", "XXXX XXXX", "str_s", "c", field, "40.0");
+    indexr("id", "7", "text", "XXXX XXXX", "str_s", "c", field, "50.0");
+    indexr("id", "8", "text", "XXXX XXXX", "str_s", "c", field, "60.0");
+    indexr("id", "9", "text", "XXXX XXXY", "str_s", "d", field, "70.0");
+    commit();
 
-      tuple = tuples.get(5);
-      assert(tuple.get("str_s").equals("c"));
-      assert(tuple.getLong("field_i") == 60);
+    SolrParams sParams = mapParams(CommonParams.QT, "/sql", "aggregationMode", mode,
+        "stmt", "select str_s, count(*), sum(" + field + "), min(" + field + "), max(" + field + "), avg(" + field
+            + ") from collection1 where text='XXXX' group by str_s order by sum(" + field + ") asc limit 2");
 
+    SolrStream solrStream = new SolrStream(jetty.url, sParams);
+    List<Tuple> tuples = getTuples(solrStream);
 
-      //reverse the sort
-      sParams = mapParams(CommonParams.QT, "/sql", "aggregationMode", "facet",
-        "stmt", "select distinct str_s, field_i from collection1 order by str_s desc, field_i desc");
+    // Only two results because of the limit.
+    assert (tuples.size() == 2);
+    Tuple tuple;
 
-      solrStream = new SolrStream(jetty.url, sParams);
-      tuples = getTuples(solrStream);
+    tuple = tuples.get(0);
+    assert (tuple.get("str_s").equals("b"));
+    assertEquals(tuple.getDouble("EXPR$1"), 2, 0.0); // count(*)
+    assertEquals(tuple.getDouble("EXPR$2"), 19, 0.0); // sum(field_i)
+    assertEquals(tuple.getDouble("EXPR$3"), 8, 0.0); // min(field_i)
+    assertEquals(tuple.getDouble("EXPR$4"), 11, 0.0); // max(field_i)
+    assertEquals(tuple.getDouble("EXPR$5"), 9.5, 0.0); // avg(field_i)
+
+    tuple = tuples.get(1);
+    assert (tuple.get("str_s").equals("a"));
+    assertEquals(tuple.getDouble("EXPR$1"), 2, 0.0); // count(*)
+    assertEquals(tuple.getDouble("EXPR$2"), 27, 0.0); // sum(field_i)
+    assertEquals(tuple.getDouble("EXPR$3"), 7, 0.0); // min(field_i)
+    assertEquals(tuple.getDouble("EXPR$4"), 20, 0.0); // max(field_i)
+    assertEquals(tuple.getDouble("EXPR$5"), 13.5, 0.0); // avg(field_i)
 
-      assert(tuples.size() == 6);
+  }
 
-      tuple = tuples.get(0);
-      assert(tuple.get("str_s").equals("c"));
-      assert(tuple.getLong("field_i") == 60);
+  private void testSelectDistinctFacets() throws Exception {
 
-      tuple = tuples.get(1);
-      assert(tuple.get("str_s").equals("c"));
-      assert(tuple.getLong("field_i") == 50);
+    CloudJettyRunner jetty = this.cloudJettys.get(0);
 
+    del("*:*");
 
-      tuple = tuples.get(2);
-      assert(tuple.get("str_s").equals("c"));
-      assert(tuple.getLong("field_i") == 30);
+    commit();
 
-      tuple = tuples.get(3);
-      assert(tuple.get("str_s").equals("b"));
-      assert(tuple.getLong("field_i") == 2);
+    indexr("id", "1", "text", "XXXX XXXX", "str_s", "a", "field_i", "1");
+    indexr("id", "2", "text", "XXXX XXXX", "str_s", "b", "field_i", "2");
+    indexr("id", "3", "text", "XXXX XXXX", "str_s", "a", "field_i", "20");
+    indexr("id", "4", "text", "XXXX XXXX", "str_s", "b", "field_i", "2");
+    indexr("id", "5", "text", "XXXX XXXX", "str_s", "c", "field_i", "30");
+    indexr("id", "6", "text", "XXXX XXXX", "str_s", "c", "field_i", "30");
+    indexr("id", "7", "text", "XXXX XXXX", "str_s", "c", "field_i", "50");
+    indexr("id", "8", "text", "XXXX XXXX", "str_s", "c", "field_i", "60");
+    commit();
 
+    SolrParams sParams = mapParams(CommonParams.QT, "/sql", "aggregationMode", "facet",
+        "stmt", "select distinct str_s, field_i from collection1 order by str_s asc, field_i asc");
 
-      tuple = tuples.get(4);
-      assert(tuple.get("str_s").equals("a"));
-      assert(tuple.getLong("field_i") == 20);
+    System.out.println("######## selectDistinctFacets #######");
 
-      tuple = tuples.get(5);
-      assert(tuple.get("str_s").equals("a"));
-      assert(tuple.getLong("field_i") == 1);
+    SolrStream solrStream = new SolrStream(jetty.url, sParams);
+    List<Tuple> tuples = getTuples(solrStream);
 
+    // assert(false);
+    assert (tuples.size() == 6);
 
-      //reverse the sort
-      sParams = mapParams(CommonParams.QT, "/sql", "aggregationMode", "facet",
-        "stmt", "select distinct str_s as myString, field_i as myInt from collection1 order by str_s desc, myInt desc");
+    Tuple tuple;
 
-      solrStream = new SolrStream(jetty.url, sParams);
-      tuples = getTuples(solrStream);
+    tuple = tuples.get(0);
+    assert (tuple.get("str_s").equals("a"));
+    assert (tuple.getLong("field_i") == 1);
 
-      assert(tuples.size() == 6);
+    tuple = tuples.get(1);
+    assert (tuple.get("str_s").equals("a"));
+    assert (tuple.getLong("field_i") == 20);
 
-      tuple = tuples.get(0);
-      assert(tuple.get("myString").equals("c"));
-      assert(tuple.getLong("myInt") == 60);
+    tuple = tuples.get(2);
+    assert (tuple.get("str_s").equals("b"));
+    assert (tuple.getLong("field_i") == 2);
 
-      tuple = tuples.get(1);
-      assert(tuple.get("myString").equals("c"));
-      assert(tuple.getLong("myInt") == 50);
+    tuple = tuples.get(3);
+    assert (tuple.get("str_s").equals("c"));
+    assert (tuple.getLong("field_i") == 30);
 
+    tuple = tuples.get(4);
+    assert (tuple.get("str_s").equals("c"));
+    assert (tuple.getLong("field_i") == 50);
 
-      tuple = tu

<TRUNCATED>

[8/8] lucene-solr:jira/http2: Merge with master

Posted by da...@apache.org.
Merge with master


Project: http://git-wip-us.apache.org/repos/asf/lucene-solr/repo
Commit: http://git-wip-us.apache.org/repos/asf/lucene-solr/commit/ca9df458
Tree: http://git-wip-us.apache.org/repos/asf/lucene-solr/tree/ca9df458
Diff: http://git-wip-us.apache.org/repos/asf/lucene-solr/diff/ca9df458

Branch: refs/heads/jira/http2
Commit: ca9df4587062744399e8c616df1d8ec84b66d09d
Parents: d34e045 280f679
Author: Cao Manh Dat <da...@apache.org>
Authored: Wed Dec 12 10:01:31 2018 +0000
Committer: Cao Manh Dat <da...@apache.org>
Committed: Wed Dec 12 10:01:31 2018 +0000

----------------------------------------------------------------------
 .../lucene/index/DocValuesFieldUpdates.java     |    2 +-
 solr/CHANGES.txt                                |   18 +
 solr/NOTICE.txt                                 |    6 +
 .../solr/ltr/feature/TestExternalFeatures.java  |    4 +-
 .../solr/cloud/OverseerTaskProcessor.java       |    2 +-
 .../similarities/BM25SimilarityFactory.java     |   11 +-
 .../LegacyBM25SimilarityFactory.java            |   64 +
 .../similarities/SchemaSimilarityFactory.java   |   12 +-
 .../apache/solr/security/BasicAuthPlugin.java   |   45 +-
 .../security/Sha256AuthenticationProvider.java  |    7 +-
 .../apache/solr/servlet/SolrDispatchFilter.java |   40 +-
 .../solr/collection1/conf/schema-bm25.xml       |   17 +
 .../apache/solr/cloud/MoveReplicaHDFSTest.java  |    3 +
 .../HdfsCollectionsAPIDistributedZkTest.java    |    3 +
 .../HdfsAutoAddReplicasIntegrationTest.java     |    3 +
 .../org/apache/solr/handler/TestSQLHandler.java | 4040 +++++++++---------
 .../solr/rest/schema/TestBulkSchemaAPI.java     |    6 +-
 .../search/TestPayloadScoreQParserPlugin.java   |    2 +-
 .../solr/search/function/TestFunctionQuery.java |   10 +-
 .../similarities/TestBM25SimilarityFactory.java |    8 +-
 .../TestLegacyBM25SimilarityFactory.java        |   45 +
 .../TestNonDefinedSimilarityFactory.java        |   28 +-
 .../similarities/TestPerFieldSimilarity.java    |    8 +-
 ...uthentication-and-authorization-plugins.adoc |   10 +
 .../src/basic-authentication-plugin.adoc        |   45 +-
 .../src/major-changes-in-solr-8.adoc            |    8 +-
 .../src/other-schema-elements.adoc              |    4 +-
 solr/webapp/web/WEB-INF/web.xml                 |    2 +-
 solr/webapp/web/css/angular/login.css           |  103 +
 solr/webapp/web/css/angular/menu.css            |    2 +
 solr/webapp/web/index.html                      |  160 +-
 solr/webapp/web/js/angular/app.js               |   45 +-
 solr/webapp/web/js/angular/controllers/login.js |  146 +
 solr/webapp/web/js/angular/services.js          |   24 +-
 solr/webapp/web/libs/angular-utf8-base64.js     |  217 +
 solr/webapp/web/libs/angular-utf8-base64.min.js |   45 +
 solr/webapp/web/partials/login.html             |   80 +
 37 files changed, 3049 insertions(+), 2226 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/ca9df458/solr/CHANGES.txt
----------------------------------------------------------------------
diff --cc solr/CHANGES.txt
index 48f9c71,bfa3666..4cdc7e0
--- a/solr/CHANGES.txt
+++ b/solr/CHANGES.txt
@@@ -136,9 -152,11 +152,11 @@@ Upgrade Note
  New Features
  ----------------------
  
 -* SOLR-12839: JSON 'terms' Faceting now supports a 'prelim_sort' option to use when initially selecting 
 +* SOLR-12839: JSON 'terms' Faceting now supports a 'prelim_sort' option to use when initially selecting
    the top ranking buckets, prior to the final 'sort' option used after refinement.  (hossman)
  
+ * SOLR-7896: Add a login page to Admin UI, with initial support for Basic Auth (janhoy)
+ 
  Bug Fixes
  ----------------------
  

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/ca9df458/solr/core/src/java/org/apache/solr/servlet/SolrDispatchFilter.java
----------------------------------------------------------------------

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/ca9df458/solr/solr-ref-guide/src/major-changes-in-solr-8.adoc
----------------------------------------------------------------------
diff --cc solr/solr-ref-guide/src/major-changes-in-solr-8.adoc
index ac29f45,41aa400..551bf1c
--- a/solr/solr-ref-guide/src/major-changes-in-solr-8.adoc
+++ b/solr/solr-ref-guide/src/major-changes-in-solr-8.adoc
@@@ -22,41 -22,10 +22,47 @@@
  // *****   Until then it's a place for upgrade notes     *****
  // *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
  
- Solr 8 is a major new release of Solr which introduces new features and a number of other changes that may impact your existing installation.
+ * Due to LIR redesign in SOLR-11702, all users must be on Solr 7.3 or higher to upgrade to Solr 8
+ 
+ * If you explicitly use BM25SimilarityFactory in your schema the absolute scoring will be lower, see SOLR-13025.
+   But ordering of documents will not change in the normal case. Use LegacyBM25SimilarityFactory if you need to force
+   the old 6.x/7.x scoring. Note that if you have not specified any similarity in schema or use the default 
+   SchemaSimilarityFactory, then LegacyBM25Similarity is automatically selected for 'luceneMatchVersion' < 8.0.0.
+   See also explanation in Reference Guide chapter "Other Schema Elements".
 +
 +== Solr 8 Upgrade Planning
 +
 +Due to the introduction of LIR redesign since Solr 7.3 (SOLR-11702) and the removing of old LIR implementation in Solr 8.
 +Rolling updates are not possible unless all nodes must be on Solr 7.3 or higher. If not updates can be lost.
 +
 +Solr nodes can now listen and serve HTTP/2 and HTTP/1 requests. By default, most of internal requests are sent by using HTTP/2
 +therefore Solr 8.0 nodes can't talk to old nodes (7.x).
 +
 +However we can follow these steps to do rolling updates:
 +
 +* Do rolling updates as normally, but the Solr 8.0 nodes must start with `-Dsolr.http1=true` as startup parameter.
 +  By using this parameter internal requests are sent by using HTTP/1.1
 +* When all nodes are upgraded to 8.0, restart them, this time `-Dsolr.http1` parameter should be removed.
 +
 +== HTTP/2
 +
 +Until Solr 8, Solr nodes only support HTTP/1 requests. HTTP/1.1 practically allows only one outstanding request
 +per TCP connection this means that for sending multiple requests at the same time multiple TCP connections must be
 +established. This leads to waste of resources on both-sides and long GC-pause. Solr 8 with HTTP/2 support overcomes that problem by allowing
 +multiple requests can be sent in parallel using a same TCP connection.
 +
 +{solr-javadocs}/solr-solrj/org/apache/solr/client/solrj/impl/Http2SolrClient.html[`Http2SolrClient`]
 +with HTTP/2 and async capabilities based on Jetty Client is introduced. This client replaced
 +`HttpSolrClient`] and `ConcurrentUpdateSolrClient` for sending most of internal requests (sent by
 +`UpdateShardHandler`, `HttpShardHandler`).
 +An interesting benchmark result showing gain of using `Http2SolrClient` had been posted
 +https://issues.apache.org/jira/browse/SOLR-12642?focusedCommentId=16606648&page=com.atlassian.jira.plugin.system.issuetabpanels%3Acomment-tabpanel#comment-16606648[here].
 +However this leads to several changes in configuration and authentication setup
 +
 +* {solr-javadocs}/solr-core/org/apache/solr/update/UpdateShardHandler.html[`UpdateShardHandler.maxConnections`] parameter is no longer being used
 +* {solr-javadocs}/solr-core/org/apache/solr/handler/component/HttpShardHandler.html[`HttpShardHandlerFactory.maxConnections`] parameter is no longer being used
 +*  Custom {solr-javadocs}/solr-core/org/apache/solr/security/AuthenticationPlugin.html[`AuthenticationPlugin`] must provide its own setup for
 +   `Http2SolrClient` through implementing
 +   {solr-javadocs}/solr-core/org/apache/solr/security/HttpClientBuilderPlugin.html[`HttpClientBuilderPlugin.setup`],
 +   if not internal requests can't be authenticated
 +


[6/8] lucene-solr:jira/http2: SOLR-13025: SchemaSimilarityFactory fallback to LegacyBM25Similarity for luceneMatchVersion < 8.0

Posted by da...@apache.org.
SOLR-13025: SchemaSimilarityFactory fallback to LegacyBM25Similarity for luceneMatchVersion < 8.0


Project: http://git-wip-us.apache.org/repos/asf/lucene-solr/repo
Commit: http://git-wip-us.apache.org/repos/asf/lucene-solr/commit/5affe742
Tree: http://git-wip-us.apache.org/repos/asf/lucene-solr/tree/5affe742
Diff: http://git-wip-us.apache.org/repos/asf/lucene-solr/diff/5affe742

Branch: refs/heads/jira/http2
Commit: 5affe7421f3f927e1fcfb7c7cb763179ce686997
Parents: bcdc6da
Author: Jan Høydahl <ja...@apache.org>
Authored: Wed Dec 12 09:40:57 2018 +0100
Committer: Jan Høydahl <ja...@apache.org>
Committed: Wed Dec 12 10:33:50 2018 +0100

----------------------------------------------------------------------
 solr/CHANGES.txt                                | 11 ++++
 .../solr/ltr/feature/TestExternalFeatures.java  |  4 +-
 .../similarities/BM25SimilarityFactory.java     | 11 ++--
 .../LegacyBM25SimilarityFactory.java            | 64 ++++++++++++++++++++
 .../similarities/SchemaSimilarityFactory.java   | 12 ++--
 .../solr/collection1/conf/schema-bm25.xml       | 17 ++++++
 .../solr/rest/schema/TestBulkSchemaAPI.java     |  6 +-
 .../search/TestPayloadScoreQParserPlugin.java   |  2 +-
 .../solr/search/function/TestFunctionQuery.java | 10 +--
 .../similarities/TestBM25SimilarityFactory.java |  8 +--
 .../TestLegacyBM25SimilarityFactory.java        | 45 ++++++++++++++
 .../TestNonDefinedSimilarityFactory.java        | 28 ++++++++-
 .../similarities/TestPerFieldSimilarity.java    |  8 +--
 .../src/major-changes-in-solr-8.adoc            |  6 ++
 .../src/other-schema-elements.adoc              |  4 +-
 15 files changed, 205 insertions(+), 31 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/5affe742/solr/CHANGES.txt
----------------------------------------------------------------------
diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt
index c7ad02a..9ab80f4 100644
--- a/solr/CHANGES.txt
+++ b/solr/CHANGES.txt
@@ -59,6 +59,13 @@ Upgrade Notes
 * SOLR-12754: The UnifiedHighlighter hl.weightMatches now defaults to true.  If there are unforseen highlight problems,
   this may be the culprit.
 
+* If you explicitly use BM25SimilarityFactory in your schema, the absolute scoring will be lower due to SOLR-13025.
+  But ordering of documents will not change in the normal case. Use LegacyBM25SimilarityFactory if you need to force
+  the old 6.x/7.x scoring. Note that if you have not specified any similarity in schema or use the default 
+  SchemaSimilarityFactory, then LegacyBM25Similarity is automatically selected for 'luceneMatchVersion' < 8.0.0.
+  See also explanation in Reference Guide chapter "Other Schema Elements".
+
+
 New Features
 ----------------------
 
@@ -94,6 +101,10 @@ Optimizations
 
 * SOLR-12725: ParseDateFieldUpdateProcessorFactory should reuse ParsePosition. (ab)
 
+* SOLR-13025: Due to LUCENE-8563, the BM25Similarity formula no longer includes the (k1+1) factor in the numerator
+  This gives a lower absolute score but doesn't affect ordering, as this is a constant factor which is the same 
+  for every document. Use LegacyBM25SimilarityFactory if you need the old 6.x/7.x scoring. See also upgrade notes (janhoy)
+
 Other Changes
 ----------------------
 

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/5affe742/solr/contrib/ltr/src/test/org/apache/solr/ltr/feature/TestExternalFeatures.java
----------------------------------------------------------------------
diff --git a/solr/contrib/ltr/src/test/org/apache/solr/ltr/feature/TestExternalFeatures.java b/solr/contrib/ltr/src/test/org/apache/solr/ltr/feature/TestExternalFeatures.java
index 45e856a..0c97f0f 100644
--- a/solr/contrib/ltr/src/test/org/apache/solr/ltr/feature/TestExternalFeatures.java
+++ b/solr/contrib/ltr/src/test/org/apache/solr/ltr/feature/TestExternalFeatures.java
@@ -70,7 +70,7 @@ public class TestExternalFeatures extends TestRerankBase {
     query.add("rq", "{!ltr reRankDocs=10 model=externalmodel efi.user_query=w3 efi.userTitlePhrase1=w4 efi.userTitlePhrase2=w5}");
 
     assertJQ("/query" + query.toQueryString(), "/response/docs/[0]/id=='3'");
-    assertJQ("/query" + query.toQueryString(), "/response/docs/[0]/score==0.7693934");
+    assertJQ("/query" + query.toQueryString(), "/response/docs/[0]/score==0.34972426");
     assertJQ("/query" + query.toQueryString(), "/response/docs/[1]/score==0.0");
     assertJQ("/query" + query.toQueryString(), "/response/docs/[2]/score==0.0");
 
@@ -80,7 +80,7 @@ public class TestExternalFeatures extends TestRerankBase {
     query.add("fl", "*,score,[fv efi.user_query=w2 efi.userTitlePhrase1=w4 efi.userTitlePhrase2=w5]");
 
     assertJQ("/query" + query.toQueryString(), "/response/docs/[0]/id=='3'");
-    assertJQ("/query" + query.toQueryString(), "/response/docs/[0]/score==0.7693934");
+    assertJQ("/query" + query.toQueryString(), "/response/docs/[0]/score==0.34972426");
     assertJQ("/query" + query.toQueryString(), "/response/docs/[1]/score==0.0");
     assertJQ("/query" + query.toQueryString(), "/response/docs/[2]/score==0.0");
   }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/5affe742/solr/core/src/java/org/apache/solr/search/similarities/BM25SimilarityFactory.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/search/similarities/BM25SimilarityFactory.java b/solr/core/src/java/org/apache/solr/search/similarities/BM25SimilarityFactory.java
index fd8a48c..fefe893 100644
--- a/solr/core/src/java/org/apache/solr/search/similarities/BM25SimilarityFactory.java
+++ b/solr/core/src/java/org/apache/solr/search/similarities/BM25SimilarityFactory.java
@@ -16,13 +16,15 @@
  */
 package org.apache.solr.search.similarities;
 
+import org.apache.lucene.search.similarities.BM25Similarity;
 import org.apache.lucene.search.similarities.Similarity;
-import org.apache.lucene.search.similarity.LegacyBM25Similarity;
 import org.apache.solr.common.params.SolrParams;
 import org.apache.solr.schema.SimilarityFactory;
 
 /**
- * Factory for {@link LegacyBM25Similarity}
+ * Factory for BM25Similarity. This is the default similarity since 8.x.
+ * If you need the exact same formula as in 6.x and 7.x you should instead look at
+ * {@link LegacyBM25SimilarityFactory}
  * <p>
  * Parameters:
  * <ul>
@@ -35,9 +37,10 @@ import org.apache.solr.schema.SimilarityFactory;
  * Optional settings:
  * <ul>
  *   <li>discountOverlaps (bool): Sets
- *       {@link LegacyBM25Similarity#setDiscountOverlaps(boolean)}</li>
+ *       {@link BM25Similarity#setDiscountOverlaps(boolean)}</li>
  * </ul>
  * @lucene.experimental
+ * @since 8.0.0
  */
 public class BM25SimilarityFactory extends SimilarityFactory {
   private boolean discountOverlaps;
@@ -54,7 +57,7 @@ public class BM25SimilarityFactory extends SimilarityFactory {
 
   @Override
   public Similarity getSimilarity() {
-    LegacyBM25Similarity sim = new LegacyBM25Similarity(k1, b);
+    BM25Similarity sim = new BM25Similarity(k1, b);
     sim.setDiscountOverlaps(discountOverlaps);
     return sim;
   }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/5affe742/solr/core/src/java/org/apache/solr/search/similarities/LegacyBM25SimilarityFactory.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/search/similarities/LegacyBM25SimilarityFactory.java b/solr/core/src/java/org/apache/solr/search/similarities/LegacyBM25SimilarityFactory.java
new file mode 100644
index 0000000..235a410
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/search/similarities/LegacyBM25SimilarityFactory.java
@@ -0,0 +1,64 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.solr.search.similarities;
+
+import org.apache.lucene.search.similarities.Similarity;
+import org.apache.lucene.search.similarity.LegacyBM25Similarity;
+import org.apache.solr.common.params.SolrParams;
+import org.apache.solr.schema.SimilarityFactory;
+
+/**
+ * Factory for {@link LegacyBM25Similarity}. 
+ * Use this to force explicit creation of the BM25 formula that was used by BM25Similarity before Solr/Lucene 8.0.0.
+ * Note that {@link SchemaSimilarityFactory} will automatically create an instance of LegacyBM25Similarity if luceneMatchVersion is &lt; 8.0.0
+ * <p>
+ * Parameters:
+ * <ul>
+ *   <li>k1 (float): Controls non-linear term frequency normalization (saturation).
+ *                   The default is <code>1.2</code>
+ *   <li>b (float): Controls to what degree document length normalizes tf values.
+ *                  The default is <code>0.75</code>
+ * </ul>
+ * <p>
+ * Optional settings:
+ * <ul>
+ *   <li>discountOverlaps (bool): Sets
+ *       {@link LegacyBM25Similarity#setDiscountOverlaps(boolean)}</li>
+ * </ul>
+ * @lucene.experimental
+ * @since 8.0.0
+ */
+public class LegacyBM25SimilarityFactory extends SimilarityFactory {
+  private boolean discountOverlaps;
+  private float k1;
+  private float b;
+
+  @Override
+  public void init(SolrParams params) {
+    super.init(params);
+    discountOverlaps = params.getBool("discountOverlaps", true);
+    k1 = params.getFloat("k1", 1.2f);
+    b = params.getFloat("b", 0.75f);
+  }
+
+  @Override
+  public Similarity getSimilarity() {
+    LegacyBM25Similarity sim = new LegacyBM25Similarity(k1, b);
+    sim.setDiscountOverlaps(discountOverlaps);
+    return sim;
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/5affe742/solr/core/src/java/org/apache/solr/search/similarities/SchemaSimilarityFactory.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/search/similarities/SchemaSimilarityFactory.java b/solr/core/src/java/org/apache/solr/search/similarities/SchemaSimilarityFactory.java
index 6c3dedf..e682b9ee 100644
--- a/solr/core/src/java/org/apache/solr/search/similarities/SchemaSimilarityFactory.java
+++ b/solr/core/src/java/org/apache/solr/search/similarities/SchemaSimilarityFactory.java
@@ -16,7 +16,7 @@
  */
 package org.apache.solr.search.similarities;
 
-import org.apache.lucene.search.similarities.ClassicSimilarity;
+import org.apache.lucene.search.similarities.BM25Similarity;
 import org.apache.lucene.search.similarities.PerFieldSimilarityWrapper;
 import org.apache.lucene.search.similarities.Similarity;
 import org.apache.lucene.search.similarity.LegacyBM25Similarity;
@@ -39,8 +39,8 @@ import org.apache.solr.util.plugin.SolrCoreAware;
  * matching configured:
  * </p>
  * <ul>
- *  <li><code>luceneMatchVersion &lt; 6.0</code> = {@link ClassicSimilarity}</li>
- *  <li><code>luceneMatchVersion &gt;= 6.0</code> = {@link LegacyBM25Similarity}</li>
+ *  <li><code>luceneMatchVersion &lt; 8.0</code> = {@link LegacyBM25Similarity}</li>
+ *  <li><code>luceneMatchVersion &gt;= 8.0</code> = {@link BM25Similarity}</li>
  * </ul>
  * <p>
  * The <code>defaultSimFromFieldType</code> option accepts the name of any fieldtype, and uses 
@@ -85,10 +85,12 @@ public class SchemaSimilarityFactory extends SimilarityFactory implements SolrCo
   
   private volatile SolrCore core; // set by inform(SolrCore)
   private volatile Similarity similarity; // lazy instantiated
+  private Version coreVersion = Version.LATEST;
 
   @Override
   public void inform(SolrCore core) {
     this.core = core;
+    this.coreVersion = this.core.getSolrConfig().luceneMatchVersion;
   }
   
   @Override
@@ -109,7 +111,9 @@ public class SchemaSimilarityFactory extends SimilarityFactory implements SolrCo
       Similarity defaultSim = null;
       if (null == defaultSimFromFieldType) {
         // nothing configured, choose a sensible implicit default...
-        defaultSim = new LegacyBM25Similarity();
+        defaultSim = coreVersion.onOrAfter(Version.LUCENE_8_0_0) ? 
+            new BM25Similarity() :
+            new LegacyBM25Similarity();
       } else {
         FieldType defSimFT = core.getLatestSchema().getFieldTypeByName(defaultSimFromFieldType);
         if (null == defSimFT) {

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/5affe742/solr/core/src/test-files/solr/collection1/conf/schema-bm25.xml
----------------------------------------------------------------------
diff --git a/solr/core/src/test-files/solr/collection1/conf/schema-bm25.xml b/solr/core/src/test-files/solr/collection1/conf/schema-bm25.xml
index 7c31f15..549efd6 100644
--- a/solr/core/src/test-files/solr/collection1/conf/schema-bm25.xml
+++ b/solr/core/src/test-files/solr/collection1/conf/schema-bm25.xml
@@ -36,10 +36,27 @@
     </similarity>
   </fieldType>
 
+  <!-- legacybm25 with default parameters -->
+  <fieldType name="legacy_text" class="solr.TextField">
+    <analyzer class="org.apache.lucene.analysis.standard.StandardAnalyzer"/>
+    <similarity class="solr.LegacyBM25SimilarityFactory"/>
+  </fieldType>
+
+  <!-- legacybm25 with parameters -->
+  <fieldType name="legacy_text_params" class="solr.TextField">
+    <analyzer class="org.apache.lucene.analysis.standard.StandardAnalyzer"/>
+    <similarity class="solr.LegacyBM25SimilarityFactory">
+      <float name="k1">1.2</float>
+      <float name="b">0.76</float>
+    </similarity>
+  </fieldType>
+  
 
   <field name="id" type="string" indexed="true" stored="true" multiValued="false" required="false"/>
   <field name="text" type="text" indexed="true" stored="false"/>
   <field name="text_params" type="text_params" indexed="true" stored="false"/>
+  <field name="legacy_text" type="legacy_text" indexed="true" stored="false"/>
+  <field name="legacy_text_params" type="legacy_text_params" indexed="true" stored="false"/>
 
   <uniqueKey>id</uniqueKey>
 

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/5affe742/solr/core/src/test/org/apache/solr/rest/schema/TestBulkSchemaAPI.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/rest/schema/TestBulkSchemaAPI.java b/solr/core/src/test/org/apache/solr/rest/schema/TestBulkSchemaAPI.java
index 9a72043..3ac735f 100644
--- a/solr/core/src/test/org/apache/solr/rest/schema/TestBulkSchemaAPI.java
+++ b/solr/core/src/test/org/apache/solr/rest/schema/TestBulkSchemaAPI.java
@@ -29,10 +29,10 @@ import java.util.function.Consumer;
 
 import org.apache.commons.io.FileUtils;
 import org.apache.lucene.misc.SweetSpotSimilarity;
+import org.apache.lucene.search.similarities.BM25Similarity;
 import org.apache.lucene.search.similarities.DFISimilarity;
 import org.apache.lucene.search.similarities.PerFieldSimilarityWrapper;
 import org.apache.lucene.search.similarities.Similarity;
-import org.apache.lucene.search.similarity.LegacyBM25Similarity;
 import org.apache.solr.common.SolrDocumentList;
 import org.apache.solr.core.CoreContainer;
 import org.apache.solr.core.SolrCore;
@@ -588,7 +588,7 @@ public class TestBulkSchemaAPI extends RestTestBase {
     assertNotNull("field a5 not created", m);
     assertEquals("myWhitespaceTxtField", m.get("type"));
     assertNull(m.get("uninvertible")); // inherited, but API shouldn't return w/o explicit showDefaults
-    assertFieldSimilarity("a5", LegacyBM25Similarity.class); // unspecified, expect default
+    assertFieldSimilarity("a5", BM25Similarity.class); // unspecified, expect default
 
     m = getObj(harness, "wdf_nocase", "fields");
     assertNull("field 'wdf_nocase' not deleted", m);
@@ -930,7 +930,7 @@ public class TestBulkSchemaAPI extends RestTestBase {
     Map fields = getObj(harness, fieldName, "fields");
     assertNotNull("field " + fieldName + " not created", fields);
     
-    assertFieldSimilarity(fieldName, LegacyBM25Similarity.class,
+    assertFieldSimilarity(fieldName, BM25Similarity.class,
        sim -> assertEquals("Unexpected k1", k1, sim.getK1(), .001),
        sim -> assertEquals("Unexpected b", b, sim.getB(), .001));
 

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/5affe742/solr/core/src/test/org/apache/solr/search/TestPayloadScoreQParserPlugin.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/search/TestPayloadScoreQParserPlugin.java b/solr/core/src/test/org/apache/solr/search/TestPayloadScoreQParserPlugin.java
index 9c9c50e..faccb4b 100644
--- a/solr/core/src/test/org/apache/solr/search/TestPayloadScoreQParserPlugin.java
+++ b/solr/core/src/test/org/apache/solr/search/TestPayloadScoreQParserPlugin.java
@@ -57,6 +57,6 @@ public class TestPayloadScoreQParserPlugin extends SolrTestCaseJ4 {
 
     // TODO: fix this includeSpanScore test to be less brittle - score result is score of "A" (via BM25) multipled by 1.0 (payload value)
     assertQ(req("fl","*,score", "q", "{!payload_score f=vals_dpf v=A func=min}"), "//float[@name='score']='1.0'");
-    assertQ(req("fl","*,score", "q", "{!payload_score f=vals_dpf v=A func=min includeSpanScore=true}"), "//float[@name='score']='0.2876821'");
+    assertQ(req("fl","*,score", "q", "{!payload_score f=vals_dpf v=A func=min includeSpanScore=true}"), "//float[@name='score']='0.13076457'");
   }
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/5affe742/solr/core/src/test/org/apache/solr/search/function/TestFunctionQuery.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/search/function/TestFunctionQuery.java b/solr/core/src/test/org/apache/solr/search/function/TestFunctionQuery.java
index cc448b3..edce3bb 100644
--- a/solr/core/src/test/org/apache/solr/search/function/TestFunctionQuery.java
+++ b/solr/core/src/test/org/apache/solr/search/function/TestFunctionQuery.java
@@ -389,16 +389,16 @@ public class TestFunctionQuery extends SolrTestCaseJ4 {
     // superman has a higher df (thus lower idf) in one segment, but reversed in the complete index
     String q ="{!func}query($qq)";
     String fq="id:120"; 
-    assertQ(req("fl","*,score","q", q, "qq","text:batman", "fq",fq), "//float[@name='score']<'1.0'");
-    assertQ(req("fl","*,score","q", q, "qq","text:superman", "fq",fq), "//float[@name='score']>'1.0'");
+    assertQ(req("fl","*,score","q", q, "qq","text:batman", "fq",fq), "//float[@name='score']<'0.6'");
+    assertQ(req("fl","*,score","q", q, "qq","text:superman", "fq",fq), "//float[@name='score']>'0.6'");
 
     // test weighting through a function range query
-    assertQ(req("fl","*,score", "fq",fq,  "q", "{!frange l=1 u=10}query($qq)", "qq","text:superman"), "//*[@numFound='1']");
+    assertQ(req("fl","*,score", "fq",fq,  "q", "{!frange l=0.6 u=10}query($qq)", "qq","text:superman"), "//*[@numFound='1']");
 
     // test weighting through a complex function
     q ="{!func}sub(div(sum(0.0,product(1,query($qq))),1),0)";
-    assertQ(req("fl","*,score","q", q, "qq","text:batman", "fq",fq), "//float[@name='score']<'1.0'");
-    assertQ(req("fl","*,score","q", q, "qq","text:superman", "fq",fq), "//float[@name='score']>'1.0'");
+    assertQ(req("fl","*,score","q", q, "qq","text:batman", "fq",fq), "//float[@name='score']<'0.6'");
+    assertQ(req("fl","*,score","q", q, "qq","text:superman", "fq",fq), "//float[@name='score']>'0.6'");
 
 
     // test full param dereferencing

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/5affe742/solr/core/src/test/org/apache/solr/search/similarities/TestBM25SimilarityFactory.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/search/similarities/TestBM25SimilarityFactory.java b/solr/core/src/test/org/apache/solr/search/similarities/TestBM25SimilarityFactory.java
index 6445b34..3f6deac 100644
--- a/solr/core/src/test/org/apache/solr/search/similarities/TestBM25SimilarityFactory.java
+++ b/solr/core/src/test/org/apache/solr/search/similarities/TestBM25SimilarityFactory.java
@@ -16,8 +16,8 @@
  */
 package org.apache.solr.search.similarities;
 
+import org.apache.lucene.search.similarities.BM25Similarity;
 import org.apache.lucene.search.similarities.Similarity;
-import org.apache.lucene.search.similarity.LegacyBM25Similarity;
 import org.junit.BeforeClass;
 
 /**
@@ -31,14 +31,14 @@ public class TestBM25SimilarityFactory extends BaseSimilarityTestCase {
   
   /** bm25 with default parameters */
   public void test() throws Exception {
-    assertEquals(LegacyBM25Similarity.class, getSimilarity("text").getClass());
+    assertEquals(BM25Similarity.class, getSimilarity("text").getClass());
   }
   
   /** bm25 with parameters */
   public void testParameters() throws Exception {
     Similarity sim = getSimilarity("text_params");
-    assertEquals(LegacyBM25Similarity.class, sim.getClass());
-    LegacyBM25Similarity bm25 = (LegacyBM25Similarity) sim;
+    assertEquals(BM25Similarity.class, sim.getClass());
+    BM25Similarity bm25 = (BM25Similarity) sim;
     assertEquals(1.2f, bm25.getK1(), 0.01f);
     assertEquals(0.76f, bm25.getB(), 0.01f);
   }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/5affe742/solr/core/src/test/org/apache/solr/search/similarities/TestLegacyBM25SimilarityFactory.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/search/similarities/TestLegacyBM25SimilarityFactory.java b/solr/core/src/test/org/apache/solr/search/similarities/TestLegacyBM25SimilarityFactory.java
new file mode 100644
index 0000000..e4c04b5
--- /dev/null
+++ b/solr/core/src/test/org/apache/solr/search/similarities/TestLegacyBM25SimilarityFactory.java
@@ -0,0 +1,45 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.solr.search.similarities;
+
+import org.apache.lucene.search.similarities.Similarity;
+import org.apache.lucene.search.similarity.LegacyBM25Similarity;
+import org.junit.BeforeClass;
+
+/**
+ * Tests {@link LegacyBM25SimilarityFactory}
+ */
+public class TestLegacyBM25SimilarityFactory extends BaseSimilarityTestCase {
+  @BeforeClass
+  public static void beforeClass() throws Exception {
+    initCore("solrconfig-basic.xml","schema-bm25.xml");
+  }
+  
+  /** bm25 with default parameters */
+  public void test() throws Exception {
+    assertEquals(LegacyBM25Similarity.class, getSimilarity("legacy_text").getClass());
+  }
+  
+  /** bm25 with parameters */
+  public void testParameters() throws Exception {
+    Similarity sim = getSimilarity("legacy_text_params");
+    assertEquals(LegacyBM25Similarity.class, sim.getClass());
+    LegacyBM25Similarity bm25 = (LegacyBM25Similarity) sim;
+    assertEquals(1.2f, bm25.getK1(), 0.01f);
+    assertEquals(0.76f, bm25.getB(), 0.01f);
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/5affe742/solr/core/src/test/org/apache/solr/search/similarities/TestNonDefinedSimilarityFactory.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/search/similarities/TestNonDefinedSimilarityFactory.java b/solr/core/src/test/org/apache/solr/search/similarities/TestNonDefinedSimilarityFactory.java
index 9fe33b7..dd30ca1 100644
--- a/solr/core/src/test/org/apache/solr/search/similarities/TestNonDefinedSimilarityFactory.java
+++ b/solr/core/src/test/org/apache/solr/search/similarities/TestNonDefinedSimilarityFactory.java
@@ -16,15 +16,19 @@
  */
 package org.apache.solr.search.similarities;
 
+import org.apache.lucene.search.similarities.BM25Similarity;
 import org.apache.lucene.search.similarity.LegacyBM25Similarity;
+import org.apache.lucene.util.Version;
 import org.junit.After;
 
 /**
- * Verifies that the default behavior of the implicit {@link ClassicSimilarityFactory} 
+ * Verifies that the default behavior of the implicit {@link BM25Similarity} 
  * (ie: no similarity configured in schema.xml at all) is consistent with 
  * expectations based on the luceneMatchVersion
  * @see <a href="https://issues.apache.org/jira/browse/SOLR-5561">SOLR-5561</a>
  * @see <a href="https://issues.apache.org/jira/browse/SOLR-8057">SOLR-8057</a>
+ * @see <a href="https://issues.apache.org/jira/browse/SOLR-13025">SOLR-13025</a>
+ * @see <a href="https://issues.apache.org/jira/browse/LUCENE-8563">LUCENE-8563</a>
  */
 public class TestNonDefinedSimilarityFactory extends BaseSimilarityTestCase {
 
@@ -33,10 +37,30 @@ public class TestNonDefinedSimilarityFactory extends BaseSimilarityTestCase {
     deleteCore();
   }
 
-  public void testCurrentBM25() throws Exception {
+  public void testCurrentBM25FromV8() throws Exception {
     // no sys prop set, rely on LATEST
     initCore("solrconfig-basic.xml","schema-tiny.xml");
+    BM25Similarity sim = getSimilarity("text", BM25Similarity.class);
+    assertEquals(0.75F, sim.getB(), 0.0F);
+  }
+
+  public void testLegacyBM25BeforeV8() throws Exception {
+    System.setProperty("tests.luceneMatchVersion", Version.LUCENE_7_0_0.toString());
+    initCore("solrconfig-basic.xml","schema-tiny.xml");
+    System.clearProperty("tests.luceneMatchVersion");
     LegacyBM25Similarity sim = getSimilarity("text", LegacyBM25Similarity.class);
     assertEquals(0.75F, sim.getB(), 0.0F);
+    deleteCore();
+
+    System.setProperty("tests.luceneMatchVersion", "5.0.0");
+    initCore("solrconfig-basic.xml","schema-tiny.xml");
+    System.clearProperty("tests.luceneMatchVersion");
+    getSimilarity("text", LegacyBM25Similarity.class);
+    deleteCore();
+
+    System.setProperty("tests.luceneMatchVersion", "6.0.0");
+    initCore("solrconfig-basic.xml","schema-tiny.xml");
+    System.clearProperty("tests.luceneMatchVersion");
+    getSimilarity("text", LegacyBM25Similarity.class);
   }
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/5affe742/solr/core/src/test/org/apache/solr/search/similarities/TestPerFieldSimilarity.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/search/similarities/TestPerFieldSimilarity.java b/solr/core/src/test/org/apache/solr/search/similarities/TestPerFieldSimilarity.java
index a27837b..58fe6ef 100644
--- a/solr/core/src/test/org/apache/solr/search/similarities/TestPerFieldSimilarity.java
+++ b/solr/core/src/test/org/apache/solr/search/similarities/TestPerFieldSimilarity.java
@@ -17,8 +17,8 @@
 package org.apache.solr.search.similarities;
 
 import org.apache.lucene.misc.SweetSpotSimilarity;
+import org.apache.lucene.search.similarities.BM25Similarity;
 import org.apache.lucene.search.similarities.Similarity;
-import org.apache.lucene.search.similarity.LegacyBM25Similarity;
 import org.junit.BeforeClass;
 
 /**
@@ -58,18 +58,18 @@ public class TestPerFieldSimilarity extends BaseSimilarityTestCase {
   /** test a field where no similarity is specified */
   public void testDefaults() throws Exception {
     Similarity sim = getSimilarity("sim3text");
-    assertEquals(LegacyBM25Similarity.class, sim.getClass());;
+    assertEquals(BM25Similarity.class, sim.getClass());;
   }
   
   /** ... and for a dynamic field */
   public void testDefaultsDynamic() throws Exception {
     Similarity sim = getSimilarity("text_sim3");
-    assertEquals(LegacyBM25Similarity.class, sim.getClass());
+    assertEquals(BM25Similarity.class, sim.getClass());
   }
   
   /** test a field that does not exist */
   public void testNonexistent() throws Exception {
     Similarity sim = getSimilarity("sdfdsfdsfdswr5fsdfdsfdsfs");
-    assertEquals(LegacyBM25Similarity.class, sim.getClass());
+    assertEquals(BM25Similarity.class, sim.getClass());
   }
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/5affe742/solr/solr-ref-guide/src/major-changes-in-solr-8.adoc
----------------------------------------------------------------------
diff --git a/solr/solr-ref-guide/src/major-changes-in-solr-8.adoc b/solr/solr-ref-guide/src/major-changes-in-solr-8.adoc
index 5df38de..41aa400 100644
--- a/solr/solr-ref-guide/src/major-changes-in-solr-8.adoc
+++ b/solr/solr-ref-guide/src/major-changes-in-solr-8.adoc
@@ -23,3 +23,9 @@
 // *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
 
 * Due to LIR redesign in SOLR-11702, all users must be on Solr 7.3 or higher to upgrade to Solr 8
+
+* If you explicitly use BM25SimilarityFactory in your schema the absolute scoring will be lower, see SOLR-13025.
+  But ordering of documents will not change in the normal case. Use LegacyBM25SimilarityFactory if you need to force
+  the old 6.x/7.x scoring. Note that if you have not specified any similarity in schema or use the default 
+  SchemaSimilarityFactory, then LegacyBM25Similarity is automatically selected for 'luceneMatchVersion' < 8.0.0.
+  See also explanation in Reference Guide chapter "Other Schema Elements".

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/5affe742/solr/solr-ref-guide/src/other-schema-elements.adoc
----------------------------------------------------------------------
diff --git a/solr/solr-ref-guide/src/other-schema-elements.adoc b/solr/solr-ref-guide/src/other-schema-elements.adoc
index 2bcf0fd..99a40e1 100644
--- a/solr/solr-ref-guide/src/other-schema-elements.adoc
+++ b/solr/solr-ref-guide/src/other-schema-elements.adoc
@@ -90,6 +90,6 @@ In most cases, specifying global level similarity like this will cause an error
 
 In the example above `IBSimilarityFactory` (using the Information-Based model) will be used for any fields of type `text_ib`, while `DFRSimilarityFactory` (divergence from random) will be used for any fields of type `text_dfr`, as well as any fields using a type that does not explicitly specify a `<similarity/>`.
 
-If `SchemaSimilarityFactory` is explicitly declared without configuring a `defaultSimFromFieldType`, then `BM25Similarity` is implicitly used as the default.
+If `SchemaSimilarityFactory` is explicitly declared without configuring a `defaultSimFromFieldType`, then `BM25Similarity` is implicitly used as the default for `luceneMatchVersion >= 8.0.0` and otherwise `LegacyBM25Similarity` is used to mimic the same BM25 formula that was the default in those versions.
 
-In addition to the various factories mentioned on this page, there are several other similarity implementations that can be used such as the `SweetSpotSimilarityFactory`, `ClassicSimilarityFactory`, etc. For details, see the Solr Javadocs for the {solr-javadocs}/solr-core/org/apache/solr/schema/SimilarityFactory.html[similarity factories].
+In addition to the various factories mentioned on this page, there are several other similarity implementations that can be used such as the `SweetSpotSimilarityFactory`, `ClassicSimilarityFactory`, `LegacyBM25SimilarityFactory` etc. For details, see the Solr Javadocs for the {solr-javadocs}/solr-core/org/apache/solr/schema/SimilarityFactory.html[similarity factories].