blob: 4bae61c98e5b9bbbe26ee7cf0b3d7741d4e80a4f [file] [log] [blame]
/*
* 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.handler.designer;
import java.io.File;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import com.google.common.collect.ImmutableMap;
import org.apache.solr.client.solrj.SolrQuery;
import org.apache.solr.client.solrj.request.CollectionAdminRequest;
import org.apache.solr.client.solrj.response.QueryResponse;
import org.apache.solr.cloud.SolrCloudTestCase;
import org.apache.solr.common.SolrDocumentList;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.cloud.SolrZkClient;
import org.apache.solr.common.params.CommonParams;
import org.apache.solr.common.params.ModifiableSolrParams;
import org.apache.solr.common.params.SolrParams;
import org.apache.solr.common.util.ContentStream;
import org.apache.solr.common.util.ContentStreamBase;
import org.apache.solr.common.util.NamedList;
import org.apache.solr.common.util.SimpleOrderedMap;
import org.apache.solr.core.CoreContainer;
import org.apache.solr.handler.TestSampleDocumentsLoader;
import org.apache.solr.request.SolrQueryRequest;
import org.apache.solr.response.SolrQueryResponse;
import org.apache.solr.schema.ManagedIndexSchema;
import org.apache.solr.schema.SchemaField;
import org.apache.solr.util.ExternalPaths;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.noggit.JSONUtil;
import static org.apache.solr.common.params.CommonParams.JSON_MIME;
import static org.apache.solr.common.util.Utils.makeMap;
import static org.apache.solr.handler.admin.ConfigSetsHandler.DEFAULT_CONFIGSET_NAME;
import static org.apache.solr.handler.designer.SchemaDesignerAPI.getMutableId;
import static org.apache.solr.response.RawResponseWriter.CONTENT;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class TestSchemaDesignerAPI extends SolrCloudTestCase implements SchemaDesignerConstants {
private CoreContainer cc;
private SchemaDesignerAPI schemaDesignerAPI;
@BeforeClass
public static void createCluster() throws Exception {
System.setProperty("managed.schema.mutable", "true");
configureCluster(1).addConfig(DEFAULT_CONFIGSET_NAME, new File(ExternalPaths.DEFAULT_CONFIGSET).toPath()).configure();
// SchemaDesignerAPI depends on the blob store
CollectionAdminRequest.createCollection(BLOB_STORE_ID, 1, 1).process(cluster.getSolrClient());
cluster.waitForActiveCollection(BLOB_STORE_ID, 1, 1);
}
@AfterClass
public static void tearDownCluster() throws Exception {
if (cluster != null && cluster.getSolrClient() != null) {
cluster.deleteAllCollections();
cluster.deleteAllConfigSets();
}
}
@Before
public void setupTest() {
assumeWorkingMockito();
assertNotNull(cluster);
cc = cluster.getJettySolrRunner(0).getCoreContainer();
assertNotNull(cc);
schemaDesignerAPI = new SchemaDesignerAPI(cc);
}
public void testTSV() throws Exception {
String configSet = "testTSV";
ModifiableSolrParams reqParams = new ModifiableSolrParams();
// GET /schema-designer/info
SolrQueryResponse rsp = new SolrQueryResponse();
SolrQueryRequest req = mock(SolrQueryRequest.class);
reqParams.set(CONFIG_SET_PARAM, configSet);
reqParams.set(LANGUAGES_PARAM, "en");
reqParams.set(ENABLE_DYNAMIC_FIELDS_PARAM, false);
when(req.getParams()).thenReturn(reqParams);
String tsv = "id\tcol1\tcol2\n1\tfoo\tbar\n2\tbaz\tbah\n";
// POST some sample TSV docs
ContentStream stream = new ContentStreamBase.StringStream(tsv, "text/csv");
when(req.getContentStreams()).thenReturn(Collections.singletonList(stream));
// POST /schema-designer/analyze
schemaDesignerAPI.analyze(req, rsp);
assertNotNull(rsp.getValues().get(CONFIG_SET_PARAM));
assertNotNull(rsp.getValues().get(SCHEMA_VERSION_PARAM));
assertEquals(2, rsp.getValues().get("numDocs"));
reqParams.clear();
reqParams.set(CONFIG_SET_PARAM, configSet);
rsp = new SolrQueryResponse();
schemaDesignerAPI.cleanupTemp(req, rsp);
String mutableId = getMutableId(configSet);
assertFalse(cc.getZkController().getClusterState().hasCollection(mutableId));
SolrZkClient zkClient = cc.getZkController().getZkClient();
assertFalse(zkClient.exists("/configs/" + mutableId, true));
}
@Test
@SuppressWarnings("unchecked")
public void testAddTechproductsProgressively() throws Exception {
File docsDir = new File(ExternalPaths.SOURCE_HOME, "example/exampledocs");
assertTrue(docsDir.getAbsolutePath() + " not found!", docsDir.isDirectory());
File[] toAdd = docsDir.listFiles((dir, name) -> name.endsWith(".xml") || name.endsWith(".json") || name.endsWith(".csv") || name.endsWith(".jsonl"));
assertNotNull("No test data files found in " + docsDir.getAbsolutePath(), toAdd);
String configSet = "techproducts";
ModifiableSolrParams reqParams = new ModifiableSolrParams();
// GET /schema-designer/info
SolrQueryResponse rsp = new SolrQueryResponse();
SolrQueryRequest req = mock(SolrQueryRequest.class);
reqParams.set(CONFIG_SET_PARAM, configSet);
when(req.getParams()).thenReturn(reqParams);
schemaDesignerAPI.getInfo(req, rsp);
// response should just be the default values
Map<String, Object> expSettings = makeMap(
ENABLE_DYNAMIC_FIELDS_PARAM, true,
ENABLE_FIELD_GUESSING_PARAM, true,
ENABLE_NESTED_DOCS_PARAM, false,
LANGUAGES_PARAM, Collections.emptyList());
assertDesignerSettings(expSettings, rsp.getValues());
SolrParams rspData = rsp.getValues().toSolrParams();
int schemaVersion = rspData.getInt(SCHEMA_VERSION_PARAM);
assertEquals(schemaVersion, -1); // shouldn't exist yet
// Use the prep endpoint to prepare the new schema
reqParams.clear();
reqParams.set(CONFIG_SET_PARAM, configSet);
rsp = new SolrQueryResponse();
req = mock(SolrQueryRequest.class);
when(req.getParams()).thenReturn(reqParams);
schemaDesignerAPI.prepNewSchema(req, rsp);
assertNotNull(rsp.getValues().get(CONFIG_SET_PARAM));
assertNotNull(rsp.getValues().get(SCHEMA_VERSION_PARAM));
rspData = rsp.getValues().toSolrParams();
schemaVersion = rspData.getInt(SCHEMA_VERSION_PARAM);
for (File next : toAdd) {
// Analyze some sample documents to refine the schema
reqParams.clear();
reqParams.set(CONFIG_SET_PARAM, configSet);
reqParams.set(LANGUAGES_PARAM, "en");
reqParams.set(ENABLE_DYNAMIC_FIELDS_PARAM, false);
reqParams.set(SCHEMA_VERSION_PARAM, String.valueOf(schemaVersion));
req = mock(SolrQueryRequest.class);
when(req.getParams()).thenReturn(reqParams);
// POST some sample JSON docs
ContentStreamBase.FileStream stream = new ContentStreamBase.FileStream(next);
stream.setContentType(TestSampleDocumentsLoader.guessContentTypeFromFilename(next.getName()));
when(req.getContentStreams()).thenReturn(Collections.singletonList(stream));
rsp = new SolrQueryResponse();
// POST /schema-designer/analyze
schemaDesignerAPI.analyze(req, rsp);
assertNotNull(rsp.getValues().get(CONFIG_SET_PARAM));
assertNotNull(rsp.getValues().get(SCHEMA_VERSION_PARAM));
assertNotNull(rsp.getValues().get("fields"));
assertNotNull(rsp.getValues().get("fieldTypes"));
assertNotNull(rsp.getValues().get("docIds"));
// capture the schema version for MVCC
rspData = rsp.getValues().toSolrParams();
schemaVersion = rspData.getInt(SCHEMA_VERSION_PARAM);
}
// get info (from the temp)
reqParams.clear();
reqParams.set(CONFIG_SET_PARAM, configSet);
req = mock(SolrQueryRequest.class);
when(req.getParams()).thenReturn(reqParams);
rsp = new SolrQueryResponse();
// GET /schema-designer/info
schemaDesignerAPI.getInfo(req, rsp);
expSettings = makeMap(
ENABLE_DYNAMIC_FIELDS_PARAM, false,
ENABLE_FIELD_GUESSING_PARAM, true,
ENABLE_NESTED_DOCS_PARAM, false,
LANGUAGES_PARAM, Collections.singletonList("en"),
COPY_FROM_PARAM, "_default");
assertDesignerSettings(expSettings, rsp.getValues());
// query to see how the schema decisions impact retrieval / ranking
reqParams.clear();
reqParams.set(SCHEMA_VERSION_PARAM, String.valueOf(schemaVersion));
reqParams.set(CONFIG_SET_PARAM, configSet);
reqParams.set(CommonParams.Q, "*:*");
req = mock(SolrQueryRequest.class);
when(req.getParams()).thenReturn(reqParams);
rsp = new SolrQueryResponse();
// GET /schema-designer/query
schemaDesignerAPI.query(req, rsp);
assertNotNull(rsp.getResponseHeader());
SolrDocumentList results = (SolrDocumentList) rsp.getResponse();
assertEquals(48, results.getNumFound());
// publish schema to a config set that can be used by real collections
reqParams.clear();
reqParams.set(SCHEMA_VERSION_PARAM, String.valueOf(schemaVersion));
reqParams.set(CONFIG_SET_PARAM, configSet);
String collection = "techproducts";
reqParams.set(NEW_COLLECTION_PARAM, collection);
reqParams.set(INDEX_TO_COLLECTION_PARAM, true);
reqParams.set(RELOAD_COLLECTIONS_PARAM, true);
reqParams.set(CLEANUP_TEMP_PARAM, true);
reqParams.set(DISABLE_DESIGNER_PARAM, true);
rsp = new SolrQueryResponse();
schemaDesignerAPI.publish(req, rsp);
assertNotNull(cc.getZkController().zkStateReader.getCollection(collection));
// listCollectionsForConfig
reqParams.clear();
reqParams.set(CONFIG_SET_PARAM, configSet);
rsp = new SolrQueryResponse();
schemaDesignerAPI.listCollectionsForConfig(req, rsp);
List<String> collections = (List<String>) rsp.getValues().get("collections");
assertNotNull(collections);
assertTrue(collections.contains(collection));
// now try to create another temp, which should fail since designer is disabled for this configSet now
reqParams.clear();
reqParams.set(CONFIG_SET_PARAM, configSet);
rsp = new SolrQueryResponse();
req = mock(SolrQueryRequest.class);
when(req.getParams()).thenReturn(reqParams);
try {
schemaDesignerAPI.prepNewSchema(req, rsp);
fail("Prep should fail for locked schema " + configSet);
} catch (SolrException solrExc) {
assertEquals(SolrException.ErrorCode.BAD_REQUEST.code, solrExc.code());
}
}
@Test
@SuppressWarnings("unchecked")
public void testSuggestFilmsXml() throws Exception {
String configSet = "films";
ModifiableSolrParams reqParams = new ModifiableSolrParams();
File filmsDir = new File(ExternalPaths.SOURCE_HOME, "example/films");
assertTrue(filmsDir.getAbsolutePath() + " not found!", filmsDir.isDirectory());
File filmsXml = new File(filmsDir, "films.xml");
assertTrue("example/films/films.xml not found", filmsXml.isFile());
reqParams.set(CONFIG_SET_PARAM, configSet);
reqParams.set(ENABLE_DYNAMIC_FIELDS_PARAM, "true");
SolrQueryRequest req = mock(SolrQueryRequest.class);
when(req.getParams()).thenReturn(reqParams);
// POST some sample XML docs
ContentStreamBase.FileStream stream = new ContentStreamBase.FileStream(filmsXml);
stream.setContentType("application/xml");
when(req.getContentStreams()).thenReturn(Collections.singletonList(stream));
SolrQueryResponse rsp = new SolrQueryResponse();
// POST /schema-designer/analyze
schemaDesignerAPI.analyze(req, rsp);
assertNotNull(rsp.getValues().get(CONFIG_SET_PARAM));
assertNotNull(rsp.getValues().get(SCHEMA_VERSION_PARAM));
assertNotNull(rsp.getValues().get("fields"));
assertNotNull(rsp.getValues().get("fieldTypes"));
List<String> docIds = (List<String>) rsp.getValues().get("docIds");
assertNotNull(docIds);
assertEquals(100, docIds.size()); // designer limits doc ids to top 100
String idField = rsp.getValues()._getStr(UNIQUE_KEY_FIELD_PARAM, null);
assertNotNull(idField);
}
@Test
@SuppressWarnings("unchecked")
public void testBasicUserWorkflow() throws Exception {
String configSet = "testJson";
ModifiableSolrParams reqParams = new ModifiableSolrParams();
// Use the prep endpoint to prepare the new schema
reqParams.set(CONFIG_SET_PARAM, configSet);
SolrQueryResponse rsp = new SolrQueryResponse();
SolrQueryRequest req = mock(SolrQueryRequest.class);
when(req.getParams()).thenReturn(reqParams);
schemaDesignerAPI.prepNewSchema(req, rsp);
assertNotNull(rsp.getValues().get(CONFIG_SET_PARAM));
assertNotNull(rsp.getValues().get(SCHEMA_VERSION_PARAM));
Map<String, Object> expSettings = makeMap(
ENABLE_DYNAMIC_FIELDS_PARAM, true,
ENABLE_FIELD_GUESSING_PARAM, true,
ENABLE_NESTED_DOCS_PARAM, false,
LANGUAGES_PARAM, Collections.emptyList(),
COPY_FROM_PARAM, "_default");
assertDesignerSettings(expSettings, rsp.getValues());
// Analyze some sample documents to refine the schema
reqParams.clear();
reqParams.set(CONFIG_SET_PARAM, configSet);
req = mock(SolrQueryRequest.class);
when(req.getParams()).thenReturn(reqParams);
// POST some sample JSON docs
File booksJson = new File(ExternalPaths.SOURCE_HOME, "example/exampledocs/books.json");
ContentStreamBase.FileStream stream = new ContentStreamBase.FileStream(booksJson);
stream.setContentType(JSON_MIME);
when(req.getContentStreams()).thenReturn(Collections.singletonList(stream));
rsp = new SolrQueryResponse();
// POST /schema-designer/analyze
schemaDesignerAPI.analyze(req, rsp);
assertNotNull(rsp.getValues().get(CONFIG_SET_PARAM));
assertNotNull(rsp.getValues().get(SCHEMA_VERSION_PARAM));
assertNotNull(rsp.getValues().get("fields"));
assertNotNull(rsp.getValues().get("fieldTypes"));
assertNotNull(rsp.getValues().get("docIds"));
String idField = rsp.getValues()._getStr(UNIQUE_KEY_FIELD_PARAM, null);
assertNotNull(idField);
assertDesignerSettings(expSettings, rsp.getValues());
// capture the schema version for MVCC
SolrParams rspData = rsp.getValues().toSolrParams();
reqParams.clear();
int schemaVersion = rspData.getInt(SCHEMA_VERSION_PARAM);
// load the contents of a file
Collection<String> files = (Collection<String>) rsp.getValues().get("files");
assertTrue(files != null && !files.isEmpty());
reqParams.set(CONFIG_SET_PARAM, configSet);
String file = null;
for (String f : files) {
if ("solrconfig.xml".equals(f)) {
file = f;
break;
}
}
assertNotNull("solrconfig.xml not found in files!", file);
reqParams.set("file", file);
req = mock(SolrQueryRequest.class);
when(req.getParams()).thenReturn(reqParams);
rsp = new SolrQueryResponse();
schemaDesignerAPI.getFileContents(req, rsp);
String solrconfigXml = (String) rsp.getValues().get(file);
assertNotNull(solrconfigXml);
reqParams.clear();
// Update solrconfig.xml
rsp = new SolrQueryResponse();
reqParams.set(SCHEMA_VERSION_PARAM, String.valueOf(schemaVersion));
reqParams.set(CONFIG_SET_PARAM, configSet);
reqParams.set("file", file);
req = mock(SolrQueryRequest.class);
when(req.getParams()).thenReturn(reqParams);
when(req.getContentStreams()).thenReturn(
Collections.singletonList(new ContentStreamBase.StringStream(solrconfigXml, "application/xml")));
schemaDesignerAPI.updateFileContents(req, rsp);
rspData = rsp.getValues().toSolrParams();
reqParams.clear();
schemaVersion = rspData.getInt(SCHEMA_VERSION_PARAM);
// update solrconfig.xml with some invalid XML mess
rsp = new SolrQueryResponse();
reqParams.set(SCHEMA_VERSION_PARAM, String.valueOf(schemaVersion));
reqParams.set(CONFIG_SET_PARAM, configSet);
reqParams.set("file", file);
req = mock(SolrQueryRequest.class);
when(req.getParams()).thenReturn(reqParams);
when(req.getContentStreams()).thenReturn(
Collections.singletonList(new ContentStreamBase.StringStream("<config/>", "application/xml")));
// this should fail b/c the updated solrconfig.xml is invalid
schemaDesignerAPI.updateFileContents(req, rsp);
rspData = rsp.getValues().toSolrParams();
reqParams.clear();
assertNotNull(rspData.get("updateFileError"));
// remove dynamic fields and change the language to "en" only
rsp = new SolrQueryResponse();
// POST /schema-designer/analyze
reqParams.set(SCHEMA_VERSION_PARAM, String.valueOf(schemaVersion));
reqParams.set(CONFIG_SET_PARAM, configSet);
reqParams.set(LANGUAGES_PARAM, "en");
reqParams.set(ENABLE_DYNAMIC_FIELDS_PARAM, false);
reqParams.set(ENABLE_FIELD_GUESSING_PARAM, false);
req = mock(SolrQueryRequest.class);
when(req.getParams()).thenReturn(reqParams);
schemaDesignerAPI.analyze(req, rsp);
expSettings = makeMap(
ENABLE_DYNAMIC_FIELDS_PARAM, false,
ENABLE_FIELD_GUESSING_PARAM, false,
ENABLE_NESTED_DOCS_PARAM, false,
LANGUAGES_PARAM, Collections.singletonList("en"),
COPY_FROM_PARAM, "_default");
assertDesignerSettings(expSettings, rsp.getValues());
List<String> filesInResp = (List<String>) rsp.getValues().get("files");
assertEquals(5, filesInResp.size());
assertTrue(filesInResp.contains("lang/stopwords_en.txt"));
rspData = rsp.getValues().toSolrParams();
schemaVersion = rspData.getInt(SCHEMA_VERSION_PARAM);
reqParams.clear();
// add the dynamic fields back and change the languages too
rsp = new SolrQueryResponse();
reqParams.set(SCHEMA_VERSION_PARAM, String.valueOf(schemaVersion));
reqParams.set(CONFIG_SET_PARAM, configSet);
reqParams.add(LANGUAGES_PARAM, "en");
reqParams.add(LANGUAGES_PARAM, "fr");
reqParams.set(ENABLE_DYNAMIC_FIELDS_PARAM, true);
reqParams.set(ENABLE_FIELD_GUESSING_PARAM, false);
req = mock(SolrQueryRequest.class);
when(req.getParams()).thenReturn(reqParams);
schemaDesignerAPI.analyze(req, rsp);
expSettings = makeMap(
ENABLE_DYNAMIC_FIELDS_PARAM, true,
ENABLE_FIELD_GUESSING_PARAM, false,
ENABLE_NESTED_DOCS_PARAM, false,
LANGUAGES_PARAM, Arrays.asList("en", "fr"),
COPY_FROM_PARAM, "_default");
assertDesignerSettings(expSettings, rsp.getValues());
filesInResp = (List<String>) rsp.getValues().get("files");
assertEquals(7, filesInResp.size());
assertTrue(filesInResp.contains("lang/stopwords_fr.txt"));
rspData = rsp.getValues().toSolrParams();
reqParams.clear();
schemaVersion = rspData.getInt(SCHEMA_VERSION_PARAM);
// add back all the default languages
rsp = new SolrQueryResponse();
reqParams.set(SCHEMA_VERSION_PARAM, String.valueOf(schemaVersion));
reqParams.set(CONFIG_SET_PARAM, configSet);
reqParams.add(LANGUAGES_PARAM, "*");
reqParams.set(ENABLE_DYNAMIC_FIELDS_PARAM, false);
req = mock(SolrQueryRequest.class);
when(req.getParams()).thenReturn(reqParams);
schemaDesignerAPI.analyze(req, rsp);
expSettings = makeMap(
ENABLE_DYNAMIC_FIELDS_PARAM, false,
ENABLE_FIELD_GUESSING_PARAM, false,
ENABLE_NESTED_DOCS_PARAM, false,
LANGUAGES_PARAM, Collections.emptyList(),
COPY_FROM_PARAM, "_default");
assertDesignerSettings(expSettings, rsp.getValues());
filesInResp = (List<String>) rsp.getValues().get("files");
assertEquals(43, filesInResp.size());
assertTrue(filesInResp.contains("lang/stopwords_fr.txt"));
assertTrue(filesInResp.contains("lang/stopwords_en.txt"));
assertTrue(filesInResp.contains("lang/stopwords_it.txt"));
rspData = rsp.getValues().toSolrParams();
reqParams.clear();
schemaVersion = rspData.getInt(SCHEMA_VERSION_PARAM);
// Get the value of a sample document
String docId = "978-0641723445";
String fieldName = "series_t";
reqParams.set(CONFIG_SET_PARAM, configSet);
reqParams.set(DOC_ID_PARAM, docId);
reqParams.set(FIELD_PARAM, fieldName);
reqParams.set(UNIQUE_KEY_FIELD_PARAM, idField);
req = mock(SolrQueryRequest.class);
when(req.getParams()).thenReturn(reqParams);
rsp = new SolrQueryResponse();
// GET /schema-designer/sample
schemaDesignerAPI.getSampleValue(req, rsp);
rspData = rsp.getValues().toSolrParams();
assertNotNull(rspData.get(idField));
assertNotNull(rspData.get(fieldName));
assertNotNull(rspData.get("analysis"));
reqParams.clear();
// at this point the user would refine the schema by
// editing suggestions for fields and adding/removing fields / field types as needed
// add a new field
reqParams.set(SCHEMA_VERSION_PARAM, String.valueOf(schemaVersion));
reqParams.set(CONFIG_SET_PARAM, configSet);
req = mock(SolrQueryRequest.class);
when(req.getParams()).thenReturn(reqParams);
stream = new ContentStreamBase.FileStream(getFile("schema-designer/add-new-field.json"));
stream.setContentType(JSON_MIME);
when(req.getContentStreams()).thenReturn(Collections.singletonList(stream));
rsp = new SolrQueryResponse();
// POST /schema-designer/add
schemaDesignerAPI.addSchemaObject(req, rsp);
assertNotNull(rsp.getValues().get("add-field"));
rspData = rsp.getValues().toSolrParams();
schemaVersion = rspData.getInt(SCHEMA_VERSION_PARAM);
assertNotNull(rsp.getValues().get("fields"));
// update an existing field
reqParams.clear();
reqParams.set(SCHEMA_VERSION_PARAM, String.valueOf(schemaVersion));
reqParams.set(CONFIG_SET_PARAM, configSet);
req = mock(SolrQueryRequest.class);
when(req.getParams()).thenReturn(reqParams);
// switch a single-valued field to a multi-valued field, which triggers a full rebuild of the "temp" collection
stream = new ContentStreamBase.FileStream(getFile("schema-designer/update-author-field.json"));
stream.setContentType(JSON_MIME);
when(req.getContentStreams()).thenReturn(Collections.singletonList(stream));
rsp = new SolrQueryResponse();
// PUT /schema-designer/update
schemaDesignerAPI.updateSchemaObject(req, rsp);
assertNotNull(rsp.getValues().get("field"));
rspData = rsp.getValues().toSolrParams();
schemaVersion = rspData.getInt(SCHEMA_VERSION_PARAM);
// add a new type
reqParams.set(SCHEMA_VERSION_PARAM, String.valueOf(schemaVersion));
reqParams.set(CONFIG_SET_PARAM, configSet);
req = mock(SolrQueryRequest.class);
when(req.getParams()).thenReturn(reqParams);
stream = new ContentStreamBase.FileStream(getFile("schema-designer/add-new-type.json"));
stream.setContentType(JSON_MIME);
when(req.getContentStreams()).thenReturn(Collections.singletonList(stream));
rsp = new SolrQueryResponse();
// POST /schema-designer/add
schemaDesignerAPI.addSchemaObject(req, rsp);
final String expectedTypeName = "test_txt";
assertEquals(expectedTypeName, rsp.getValues().get("add-field-type"));
rspData = rsp.getValues().toSolrParams();
schemaVersion = rspData.getInt(SCHEMA_VERSION_PARAM);
assertNotNull(rsp.getValues().get("fieldTypes"));
List<SimpleOrderedMap<Object>> fieldTypes = (List<SimpleOrderedMap<Object>>) rsp.getValues().get("fieldTypes");
Optional<SimpleOrderedMap<Object>> expected = fieldTypes.stream().filter(m -> expectedTypeName.equals(m.get("name"))).findFirst();
assertTrue("New field type '" + expectedTypeName + "' not found in add type response!", expected.isPresent());
reqParams.clear();
reqParams.set(SCHEMA_VERSION_PARAM, String.valueOf(schemaVersion));
reqParams.set(CONFIG_SET_PARAM, configSet);
req = mock(SolrQueryRequest.class);
when(req.getParams()).thenReturn(reqParams);
stream = new ContentStreamBase.FileStream(getFile("schema-designer/update-type.json"));
stream.setContentType(JSON_MIME);
when(req.getContentStreams()).thenReturn(Collections.singletonList(stream));
rsp = new SolrQueryResponse();
// POST /schema-designer/update
schemaDesignerAPI.updateSchemaObject(req, rsp);
rspData = rsp.getValues().toSolrParams();
schemaVersion = rspData.getInt(SCHEMA_VERSION_PARAM);
// query to see how the schema decisions impact retrieval / ranking
reqParams.clear();
reqParams.set(SCHEMA_VERSION_PARAM, String.valueOf(schemaVersion));
reqParams.set(CONFIG_SET_PARAM, configSet);
reqParams.set(CommonParams.Q, "*:*");
req = mock(SolrQueryRequest.class);
when(req.getParams()).thenReturn(reqParams);
rsp = new SolrQueryResponse();
// GET /schema-designer/query
schemaDesignerAPI.query(req, rsp);
assertNotNull(rsp.getResponseHeader());
SolrDocumentList results = (SolrDocumentList) rsp.getResponse();
assertEquals(4, results.size());
// Download ZIP
reqParams.clear();
reqParams.set(CONFIG_SET_PARAM, configSet);
req = mock(SolrQueryRequest.class);
when(req.getParams()).thenReturn(reqParams);
rsp = new SolrQueryResponse();
schemaDesignerAPI.downloadConfig(req, rsp);
assertNotNull(rsp.getValues().get(CONTENT));
// publish schema to a config set that can be used by real collections
reqParams.clear();
reqParams.set(SCHEMA_VERSION_PARAM, String.valueOf(schemaVersion));
reqParams.set(CONFIG_SET_PARAM, configSet);
String collection = "test123";
reqParams.set(NEW_COLLECTION_PARAM, collection);
reqParams.set(INDEX_TO_COLLECTION_PARAM, true);
reqParams.set(RELOAD_COLLECTIONS_PARAM, true);
reqParams.set(CLEANUP_TEMP_PARAM, true);
rsp = new SolrQueryResponse();
schemaDesignerAPI.publish(req, rsp);
assertNotNull(cc.getZkController().zkStateReader.getCollection(collection));
// listCollectionsForConfig
reqParams.clear();
reqParams.set(CONFIG_SET_PARAM, configSet);
rsp = new SolrQueryResponse();
schemaDesignerAPI.listCollectionsForConfig(req, rsp);
List<String> collections = (List<String>) rsp.getValues().get("collections");
assertNotNull(collections);
assertTrue(collections.contains(collection));
// verify temp designer objects were cleaned up during the publish operation ...
String mutableId = getMutableId(configSet);
assertFalse(cc.getZkController().getClusterState().hasCollection(mutableId));
SolrZkClient zkClient = cc.getZkController().getZkClient();
assertFalse(zkClient.exists("/configs/" + mutableId, true));
SolrQuery query = new SolrQuery("*:*");
query.setRows(0);
QueryResponse qr = cluster.getSolrClient().query(collection, query);
// this proves the docs were stored in the blob store too
assertEquals(4, qr.getResults().getNumFound());
}
@SuppressWarnings("unchecked")
public void testFieldUpdates() throws Exception {
String configSet = "fieldUpdates";
ModifiableSolrParams reqParams = new ModifiableSolrParams();
// Use the prep endpoint to prepare the new schema
reqParams.set(CONFIG_SET_PARAM, configSet);
SolrQueryResponse rsp = new SolrQueryResponse();
SolrQueryRequest req = mock(SolrQueryRequest.class);
when(req.getParams()).thenReturn(reqParams);
schemaDesignerAPI.prepNewSchema(req, rsp);
assertNotNull(rsp.getValues().get(CONFIG_SET_PARAM));
assertNotNull(rsp.getValues().get(SCHEMA_VERSION_PARAM));
SolrParams rspData = rsp.getValues().toSolrParams();
int schemaVersion = rspData.getInt(SCHEMA_VERSION_PARAM);
// add our test field that we'll test various updates to
reqParams.clear();
reqParams.set(SCHEMA_VERSION_PARAM, String.valueOf(schemaVersion));
reqParams.set(CONFIG_SET_PARAM, configSet);
req = mock(SolrQueryRequest.class);
when(req.getParams()).thenReturn(reqParams);
ContentStreamBase.FileStream stream = new ContentStreamBase.FileStream(getFile("schema-designer/add-new-field.json"));
stream.setContentType(JSON_MIME);
when(req.getContentStreams()).thenReturn(Collections.singletonList(stream));
rsp = new SolrQueryResponse();
// POST /schema-designer/add
schemaDesignerAPI.addSchemaObject(req, rsp);
assertNotNull(rsp.getValues().get("add-field"));
final String fieldName = "keywords";
Optional<SimpleOrderedMap<Object>> maybeField =
((List<SimpleOrderedMap<Object>>) rsp.getValues().get("fields")).stream().filter(m -> fieldName.equals(m.get("name"))).findFirst();
assertTrue(maybeField.isPresent());
SimpleOrderedMap<Object> field = maybeField.get();
assertEquals(Boolean.FALSE, field.get("indexed"));
assertEquals(Boolean.FALSE, field.get("required"));
assertEquals(Boolean.TRUE, field.get("stored"));
assertEquals(Boolean.TRUE, field.get("docValues"));
assertEquals(Boolean.TRUE, field.get("useDocValuesAsStored"));
assertEquals(Boolean.FALSE, field.get("multiValued"));
assertEquals("string", field.get("type"));
String mutableId = getMutableId(configSet);
SchemaDesignerConfigSetHelper configSetHelper =
new SchemaDesignerConfigSetHelper(cc, SchemaDesignerAPI.newSchemaSuggester(cc));
ManagedIndexSchema schema = schemaDesignerAPI.loadLatestSchema(mutableId);
// make it required
Map<String, Object> updateField = makeMap("name", fieldName, "type", field.get("type"), "required", true);
configSetHelper.updateField(configSet, updateField, schema);
schema = schemaDesignerAPI.loadLatestSchema(mutableId);
SchemaField schemaField = schema.getField(fieldName);
assertTrue(schemaField.isRequired());
updateField = makeMap("name", fieldName, "type", field.get("type"), "required", false, "stored", false);
configSetHelper.updateField(configSet, updateField, schema);
schema = schemaDesignerAPI.loadLatestSchema(mutableId);
schemaField = schema.getField(fieldName);
assertFalse(schemaField.isRequired());
assertFalse(schemaField.stored());
updateField = makeMap("name", fieldName, "type", field.get("type"), "required", false, "stored", false, "multiValued", true);
configSetHelper.updateField(configSet, updateField, schema);
schema = schemaDesignerAPI.loadLatestSchema(mutableId);
schemaField = schema.getField(fieldName);
assertFalse(schemaField.isRequired());
assertFalse(schemaField.stored());
assertTrue(schemaField.multiValued());
updateField = makeMap("name", fieldName, "type", "strings", "copyDest", "_text_");
configSetHelper.updateField(configSet, updateField, schema);
schema = schemaDesignerAPI.loadLatestSchema(mutableId);
schemaField = schema.getField(fieldName);
assertTrue(schemaField.multiValued());
assertEquals("strings", schemaField.getType().getTypeName());
assertFalse(schemaField.isRequired());
assertTrue(schemaField.stored());
List<String> srcFields = schema.getCopySources("_text_");
assertEquals(Collections.singletonList(fieldName), srcFields);
}
@SuppressWarnings({"unchecked"})
public void testSchemaDiffEndpoint() throws Exception {
String configSet = "testDiff";
ModifiableSolrParams reqParams = new ModifiableSolrParams();
// Use the prep endpoint to prepare the new schema
reqParams.set(CONFIG_SET_PARAM, configSet);
SolrQueryResponse rsp = new SolrQueryResponse();
SolrQueryRequest req = mock(SolrQueryRequest.class);
when(req.getParams()).thenReturn(reqParams);
schemaDesignerAPI.prepNewSchema(req, rsp);
assertNotNull(rsp.getValues().get(CONFIG_SET_PARAM));
assertNotNull(rsp.getValues().get(SCHEMA_VERSION_PARAM));
// publish schema to a config set that can be used by real collections
reqParams.clear();
reqParams.set(SCHEMA_VERSION_PARAM, String.valueOf(rsp.getValues().get(SCHEMA_VERSION_PARAM)));
reqParams.set(CONFIG_SET_PARAM, configSet);
String collection = "diff456";
reqParams.set(NEW_COLLECTION_PARAM, collection);
reqParams.set(INDEX_TO_COLLECTION_PARAM, true);
reqParams.set(RELOAD_COLLECTIONS_PARAM, true);
reqParams.set(CLEANUP_TEMP_PARAM, true);
rsp = new SolrQueryResponse();
schemaDesignerAPI.publish(req, rsp);
assertNotNull(cc.getZkController().zkStateReader.getCollection(collection));
// Load the schema designer for the existing config set and make some changes to it
reqParams.clear();
reqParams.set(CONFIG_SET_PARAM, configSet);
reqParams.set(ENABLE_DYNAMIC_FIELDS_PARAM, "true");
reqParams.set(ENABLE_FIELD_GUESSING_PARAM, "false");
rsp = new SolrQueryResponse();
schemaDesignerAPI.analyze(req, rsp);
// Update id field to not use docValues
List<SimpleOrderedMap<Object>> fields = (List<SimpleOrderedMap<Object>>) rsp.getValues().get("fields");
SimpleOrderedMap<Object> idFieldMap = fields.stream().filter(field -> field.get("name").equals("id")).findFirst().get();
idFieldMap.remove("copyDest"); // Don't include copyDest as it is not a property of SchemaField
SimpleOrderedMap<Object> idFieldMapUpdated = idFieldMap.clone();
idFieldMapUpdated.setVal(idFieldMapUpdated.indexOf("docValues", 0), Boolean.FALSE);
idFieldMapUpdated.setVal(idFieldMapUpdated.indexOf("useDocValuesAsStored", 0), Boolean.FALSE);
idFieldMapUpdated.setVal(idFieldMapUpdated.indexOf("omitTermFreqAndPositions", 0), Boolean.FALSE);
SolrParams solrParams = idFieldMapUpdated.toSolrParams();
Map<String, Object> mapParams = solrParams.toMap(new HashMap<>());
mapParams.put("termVectors", Boolean.FALSE);
reqParams.set(SCHEMA_VERSION_PARAM, rsp.getValues().toSolrParams().getInt(SCHEMA_VERSION_PARAM));
ContentStreamBase.StringStream stringStream = new ContentStreamBase.StringStream(JSONUtil.toJSON(mapParams), JSON_MIME);
when(req.getContentStreams()).thenReturn(Collections.singletonList(stringStream));
rsp = new SolrQueryResponse();
schemaDesignerAPI.updateSchemaObject(req, rsp);
// Add a new field
Integer schemaVersion = rsp.getValues().toSolrParams().getInt(SCHEMA_VERSION_PARAM);
reqParams.set(SCHEMA_VERSION_PARAM, schemaVersion);
ContentStreamBase.FileStream fileStream = new ContentStreamBase.FileStream(getFile("schema-designer/add-new-field.json"));
fileStream.setContentType(JSON_MIME);
when(req.getContentStreams()).thenReturn(Collections.singletonList(fileStream));
rsp = new SolrQueryResponse();
// POST /schema-designer/add
schemaDesignerAPI.addSchemaObject(req, rsp);
assertNotNull(rsp.getValues().get("add-field"));
// Add a new field type
schemaVersion = rsp.getValues().toSolrParams().getInt(SCHEMA_VERSION_PARAM);
reqParams.set(SCHEMA_VERSION_PARAM, schemaVersion);
fileStream = new ContentStreamBase.FileStream(getFile("schema-designer/add-new-type.json"));
fileStream.setContentType(JSON_MIME);
when(req.getContentStreams()).thenReturn(Collections.singletonList(fileStream));
rsp = new SolrQueryResponse();
// POST /schema-designer/add
schemaDesignerAPI.addSchemaObject(req, rsp);
assertNotNull(rsp.getValues().get("add-field-type"));
// Let's do a diff now
rsp = new SolrQueryResponse();
schemaDesignerAPI.getSchemaDiff(req, rsp);
Map<String, Object> diff = (Map<String, Object>) rsp.getValues().get("diff");
// field asserts
assertNotNull(diff.get("fields"));
Map<String, Object> fieldsDiff = (Map<String, Object>) diff.get("fields");
assertNotNull(fieldsDiff.get("updated"));
Map<String, Object> mapDiff = (Map<String, Object>) fieldsDiff.get("updated");
assertEquals(
Arrays.asList(
ImmutableMap.of("omitTermFreqAndPositions", true, "useDocValuesAsStored", true, "docValues", true),
ImmutableMap.of("omitTermFreqAndPositions", false, "useDocValuesAsStored", false, "docValues", false)),
mapDiff.get("id"));
assertNotNull(fieldsDiff.get("added"));
Map<String, Object> fieldsAdded = (Map<String, Object>) fieldsDiff.get("added");
assertNotNull(fieldsAdded.get("keywords"));
// field type asserts
assertNotNull(diff.get("fieldTypes"));
Map<String, Object> fieldTypesDiff = (Map<String, Object>) diff.get("fieldTypes");
assertNotNull(fieldTypesDiff.get("added"));
Map<String, Object> fieldTypesAdded = (Map<String, Object>) fieldTypesDiff.get("added");
assertNotNull(fieldTypesAdded.get("test_txt"));
}
@SuppressWarnings("rawtypes")
protected void assertDesignerSettings(Map<String, Object> expected, NamedList actual) {
for (String expKey : expected.keySet()) {
Object expValue = expected.get(expKey);
assertEquals("Value for designer setting '" + expKey + "' not match expected!", expValue, actual.get(expKey));
}
}
}