blob: 5132a9b7f0796df75c0e6edddb5a1677d824115e [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.update.processor;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.apache.lucene.util.TestUtil;
import org.apache.solr.SolrTestCaseJ4;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.SolrInputDocument;
import org.apache.solr.common.util.ExecutorUtil;
import org.apache.solr.common.util.NamedList;
import org.apache.solr.schema.IndexSchema;
import org.apache.solr.schema.SchemaField;
import org.apache.solr.common.util.SolrNamedThreadFactory;
import org.junit.Before;
import org.junit.BeforeClass;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.hasItem;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.CoreMatchers.nullValue;
public class TestDocBasedVersionConstraints extends SolrTestCaseJ4 {
@BeforeClass
public static void beforeClass() throws Exception {
initCore("solrconfig-externalversionconstraint.xml", "schema15.xml");
}
@Before
public void before() throws Exception {
assertU(delQ("*:*"));
assertU(commit());
}
public void testSimpleUpdates() throws Exception {
// skip low version against committed data
assertU(adoc("id", "aaa", "name", "a1", "my_version_l", "1001"));
assertU(commit());
assertU(adoc("id", "aaa", "name", "a2", "my_version_l", "1002"));
assertU(commit());
assertU(adoc("id", "aaa", "name", "XX", "my_version_l", "1"));
assertJQ(req("qt","/get", "id","aaa", "fl","name")
, "=={'doc':{'name':'a2'}}");
assertU(commit());
assertJQ(req("q","+id:aaa"), "/response/numFound==1");
assertJQ(req("q","+id:aaa +name:XX"), "/response/numFound==0");
assertJQ(req("q","+id:aaa +name:a2"), "/response/numFound==1");
assertJQ(req("qt","/get", "id","aaa", "fl","name")
, "=={'doc':{'name':'a2'}}");
// skip low version against uncommitted data from updateLog
assertU(adoc("id", "aaa", "name", "a3", "my_version_l", "1003"));
assertU(adoc("id", "aaa", "name", "XX", "my_version_l", "7"));
assertJQ(req("qt","/get", "id","aaa", "fl","name")
, "=={'doc':{'name':'a3'}}");
assertU(commit());
assertJQ(req("q","+id:aaa"), "/response/numFound==1");
assertJQ(req("q","+id:aaa +name:XX"), "/response/numFound==0");
assertJQ(req("q","+id:aaa +name:a3"), "/response/numFound==1");
assertJQ(req("qt","/get", "id","aaa", "fl","name")
, "=={'doc':{'name':'a3'}}");
// interleave updates to multiple docs using same versions
for (long ver = 1010; ver < 1020; ver++) {
for (String id : new String[] {"aaa", "bbb", "ccc", "ddd"}) {
assertU(adoc("id", id, "my_version_l", ""+ver));
}
}
for (String id : new String[] {"aaa", "bbb", "ccc", "ddd"}) {
assertU(adoc("id", id, "name", "XX", "my_version_l", "10"));
assertJQ(req("qt","/get", "id",id, "fl","my_version_l")
, "=={'doc':{'my_version_l':"+1019+"}}");
}
assertU(commit());
assertJQ(req("q","name:XX"), "/response/numFound==0");
for (String id : new String[] {"aaa", "bbb", "ccc", "ddd"}) {
assertJQ(req("q","+id:"+id), "/response/numFound==1");
assertJQ(req("q","+name:XX +id:"+id), "/response/numFound==0");
assertJQ(req("q","+id:"+id + " +my_version_l:1019"), "/response/numFound==1");
assertJQ(req("qt","/get", "id",id, "fl","my_version_l")
, "=={'doc':{'my_version_l':"+1019+"}}");
}
}
public void testSimpleDeletes() throws Exception {
// skip low version delete against committed doc
assertU(adoc("id", "aaa", "name", "a1", "my_version_l", "1001"));
assertU(commit());
assertU(adoc("id", "aaa", "name", "a2", "my_version_l", "1002"));
assertU(commit());
deleteAndGetVersion("aaa",
params("del_version", "7"));
assertJQ(req("qt","/get", "id","aaa", "fl","name")
, "=={'doc':{'name':'a2'}}");
assertU(commit());
assertJQ(req("q","+id:aaa"), "/response/numFound==1");
assertJQ(req("q","+id:aaa +name:a2"), "/response/numFound==1");
assertJQ(req("qt","/get", "id","aaa", "fl","name")
, "=={'doc':{'name':'a2'}}");
// skip low version delete against uncommitted doc from updateLog
assertU(adoc("id", "aaa", "name", "a3", "my_version_l", "1003"));
deleteAndGetVersion("aaa",
params("del_version", "8"));
assertJQ(req("qt","/get", "id","aaa", "fl","name")
, "=={'doc':{'name':'a3'}}");
assertU(commit());
assertJQ(req("q","+id:aaa"), "/response/numFound==1");
assertJQ(req("q","+id:aaa +name:a3"), "/response/numFound==1");
assertJQ(req("qt","/get", "id","aaa", "fl","name")
, "=={'doc':{'name':'a3'}}");
// skip low version add against uncommitted "delete" from updateLog
deleteAndGetVersion("aaa", params("del_version", "1010"));
assertU(adoc("id", "aaa", "name", "XX", "my_version_l", "22"));
assertJQ(req("qt","/get", "id","aaa", "fl","my_version_l")
, "=={'doc':{'my_version_l':1010}}}");
assertU(commit());
assertJQ(req("q","+id:aaa"), "/response/numFound==1");
assertJQ(req("q","+id:aaa +name:XX"), "/response/numFound==0");
assertJQ(req("qt","/get", "id","aaa", "fl","my_version_l")
, "=={'doc':{'my_version_l':1010}}");
// skip low version add against committed "delete"
// (delete was already done & committed above)
assertU(adoc("id", "aaa", "name", "XX", "my_version_l", "23"));
assertJQ(req("qt","/get", "id","aaa", "fl","my_version_l")
, "=={'doc':{'my_version_l':1010}}}");
assertU(commit());
assertJQ(req("q","+id:aaa"), "/response/numFound==1");
assertJQ(req("q","+id:aaa +name:XX"), "/response/numFound==0");
assertJQ(req("qt","/get", "id","aaa", "fl","my_version_l")
, "=={'doc':{'my_version_l':1010}}");
}
/**
* Sanity check that there are no hardcoded assumptions about the
* field type used that could byte us in the ass.
*/
public void testFloatVersionField() throws Exception {
// skip low version add & low version delete against committed doc
updateJ(jsonAdd(sdoc("id", "aaa", "name", "a1", "my_version_f", "10.01")),
params("update.chain","external-version-float"));
assertU(commit());
updateJ(jsonAdd(sdoc("id", "aaa", "name", "XX", "my_version_f", "4.2")),
params("update.chain","external-version-float"));
assertU(commit());
assertJQ(req("qt","/get", "id","aaa", "fl","name")
, "=={'doc':{'name':'a1'}}");
deleteAndGetVersion("aaa", params("del_version", "7",
"update.chain","external-version-float"));
assertJQ(req("qt","/get", "id","aaa", "fl","name")
, "=={'doc':{'name':'a1'}}");
assertU(commit());
// skip low version delete against uncommitted doc from updateLog
updateJ(jsonAdd(sdoc("id", "aaa", "name", "a2", "my_version_f", "10.02")),
params("update.chain","external-version-float"));
deleteAndGetVersion("aaa", params("del_version", "8",
"update.chain","external-version-float"));
assertJQ(req("qt","/get", "id","aaa", "fl","name")
, "=={'doc':{'name':'a2'}}");
assertU(commit());
assertJQ(req("q","+id:aaa"), "/response/numFound==1");
assertJQ(req("q","+id:aaa +name:a2"), "/response/numFound==1");
assertJQ(req("qt","/get", "id","aaa", "fl","name")
, "=={'doc':{'name':'a2'}}");
// skip low version add against uncommitted "delete" from updateLog
deleteAndGetVersion("aaa", params("del_version", "10.10",
"update.chain","external-version-float"));
updateJ(jsonAdd(sdoc("id", "aaa", "name", "XX", "my_version_f", "10.05")),
params("update.chain","external-version-float"));
assertJQ(req("qt","/get", "id","aaa", "fl","my_version_f")
, "=={'doc':{'my_version_f':10.10}}}");
assertU(commit());
assertJQ(req("q","+id:aaa"), "/response/numFound==1");
assertJQ(req("q","+id:aaa +name:XX"), "/response/numFound==0");
assertJQ(req("qt","/get", "id","aaa", "fl","my_version_f")
, "=={'doc':{'my_version_f':10.10}}");
// skip low version add against committed "delete"
// (delete was already done & committed above)
updateJ(jsonAdd(sdoc("id", "aaa", "name", "XX", "my_version_f", "10.09")),
params("update.chain","external-version-float"));
assertJQ(req("qt","/get", "id","aaa", "fl","my_version_f")
, "=={'doc':{'my_version_f':10.10}}}");
assertU(commit());
assertJQ(req("q","+id:aaa"), "/response/numFound==1");
assertJQ(req("q","+id:aaa +name:XX"), "/response/numFound==0");
assertJQ(req("qt","/get", "id","aaa", "fl","my_version_f")
, "=={'doc':{'my_version_f':10.10}}");
}
public void testFailOnOldVersion() throws Exception {
// fail low version add & low version delete against committed doc
updateJ(jsonAdd(sdoc("id", "aaa", "name", "a1", "my_version_l", "1001")),
params("update.chain","external-version-failhard"));
assertU(commit());
SolrException ex = expectThrows(SolrException.class, () -> {
updateJ(jsonAdd(sdoc("id", "aaa", "name", "XX", "my_version_l", "42")),
params("update.chain","external-version-failhard"));
});
assertEquals(409, ex.code());
assertU(commit());
assertJQ(req("qt","/get", "id","aaa", "fl","name")
, "=={'doc':{'name':'a1'}}");
ex = expectThrows(SolrException.class, () -> {
deleteAndGetVersion("aaa", params("del_version", "7",
"update.chain","external-version-failhard"));
});
assertEquals(409, ex.code());
assertJQ(req("qt","/get", "id","aaa", "fl","name")
, "=={'doc':{'name':'a1'}}");
assertU(commit());
// fail low version delete against uncommitted doc from updateLog
updateJ(jsonAdd(sdoc("id", "aaa", "name", "a2", "my_version_l", "1002")),
params("update.chain","external-version-failhard"));
ex = expectThrows(SolrException.class, () -> {
deleteAndGetVersion("aaa", params("del_version", "8",
"update.chain","external-version-failhard"));
});
assertEquals(409, ex.code());
assertJQ(req("qt","/get", "id","aaa", "fl","name")
, "=={'doc':{'name':'a2'}}");
assertU(commit());
assertJQ(req("q","+id:aaa"), "/response/numFound==1");
assertJQ(req("q","+id:aaa +name:a2"), "/response/numFound==1");
assertJQ(req("qt","/get", "id","aaa", "fl","name")
, "=={'doc':{'name':'a2'}}");
// fail low version add against uncommitted "delete" from updateLog
deleteAndGetVersion("aaa", params("del_version", "1010",
"update.chain","external-version-failhard"));
ex = expectThrows(SolrException.class, () -> {
updateJ(jsonAdd(sdoc("id", "aaa", "name", "XX", "my_version_l", "1005")),
params("update.chain","external-version-failhard"));
});
assertEquals(409, ex.code());
assertJQ(req("qt","/get", "id","aaa", "fl","my_version_l")
, "=={'doc':{'my_version_l':1010}}}");
assertU(commit());
assertJQ(req("q","+id:aaa"), "/response/numFound==1");
assertJQ(req("q","+id:aaa +name:XX"), "/response/numFound==0");
assertJQ(req("qt","/get", "id","aaa", "fl","my_version_l")
, "=={'doc':{'my_version_l':1010}}");
// fail low version add against committed "delete"
// (delete was already done & committed above)
ex = expectThrows(SolrException.class, () -> {
updateJ(jsonAdd(sdoc("id", "aaa", "name", "XX", "my_version_l", "1009")),
params("update.chain","external-version-failhard"));
});
assertEquals(409, ex.code());
assertJQ(req("qt","/get", "id","aaa", "fl","my_version_l")
, "=={'doc':{'my_version_l':1010}}}");
assertU(commit());
assertJQ(req("q","+id:aaa"), "/response/numFound==1");
assertJQ(req("q","+id:aaa +name:XX"), "/response/numFound==0");
assertJQ(req("qt","/get", "id","aaa", "fl","my_version_l")
, "=={'doc':{'my_version_l':1010}}");
}
// Test multiple versions, that it has to be greater than my_version_l and my_version_f
public void testMultipleVersions() throws Exception {
updateJ(jsonAdd(sdoc("id", "aaa", "name", "a1", "my_version_l", "1001", "my_version_f", "1.0")),
params("update.chain","external-version-failhard-multiple"));
assertU(commit());
// All variations of additional versions should fail other than my_version_l greater or my_version_f greater.
SolrException ex = expectThrows(SolrException.class, () -> {
updateJ(jsonAdd(sdoc("id", "aaa", "name", "X1", "my_version_l", "1000", "my_version_f", "1.0")),
params("update.chain","external-version-failhard-multiple"));
});
assertEquals(409, ex.code());
ex = expectThrows(SolrException.class, () -> {
updateJ(jsonAdd(sdoc("id", "aaa", "name", "X2", "my_version_l", "1001", "my_version_f", "0.9")),
params("update.chain","external-version-failhard-multiple"));
});
assertEquals(409, ex.code());
// Also fails on the exact same version
ex = expectThrows(SolrException.class, () -> {
updateJ(jsonAdd(sdoc("id", "aaa", "name", "X3", "my_version_l", "1001", "my_version_f", "1.0")),
params("update.chain","external-version-failhard-multiple"));
});
assertEquals(409, ex.code());
//Verify we are still unchanged
assertU(commit());
assertJQ(req("q","+id:aaa +name:a1"), "/response/numFound==1");
// update version 1
updateJ(jsonAdd(sdoc("id", "aaa", "name", "Y1", "my_version_l", "2001", "my_version_f", "1.0")),
params("update.chain","external-version-failhard-multiple"));
assertU(commit());
assertJQ(req("q","+id:aaa +name:Y1"), "/response/numFound==1");
// update version 2
updateJ(jsonAdd(sdoc("id", "aaa", "name", "Y2", "my_version_l", "2001", "my_version_f", "2.0")),
params("update.chain","external-version-failhard-multiple"));
assertU(commit());
assertJQ(req("q","+id:aaa +name:Y2"), "/response/numFound==1");
}
public void testMultipleVersionDeletes() throws Exception {
updateJ(jsonAdd(sdoc("id", "aaa", "name", "a1", "my_version_l", "1001", "my_version_f", "1.0")),
params("update.chain","external-version-failhard-multiple"));
assertU(commit());
SolrException ex = expectThrows(SolrException.class, () -> {
deleteAndGetVersion("aaa", params("del_version", "1000", "del_version_2", "1.0",
"update.chain","external-version-failhard-multiple"));
});
assertEquals(409, ex.code());
ex = expectThrows(SolrException.class, () -> {
deleteAndGetVersion("aaa", params("del_version", "1001", "del_version_2", "0.9",
"update.chain","external-version-failhard-multiple"));
});
assertEquals(409, ex.code());
// And just verify if we pass version 1, we still error if version 2 isn't found.
ignoreException("Delete by ID must specify doc version param");
ex = expectThrows(SolrException.class, () -> {
deleteAndGetVersion("aaa", params("del_version", "1001",
"update.chain","external-version-failhard-multiple"));
});
assertEquals(400, ex.code());
unIgnoreException("Delete by ID must specify doc version param");
//Verify we are still unchanged
assertU(commit());
assertJQ(req("q","+id:aaa +name:a1"), "/response/numFound==1");
//And let's verify the actual case.
deleteAndGetVersion("aaa", params("del_version", "1001", "del_version_2", "2.0",
"update.chain","external-version-failhard-multiple"));
assertU(commit());
assertJQ(req("q","+id:aaa +name:a1"), "/response/numFound==0"); //Delete allowed
}
/**
* Proof of concept test demonstrating how to manage and periodically cleanup
* the "logically" deleted documents
*/
public void testManagingDeletes() throws Exception {
// add some docs
for (long ver = 1010; ver < 1020; ver++) {
for (String id : new String[] {"aaa", "bbb", "ccc", "ddd"}) {
assertU(adoc("id", id, "name", "name_"+id, "my_version_l", ""+ver));
}
}
assertU(adoc("id", "aaa", "name", "name_aaa", "my_version_l", "1030"));
assertU(commit());
// sample queries
assertJQ(req("q","*:*",
"fq","live_b:true")
,"/response/numFound==4");
assertJQ(req("q","id:aaa",
"fq","live_b:true",
"fl","id,my_version_l")
,"/response/numFound==1"
,"/response/docs==[{'id':'aaa','my_version_l':1030}]}");
// logically delete
deleteAndGetVersion("aaa",
params("del_version", "1031"));
assertU(commit());
// sample queries
assertJQ(req("q","*:*",
"fq","live_b:true")
,"/response/numFound==3");
assertJQ(req("q","id:aaa",
"fq","live_b:true")
,"/response/numFound==0");
// placeholder doc is still in the index though
assertJQ(req("q","id:aaa",
"fq","live_b:false",
"fq", "timestamp_tdt:[* TO *]",
"fl","id,live_b,my_version_l")
,"/response/numFound==1"
,"/response/docs==[{'id':'aaa','my_version_l':1031,'live_b':false}]}");
// doc can't be re-added with a low version
assertU(adoc("id", "aaa", "name", "XX", "my_version_l", "1025"));
assertU(commit());
assertJQ(req("q","id:aaa",
"fq","live_b:true")
,"/response/numFound==0");
// "dead" placeholder docs can be periodically cleaned up
// ie: assertU(delQ("+live_b:false +timestamp_tdt:[* TO NOW/MINUTE-5MINUTE]"));
// but to prevent the test from ebing time sensitive we'll just purge them all
assertU(delQ("+live_b:false"));
assertU(commit());
// now doc can be re-added w/any version, no matter how low
assertU(adoc("id", "aaa", "name", "aaa", "my_version_l", "7"));
assertU(commit());
assertJQ(req("q","id:aaa",
"fq","live_b:true",
"fl","id,live_b,my_version_l")
,"/response/numFound==1"
,"/response/docs==[{'id':'aaa','my_version_l':7,'live_b':true}]}");
}
/**
* Constantly hammer the same doc with multiple concurrent threads and diff versions,
* confirm that the highest version wins.
*/
public void testConcurrentAdds() throws Exception {
final int NUM_DOCS = atLeast(50);
final int MAX_CONCURENT = atLeast(10);
ExecutorService runner = ExecutorUtil.newMDCAwareFixedThreadPool(MAX_CONCURENT, new SolrNamedThreadFactory("TestDocBasedVersionConstraints"));
// runner = Executors.newFixedThreadPool(1); // to test single threaded
try {
for (int id = 0; id < NUM_DOCS; id++) {
final int numAdds = TestUtil.nextInt(random(), 3, MAX_CONCURENT);
final int winner = TestUtil.nextInt(random(), 0, numAdds - 1);
final int winnerVersion = atLeast(100);
final boolean winnerIsDeleted = (0 == TestUtil.nextInt(random(), 0, 4));
List<Callable<Object>> tasks = new ArrayList<>(numAdds);
for (int variant = 0; variant < numAdds; variant++) {
final boolean iShouldWin = (variant==winner);
final long version = (iShouldWin ? winnerVersion
: TestUtil.nextInt(random(), 1, winnerVersion - 1));
if ((iShouldWin && winnerIsDeleted)
|| (!iShouldWin && 0 == TestUtil.nextInt(random(), 0, 4))) {
tasks.add(delayedDelete(""+id, ""+version));
} else {
tasks.add(delayedAdd("id",""+id,"name","name"+id+"_"+variant,
"my_version_l", ""+ version));
}
}
runner.invokeAll(tasks);
final String expectedDoc = "{'id':'"+id+"','my_version_l':"+winnerVersion +
( ! winnerIsDeleted ? ",'name':'name"+id+"_"+winner+"'}" : "}");
assertJQ(req("qt","/get", "id",""+id, "fl","id,name,my_version_l")
, "=={'doc':" + expectedDoc + "}");
assertU(commit());
assertJQ(req("q","id:"+id,
"fl","id,name,my_version_l")
,"/response/numFound==1"
,"/response/docs==["+expectedDoc+"]");
}
} finally {
ExecutorUtil.shutdownAndAwaitTermination(runner);
}
}
public void testMissingVersionOnOldDocs() throws Exception {
String version = "2";
// Write one doc with version, one doc without version using the "no version" chain
updateJ(json("[{\"id\": \"a\", \"name\": \"a1\", \"my_version_l\": " + version + "}]"),
params("update.chain", "no-external-version"));
updateJ(json("[{\"id\": \"b\", \"name\": \"b1\"}]"), params("update.chain", "no-external-version"));
assertU(commit());
assertJQ(req("q","*:*"), "/response/numFound==2");
assertJQ(req("q","id:a"), "/response/numFound==1");
assertJQ(req("q","id:b"), "/response/numFound==1");
// Try updating both with a new version and using the enforced version chain, expect id=b to fail bc old
// doc is missing the version field
String newVersion = "3";
updateJ(json("[{\"id\": \"a\", \"name\": \"a1\", \"my_version_l\": " + newVersion + "}]"),
params("update.chain", "external-version-constraint"));
ignoreException("Doc exists in index, but has null versionField: my_version_l");
SolrException ex = expectThrows(SolrException.class, () -> {
updateJ(json("[{\"id\": \"b\", \"name\": \"b1\", \"my_version_l\": " + newVersion + "}]"),
params("update.chain", "external-version-constraint"));
});
assertEquals("Doc exists in index, but has null versionField: my_version_l", ex.getMessage());
unIgnoreException("Doc exists in index, but has null versionField: my_version_l");
assertU(commit());
assertJQ(req("q","*:*"), "/response/numFound==2");
assertJQ(req("qt","/get", "id", "a", "fl", "id,my_version_l"), "=={'doc':{'id':'a', 'my_version_l':3}}"); // version changed to 3
assertJQ(req("qt","/get", "id", "b", "fl", "id,my_version_l"), "=={'doc':{'id':'b'}}"); // no version, because update failed
// Try to update again using the external version enforcement, but allowing old docs to not have the version
// field. Expect id=a to fail because version is lower, expect id=b to succeed.
version = "1";
updateJ(json("[{\"id\": \"a\", \"name\": \"a1\", \"my_version_l\": " + version + "}]"),
params("update.chain", "external-version-support-missing"));
System.out.println("send b");
updateJ(json("[{\"id\": \"b\", \"name\": \"b1\", \"my_version_l\": " + version + "}]"),
params("update.chain", "external-version-support-missing"));
assertU(commit());
assertJQ(req("q","*:*"), "/response/numFound==2");
assertJQ(req("qt","/get", "id", "a", "fl", "id,my_version_l"), "=={'doc':{'id':'a', 'my_version_l':3}}");
assertJQ(req("qt","/get", "id", "b", "fl", "id,my_version_l"), "=={'doc':{'id':'b', 'my_version_l':1}}");
}
public void testTombstoneConfig() throws Exception {
assertJQ(req("q","*:*"),"/response/numFound==0");
updateWithChain("tombstone-config",
"id", "b!doc1",
"my_version_l", "1");
assertU(commit());
assertJQ(req("q","*:*"),"/response/numFound==1");
assertJQ(req("q","foo_b:true"),"/response/numFound==0");
assertJQ(req("q","foo_i:1"),"/response/numFound==0");
assertJQ(req("q","foo_l:1"),"/response/numFound==0");
assertJQ(req("q","foo_f:1.5"),"/response/numFound==0");
assertJQ(req("q","foo_s:bar"),"/response/numFound==0");
assertJQ(req("q","foo_ss:bar1"),"/response/numFound==0");
assertJQ(req("q","foo_ss:bar2"),"/response/numFound==0");
deleteAndGetVersion("b!doc1",
params("del_version", "2", "update.chain",
"tombstone-config"));
assertU(commit());
assertJQ(req("q","foo_b:true"),"/response/numFound==1");
assertJQ(req("q","foo_i:1"),"/response/numFound==1");
assertJQ(req("q","foo_l:1"),"/response/numFound==1");
assertJQ(req("q","foo_f:1.5"),"/response/numFound==1");
assertJQ(req("q","foo_s:bar"),"/response/numFound==1");
assertJQ(req("q","foo_ss:bar1"),"/response/numFound==1");
assertJQ(req("q","foo_ss:bar2"),"/response/numFound==1");
}
public void testCanCreateTombstonesBasic() {
DocBasedVersionConstraintsProcessorFactory factory = new DocBasedVersionConstraintsProcessorFactory();
NamedList<Object> config = new NamedList<>();
config.add("versionField", "_version_");
factory.init(config);
IndexSchema schema = h.getCore().getLatestSchema();
assertThat(factory.canCreateTombstoneDocument(schema), is(true));
}
public void testCanCreateTombstonesMissingRequiredField() {
DocBasedVersionConstraintsProcessorFactory factory = new DocBasedVersionConstraintsProcessorFactory();
NamedList<Object> config = new NamedList<>();
config.add("versionField", "_version_");
factory.init(config);
IndexSchema schema = h.getCore().getLatestSchema();
SchemaField sf = schema.getField("sku1");
assertThat(sf, is(not(nullValue())));
assertThat(schema.getRequiredFields(), not(hasItem(sf)));
try {
schema.getRequiredFields().add(sf);
assertThat(factory.canCreateTombstoneDocument(schema), is(false));
} finally {
schema.getRequiredFields().remove(sf);
}
}
public void testCanCreateTombstonesRequiredFieldWithDefault() {
DocBasedVersionConstraintsProcessorFactory factory = new DocBasedVersionConstraintsProcessorFactory();
NamedList<Object> config = new NamedList<>();
config.add("versionField", "_version_");
factory.init(config);
IndexSchema schema = h.getCore().getLatestSchema();
SchemaField sf = schema.getField("sku1");
SchemaField sf2 = new SchemaField("sku1_with_default", sf.getType(), sf.getProperties(), "foo");
try {
schema.getRequiredFields().add(sf2);
assertThat(factory.canCreateTombstoneDocument(schema), is(true));
} finally {
schema.getRequiredFields().remove(sf2);
}
}
public void testCanCreateTombstonesRequiredFieldInTombstoneConfig() {
DocBasedVersionConstraintsProcessorFactory factory = new DocBasedVersionConstraintsProcessorFactory();
NamedList<Object> config = new NamedList<>();
config.add("versionField", "_version_");
NamedList<Object> tombstoneConfig = new NamedList<>();
config.add("tombstoneConfig", tombstoneConfig);
tombstoneConfig.add("sku1", "foo");
factory.init(config);
IndexSchema schema = h.getCore().getLatestSchema();
SchemaField sf = schema.getField("sku1");
assertThat(sf, is(not(nullValue())));
assertThat(schema.getRequiredFields(), not(hasItem(sf)));
try {
schema.getRequiredFields().add(sf);
assertThat(factory.canCreateTombstoneDocument(schema), is(true));
} finally {
schema.getRequiredFields().remove(sf);
}
}
public void testCanCreateTombstonesVersionFieldRequired() {
DocBasedVersionConstraintsProcessorFactory factory = new DocBasedVersionConstraintsProcessorFactory();
NamedList<Object> config = new NamedList<>();
config.add("versionField", "_version_");
factory.init(config);
IndexSchema schema = h.getCore().getLatestSchema();
SchemaField versionField = schema.getField("_version_");
assertThat(versionField, is(not(nullValue())));
assertThat(schema.getRequiredFields(), not(hasItem(versionField)));
try {
schema.getRequiredFields().add(versionField);
assertThat(factory.canCreateTombstoneDocument(schema), is(true));
} finally {
schema.getRequiredFields().remove(versionField);
}
}
public void testCanCreateTombstonesUniqueKeyFieldRequired() {
DocBasedVersionConstraintsProcessorFactory factory = new DocBasedVersionConstraintsProcessorFactory();
NamedList<Object> config = new NamedList<>();
config.add("versionField", "_version_");
factory.init(config);
IndexSchema schema = h.getCore().getLatestSchema();
SchemaField uniqueKeyField = schema.getField("id");
assertThat(uniqueKeyField, is(not(nullValue())));
assertThat(uniqueKeyField, equalTo(schema.getUniqueKeyField()));
assertThat(schema.getRequiredFields(), hasItem(schema.getUniqueKeyField()));
assertThat(factory.canCreateTombstoneDocument(schema), is(true));
}
private void updateWithChain(String chain, String...fields) throws Exception {
assert fields.length % 2 == 0;
SolrInputDocument doc = new SolrInputDocument(fields);
updateJ(jsonAdd(doc), params("update.chain", chain));
}
private Callable<Object> delayedAdd(final String... fields) {
return Executors.callable(() -> {
// log.info("ADDING DOC: " + adoc(fields));
assertU(adoc(fields));
});
}
private Callable<Object> delayedDelete(final String id, final String externalVersion) {
return Executors.callable(() -> {
try {
// Why does this throw "Exception" ???
// log.info("DELETING DOC: " + id + " v="+externalVersion);
deleteAndGetVersion(id, params("del_version", externalVersion));
} catch (Exception e) {
throw new RuntimeException(e);
}
});
}
}