| /* |
| * 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; |
| |
| import javax.xml.parsers.DocumentBuilderFactory; |
| import javax.xml.parsers.ParserConfigurationException; |
| import javax.xml.stream.XMLInputFactory; |
| import javax.xml.stream.XMLStreamException; |
| import javax.xml.stream.XMLStreamReader; |
| import javax.xml.transform.Transformer; |
| import javax.xml.transform.TransformerException; |
| import javax.xml.transform.TransformerFactory; |
| import javax.xml.transform.dom.DOMSource; |
| import javax.xml.transform.stream.StreamResult; |
| import java.io.ByteArrayInputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.OutputStream; |
| import java.io.StringReader; |
| import java.io.StringWriter; |
| import java.lang.invoke.MethodHandles; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Locale; |
| import java.util.Map; |
| import java.util.concurrent.Callable; |
| import java.util.concurrent.ExecutorService; |
| import java.util.concurrent.Future; |
| import java.util.concurrent.atomic.AtomicInteger; |
| |
| import org.apache.commons.io.output.ByteArrayOutputStream; |
| import org.apache.lucene.index.Term; |
| import org.apache.lucene.search.TermQuery; |
| import org.apache.lucene.search.TermRangeQuery; |
| import org.apache.lucene.search.TopDocs; |
| import org.apache.lucene.search.join.QueryBitSetProducer; |
| import org.apache.lucene.search.join.ScoreMode; |
| import org.apache.lucene.search.join.ToParentBlockJoinQuery; |
| import org.apache.solr.SolrTestCaseJ4; |
| import org.apache.solr.client.solrj.request.RequestWriter; |
| import org.apache.solr.client.solrj.request.UpdateRequest; |
| import org.apache.solr.common.SolrException; |
| import org.apache.solr.common.SolrInputDocument; |
| import org.apache.solr.common.params.ModifiableSolrParams; |
| import org.apache.solr.common.util.ExecutorUtil; |
| import org.apache.solr.common.util.JavaBinCodec; |
| import org.apache.solr.handler.loader.XMLLoader; |
| import org.apache.solr.request.LocalSolrQueryRequest; |
| import org.apache.solr.request.SolrQueryRequest; |
| import org.apache.solr.search.SolrIndexSearcher; |
| import org.apache.solr.common.util.SolrNamedThreadFactory; |
| import org.apache.solr.util.RefCounted; |
| import org.junit.After; |
| import org.junit.AfterClass; |
| import org.junit.Before; |
| import org.junit.BeforeClass; |
| import org.junit.Rule; |
| import org.junit.Test; |
| import org.junit.rules.ExpectedException; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| import org.w3c.dom.Document; |
| import org.w3c.dom.Element; |
| import org.w3c.dom.Node; |
| import org.w3c.dom.NodeList; |
| import org.xml.sax.SAXException; |
| |
| public class AddBlockUpdateTest extends SolrTestCaseJ4 { |
| private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); |
| |
| private static final String child = "child_s"; |
| private static final String parent = "parent_s"; |
| private static final String type = "type_s"; |
| |
| private final static AtomicInteger counter = new AtomicInteger(); |
| private static ExecutorService exe; |
| private static boolean cachedMode; |
| |
| private static XMLInputFactory inputFactory; |
| |
| private RefCounted<SolrIndexSearcher> searcherRef; |
| private SolrIndexSearcher _searcher; |
| |
| @Rule |
| public ExpectedException thrown = ExpectedException.none(); |
| |
| @BeforeClass |
| public static void beforeClass() throws Exception { |
| String oldCacheNamePropValue = System |
| .getProperty("blockJoinParentFilterCache"); |
| System.setProperty("blockJoinParentFilterCache", (cachedMode = random() |
| .nextBoolean()) ? "blockJoinParentFilterCache" : "don't cache"); |
| if (oldCacheNamePropValue != null) { |
| System.setProperty("blockJoinParentFilterCache", oldCacheNamePropValue); |
| } |
| inputFactory = XMLInputFactory.newInstance(); |
| |
| exe = // Executors.newSingleThreadExecutor(); |
| rarely() ? ExecutorUtil.newMDCAwareFixedThreadPool(atLeast(2), new SolrNamedThreadFactory("AddBlockUpdateTest")) : ExecutorUtil |
| .newMDCAwareCachedThreadPool(new SolrNamedThreadFactory("AddBlockUpdateTest")); |
| |
| counter.set(0); |
| initCore("solrconfig.xml", "schema15.xml"); |
| } |
| |
| @Before |
| public void prepare() { |
| // assertU("<rollback/>"); |
| assertU(delQ("*:*")); |
| assertU(commit("expungeDeletes", "true")); |
| } |
| |
| private Document getDocument() throws ParserConfigurationException { |
| DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance(); |
| javax.xml.parsers.DocumentBuilder docBuilder = docFactory.newDocumentBuilder(); |
| return docBuilder.newDocument(); |
| } |
| |
| private SolrIndexSearcher getSearcher() { |
| if (_searcher == null) { |
| searcherRef = h.getCore().getSearcher(); |
| _searcher = searcherRef.get(); |
| } |
| return _searcher; |
| } |
| |
| @After |
| public void cleanup() { |
| if (searcherRef != null || _searcher != null) { |
| searcherRef.decref(); |
| searcherRef = null; |
| _searcher = null; |
| } |
| } |
| |
| @AfterClass |
| public static void afterClass() throws Exception { |
| if (null != exe) { |
| exe.shutdownNow(); |
| exe = null; |
| } |
| inputFactory = null; |
| } |
| |
| @Test |
| public void testOverwrite() throws IOException{ |
| assertU(add( |
| nest(doc("id","X", parent, "X"), |
| doc(child,"a", "id", "66"), |
| doc(child,"b", "id", "66")))); |
| assertU(add( |
| nest(doc("id","Y", parent, "Y"), |
| doc(child,"a", "id", "66"), |
| doc(child,"b", "id", "66")))); |
| String overwritten = random().nextBoolean() ? "X": "Y"; |
| String dubbed = overwritten.equals("X") ? "Y":"X"; |
| |
| assertU(add( |
| nest(doc("id",overwritten, parent, overwritten), |
| doc(child,"c","id", "66"), |
| doc(child,"d","id", "66")), "overwrite", "true")); |
| assertU(add( |
| nest(doc("id",dubbed, parent, dubbed), |
| doc(child,"c","id", "66"), |
| doc(child,"d","id", "66")), "overwrite", "false")); |
| |
| assertU(commit()); |
| |
| assertQ(req(parent+":"+overwritten, "//*[@numFound='1']")); |
| assertQ(req(parent+":"+dubbed, "//*[@numFound='2']")); |
| |
| final SolrIndexSearcher searcher = getSearcher(); |
| assertSingleParentOf(searcher, one("ab"), dubbed); |
| |
| final TopDocs docs = searcher.search(join(one("cd")), 10); |
| assertEquals(2, docs.totalHits.value); |
| final String pAct = searcher.doc(docs.scoreDocs[0].doc).get(parent)+ |
| searcher.doc(docs.scoreDocs[1].doc).get(parent); |
| assertTrue(pAct.contains(dubbed) && pAct.contains(overwritten) && pAct.length()==2); |
| |
| assertQ(req("id:66", "//*[@numFound='6']")); |
| assertQ(req(child+":(a b)", "//*[@numFound='2']")); |
| assertQ(req(child+":(c d)", "//*[@numFound='4']")); |
| } |
| |
| private static XmlDoc nest(XmlDoc parent, XmlDoc ... children){ |
| XmlDoc xmlDoc = new XmlDoc(); |
| xmlDoc.xml = parent.xml.replace("</doc>", |
| Arrays.toString(children).replaceAll("[\\[\\]]", "")+"</doc>"); |
| return xmlDoc; |
| } |
| |
| @Test |
| public void testBasics() throws Exception { |
| List<Document> blocks = new ArrayList<>(Arrays.asList( |
| block("abcD"), |
| block("efgH"), |
| merge(block("ijkL"), block("mnoP")), |
| merge(block("qrsT"), block("uvwX")), |
| block("Y"), |
| block("Z"))); |
| |
| Collections.shuffle(blocks, random()); |
| |
| log.trace("{}", blocks); |
| |
| for (Future<Void> f : exe.invokeAll(callables(blocks))) { |
| f.get(); // exceptions? |
| } |
| |
| assertU(commit()); |
| |
| final SolrIndexSearcher searcher = getSearcher(); |
| // final String resp = h.query(req("q","*:*", "sort","_docid_ asc", "rows", |
| // "10000")); |
| // log.trace(resp); |
| int parentsNum = "DHLPTXYZ".length(); |
| assertQ(req(parent + ":[* TO *]"), "//*[@numFound='" + parentsNum + "']"); |
| assertQ(req(child + ":[* TO *]"), "//*[@numFound='" |
| + (('z' - 'a' + 1) - parentsNum) + "']"); |
| assertQ(req("*:*"), "//*[@numFound='" + ('z' - 'a' + 1) + "']"); |
| assertSingleParentOf(searcher, one("abc"), "D"); |
| assertSingleParentOf(searcher, one("efg"), "H"); |
| assertSingleParentOf(searcher, one("ijk"), "L"); |
| assertSingleParentOf(searcher, one("mno"), "P"); |
| assertSingleParentOf(searcher, one("qrs"), "T"); |
| assertSingleParentOf(searcher, one("uvw"), "X"); |
| |
| assertQ(req("q",child+":(a b c)", "sort","_docid_ asc"), |
| "//*[@numFound='3']", // assert physical order of children |
| "//doc[1]/arr[@name='child_s']/str[text()='a']", |
| "//doc[2]/arr[@name='child_s']/str[text()='b']", |
| "//doc[3]/arr[@name='child_s']/str[text()='c']"); |
| } |
| |
| @Test |
| public void testExceptionThrown() throws Exception { |
| final String abcD = getStringFromDocument(block("abcD")); |
| log.info(abcD); |
| assertBlockU(abcD); |
| |
| Document docToFail = getDocument(); |
| Element root = docToFail.createElement("add"); |
| docToFail.appendChild(root); |
| Element doc1 = docToFail.createElement("doc"); |
| root.appendChild(doc1); |
| attachField(docToFail, doc1, "id", id()); |
| attachField(docToFail, doc1, parent, "Y"); |
| attachField(docToFail, doc1, "sample_i", "notanumber/ignore_exception"); |
| Element subDoc1 = docToFail.createElement("doc"); |
| doc1.appendChild(subDoc1); |
| attachField(docToFail, subDoc1, "id", id()); |
| attachField(docToFail, subDoc1, child, "x"); |
| Element doc2 = docToFail.createElement("doc"); |
| root.appendChild(doc2); |
| attachField(docToFail, doc2, "id", id()); |
| attachField(docToFail, doc2, parent, "W"); |
| |
| assertFailedBlockU(getStringFromDocument(docToFail)); |
| |
| assertBlockU(getStringFromDocument(block("efgH"))); |
| assertBlockU(commit()); |
| |
| final SolrIndexSearcher searcher = getSearcher(); |
| assertQ(req("q","*:*","indent","true", "fl","id,parent_s,child_s"), "//*[@numFound='" + "abcDefgH".length() + "']"); |
| assertSingleParentOf(searcher, one("abc"), "D"); |
| assertSingleParentOf(searcher, one("efg"), "H"); |
| |
| assertQ(req(child + ":x"), "//*[@numFound='0']"); |
| assertQ(req(parent + ":Y"), "//*[@numFound='0']"); |
| assertQ(req(parent + ":W"), "//*[@numFound='0']"); |
| } |
| |
| @Test |
| public void testExceptionThrownChildDocWAnonymousChildren() throws Exception { |
| SolrInputDocument document1 = sdoc("id", id(), parent, "X", |
| "child1_s", sdoc("id", id(), "child_s", "y"), |
| "child2_s", sdoc("id", id(), "child_s", "z")); |
| |
| SolrInputDocument exceptionChildDoc = (SolrInputDocument) document1.get("child1_s").getValue(); |
| addChildren("child", exceptionChildDoc, 0, false); |
| |
| thrown.expect(SolrException.class); |
| final String expectedMessage = "Anonymous child docs can only hang from others or the root"; |
| thrown.expectMessage(expectedMessage); |
| indexSolrInputDocumentsDirectly(document1); |
| } |
| |
| @Test |
| public void testSolrNestedFieldsList() throws Exception { |
| final String id1 = id(); |
| List<SolrInputDocument> children1 = Arrays.asList(sdoc("id", id(), child, "y"), sdoc("id", id(), child, "z")); |
| |
| SolrInputDocument document1 = sdoc("id", id1, parent, "X", |
| "children", children1); |
| |
| final String id2 = id(); |
| List<SolrInputDocument> children2 = Arrays.asList(sdoc("id", id(), child, "b"), sdoc("id", id(), child, "c")); |
| |
| SolrInputDocument document2 = sdoc("id", id2, parent, "A", |
| "children", children2); |
| |
| indexSolrInputDocumentsDirectly(document1, document2); |
| |
| final SolrIndexSearcher searcher = getSearcher(); |
| assertJQ(req("q","*:*", |
| "fl","*", |
| "sort","id asc", |
| "wt","json"), |
| "/response/numFound==" + "XyzAbc".length()); |
| assertJQ(req("q",parent+":" + document2.getFieldValue(parent), |
| "fl","*", |
| "sort","id asc", |
| "wt","json"), |
| "/response/docs/[0]/id=='" + document2.getFieldValue("id") + "'"); |
| assertQ(req("q",child+":(y z b c)", "sort","_docid_ asc"), |
| "//*[@numFound='" + "yzbc".length() + "']", // assert physical order of children |
| "//doc[1]/arr[@name='child_s']/str[text()='y']", |
| "//doc[2]/arr[@name='child_s']/str[text()='z']", |
| "//doc[3]/arr[@name='child_s']/str[text()='b']", |
| "//doc[4]/arr[@name='child_s']/str[text()='c']"); |
| assertSingleParentOf(searcher, one("bc"), "A"); |
| assertSingleParentOf(searcher, one("yz"), "X"); |
| } |
| |
| @Test |
| public void testSolrNestedFieldsSingleVal() throws Exception { |
| SolrInputDocument document1 = sdoc("id", id(), parent, "X", |
| "child1_s", sdoc("id", id(), "child_s", "y"), |
| "child2_s", sdoc("id", id(), "child_s", "z")); |
| |
| SolrInputDocument document2 = sdoc("id", id(), parent, "A", |
| "child1_s", sdoc("id", id(), "child_s", "b"), |
| "child2_s", sdoc("id", id(), "child_s", "c")); |
| |
| indexSolrInputDocumentsDirectly(document1, document2); |
| |
| final SolrIndexSearcher searcher = getSearcher(); |
| assertJQ(req("q","*:*", |
| "fl","*", |
| "sort","id asc", |
| "wt","json"), |
| "/response/numFound==" + "XyzAbc".length()); |
| assertJQ(req("q",parent+":" + document2.getFieldValue(parent), |
| "fl","*", |
| "sort","id asc", |
| "wt","json"), |
| "/response/docs/[0]/id=='" + document2.getFieldValue("id") + "'"); |
| assertQ(req("q",child+":(y z b c)", "sort","_docid_ asc"), |
| "//*[@numFound='" + "yzbc".length() + "']", // assert physical order of children |
| "//doc[1]/arr[@name='child_s']/str[text()='y']", |
| "//doc[2]/arr[@name='child_s']/str[text()='z']", |
| "//doc[3]/arr[@name='child_s']/str[text()='b']", |
| "//doc[4]/arr[@name='child_s']/str[text()='c']"); |
| assertSingleParentOf(searcher, one("bc"), "A"); |
| assertSingleParentOf(searcher, one("yz"), "X"); |
| } |
| |
| @SuppressWarnings("serial") |
| @Test |
| public void testSolrJXML() throws Exception { |
| UpdateRequest req = new UpdateRequest(); |
| |
| List<SolrInputDocument> docs = new ArrayList<>(); |
| |
| SolrInputDocument document1 = new SolrInputDocument() { |
| { |
| final String id = id(); |
| addField("id", id); |
| addField("parent_s", "X"); |
| |
| ArrayList<SolrInputDocument> ch1 = new ArrayList<>( |
| Arrays.asList(new SolrInputDocument() { |
| { |
| addField("id", id()); |
| addField("child_s", "y"); |
| } |
| }, new SolrInputDocument() { |
| { |
| addField("id", id()); |
| addField("child_s", "z"); |
| } |
| })); |
| |
| Collections.shuffle(ch1, random()); |
| addChildDocuments(ch1); |
| } |
| }; |
| |
| SolrInputDocument document2 = new SolrInputDocument() { |
| { |
| final String id = id(); |
| addField("id", id); |
| addField("parent_s", "A"); |
| addChildDocument(new SolrInputDocument() { |
| { |
| addField("id", id()); |
| addField("child_s", "b"); |
| } |
| }); |
| addChildDocument(new SolrInputDocument() { |
| { |
| addField("id", id()); |
| addField("child_s", "c"); |
| } |
| }); |
| } |
| }; |
| |
| docs.add(document1); |
| docs.add(document2); |
| |
| Collections.shuffle(docs, random()); |
| req.add(docs); |
| |
| RequestWriter requestWriter = new RequestWriter(); |
| OutputStream os = new ByteArrayOutputStream(); |
| requestWriter.write(req, os); |
| assertBlockU(os.toString()); |
| assertU(commit()); |
| |
| assertJQ(req("q","*:*", |
| "fl","*", |
| "sort","id asc", |
| "wt","json"), |
| "/response/numFound==" + 6); |
| final SolrIndexSearcher searcher = getSearcher(); |
| assertSingleParentOf(searcher, one("yz"), "X"); |
| assertSingleParentOf(searcher, one("bc"), "A"); |
| } |
| //This is the same as testSolrJXML above but uses the XMLLoader |
| // to illustrate the structure of the XML documents |
| @Test |
| public void testXML() throws IOException, XMLStreamException { |
| UpdateRequest req = new UpdateRequest(); |
| |
| List<SolrInputDocument> docs = new ArrayList<>(); |
| |
| String xml_doc1 = |
| "<doc >" + |
| " <field name=\"id\">1</field>" + |
| " <field name=\"parent_s\">X</field>" + |
| "<doc> " + |
| " <field name=\"id\" >2</field>" + |
| " <field name=\"child_s\">y</field>" + |
| "</doc>"+ |
| "<doc> " + |
| " <field name=\"id\" >3</field>" + |
| " <field name=\"child_s\">z</field>" + |
| "</doc>"+ |
| "</doc>"; |
| |
| String xml_doc2 = |
| "<doc >" + |
| " <field name=\"id\">4</field>" + |
| " <field name=\"parent_s\">A</field>" + |
| "<doc> " + |
| " <field name=\"id\" >5</field>" + |
| " <field name=\"child_s\">b</field>" + |
| "</doc>"+ |
| "<doc> " + |
| " <field name=\"id\" >6</field>" + |
| " <field name=\"child_s\">c</field>" + |
| "</doc>"+ |
| "</doc>"; |
| |
| |
| XMLStreamReader parser = |
| inputFactory.createXMLStreamReader( new StringReader( xml_doc1 ) ); |
| parser.next(); // read the START document... |
| //null for the processor is all right here |
| XMLLoader loader = new XMLLoader(); |
| SolrInputDocument document1 = loader.readDoc( parser ); |
| |
| XMLStreamReader parser2 = |
| inputFactory.createXMLStreamReader( new StringReader( xml_doc2 ) ); |
| parser2.next(); // read the START document... |
| //null for the processor is all right here |
| //XMLLoader loader = new XMLLoader(); |
| SolrInputDocument document2 = loader.readDoc( parser2 ); |
| |
| docs.add(document1); |
| docs.add(document2); |
| |
| Collections.shuffle(docs, random()); |
| req.add(docs); |
| |
| RequestWriter requestWriter = new RequestWriter(); |
| OutputStream os = new ByteArrayOutputStream(); |
| requestWriter.write(req, os); |
| assertBlockU(os.toString()); |
| assertU(commit()); |
| |
| final SolrIndexSearcher searcher = getSearcher(); |
| assertSingleParentOf(searcher, one("yz"), "X"); |
| assertSingleParentOf(searcher, one("bc"), "A"); |
| } |
| |
| @Test |
| public void testXMLMultiLevelLabeledChildren() throws XMLStreamException { |
| String xml_doc1 = |
| "<doc >" + |
| " <field name=\"id\">1</field>" + |
| " <field name=\"empty_s\"></field>" + |
| " <field name=\"parent_s\">X</field>" + |
| " <field name=\"test\">" + |
| " <doc> " + |
| " <field name=\"id\" >2</field>" + |
| " <field name=\"child_s\">y</field>" + |
| " </doc>" + |
| " <doc> " + |
| " <field name=\"id\" >3</field>" + |
| " <field name=\"child_s\">z</field>" + |
| " </doc>" + |
| " </field> " + |
| "</doc>"; |
| |
| String xml_doc2 = |
| "<doc >" + |
| " <field name=\"id\">4</field>" + |
| " <field name=\"parent_s\">A</field>" + |
| " <field name=\"test\">" + |
| " <doc> " + |
| " <field name=\"id\" >5</field>" + |
| " <field name=\"child_s\">b</field>" + |
| " <field name=\"grandChild\">" + |
| " <doc> " + |
| " <field name=\"id\" >7</field>" + |
| " <field name=\"child_s\">d</field>" + |
| " </doc>" + |
| " </field>" + |
| " </doc>" + |
| " </field>" + |
| " <field name=\"test\">" + |
| " <doc> " + |
| " <field name=\"id\" >6</field>" + |
| " <field name=\"child_s\">c</field>" + |
| " </doc>" + |
| " </field> " + |
| "</doc>"; |
| |
| XMLStreamReader parser = |
| inputFactory.createXMLStreamReader(new StringReader(xml_doc1)); |
| parser.next(); // read the START document... |
| //null for the processor is all right here |
| XMLLoader loader = new XMLLoader(); |
| SolrInputDocument document1 = loader.readDoc(parser); |
| |
| XMLStreamReader parser2 = |
| inputFactory.createXMLStreamReader(new StringReader(xml_doc2)); |
| parser2.next(); // read the START document... |
| //null for the processor is all right here |
| //XMLLoader loader = new XMLLoader(); |
| SolrInputDocument document2 = loader.readDoc(parser2); |
| |
| assertFalse(document1.hasChildDocuments()); |
| assertEquals(document1.toString(), sdoc("id", "1", "empty_s", "", "parent_s", "X", "test", |
| sdocs(sdoc("id", "2", "child_s", "y"), sdoc("id", "3", "child_s", "z"))).toString()); |
| |
| assertFalse(document2.hasChildDocuments()); |
| assertEquals(document2.toString(), sdoc("id", "4", "parent_s", "A", "test", |
| sdocs(sdoc("id", "5", "child_s", "b", "grandChild", Collections.singleton(sdoc("id", "7", "child_s", "d"))), |
| sdoc("id", "6", "child_s", "c"))).toString()); |
| } |
| |
| @Test |
| public void testXMLLabeledChildren() throws IOException, XMLStreamException { |
| UpdateRequest req = new UpdateRequest(); |
| |
| List<SolrInputDocument> docs = new ArrayList<>(); |
| |
| String xml_doc1 = |
| "<doc >" + |
| " <field name=\"id\">1</field>" + |
| " <field name=\"empty_s\"></field>" + |
| " <field name=\"parent_s\">X</field>" + |
| " <field name=\"test\">" + |
| " <doc> " + |
| " <field name=\"id\" >2</field>" + |
| " <field name=\"child_s\">y</field>" + |
| " </doc>"+ |
| " <doc> " + |
| " <field name=\"id\" >3</field>" + |
| " <field name=\"child_s\">z</field>" + |
| " </doc>" + |
| " </field> " + |
| "</doc>"; |
| |
| String xml_doc2 = |
| "<doc >" + |
| " <field name=\"id\">4</field>" + |
| " <field name=\"parent_s\">A</field>" + |
| " <field name=\"test\">" + |
| " <doc> " + |
| " <field name=\"id\" >5</field>" + |
| " <field name=\"child_s\">b</field>" + |
| " </doc>"+ |
| " </field>" + |
| " <field name=\"test\">" + |
| " <doc> " + |
| " <field name=\"id\" >6</field>" + |
| " <field name=\"child_s\">c</field>" + |
| " </doc>" + |
| " </field> " + |
| "</doc>"; |
| |
| XMLStreamReader parser = |
| inputFactory.createXMLStreamReader( new StringReader( xml_doc1 ) ); |
| parser.next(); // read the START document... |
| //null for the processor is all right here |
| XMLLoader loader = new XMLLoader(); |
| SolrInputDocument document1 = loader.readDoc( parser ); |
| |
| XMLStreamReader parser2 = |
| inputFactory.createXMLStreamReader( new StringReader( xml_doc2 ) ); |
| parser2.next(); // read the START document... |
| //null for the processor is all right here |
| //XMLLoader loader = new XMLLoader(); |
| SolrInputDocument document2 = loader.readDoc( parser2 ); |
| |
| assertFalse(document1.hasChildDocuments()); |
| assertEquals(document1.toString(), sdoc("id", "1", "empty_s", "", "parent_s", "X", "test", |
| sdocs(sdoc("id", "2", "child_s", "y"), sdoc("id", "3", "child_s", "z"))).toString()); |
| |
| assertFalse(document2.hasChildDocuments()); |
| assertEquals(document2.toString(), sdoc("id", "4", "parent_s", "A", "test", |
| sdocs(sdoc("id", "5", "child_s", "b"), sdoc("id", "6", "child_s", "c"))).toString()); |
| |
| docs.add(document1); |
| docs.add(document2); |
| |
| Collections.shuffle(docs, random()); |
| req.add(docs); |
| |
| RequestWriter requestWriter = new RequestWriter(); |
| OutputStream os = new ByteArrayOutputStream(); |
| requestWriter.write(req, os); |
| assertBlockU(os.toString()); |
| assertU(commit()); |
| |
| final SolrIndexSearcher searcher = getSearcher(); |
| assertSingleParentOf(searcher, one("yz"), "X"); |
| assertSingleParentOf(searcher, one("bc"), "A"); |
| } |
| |
| @Test |
| public void testJavaBinCodecNestedRelation() throws IOException { |
| SolrInputDocument topDocument = new SolrInputDocument(); |
| topDocument.addField("parent_f1", "v1"); |
| topDocument.addField("parent_f2", "v2"); |
| |
| int childsNum = atLeast(10); |
| Map<String, SolrInputDocument> children = new HashMap<>(childsNum); |
| for(int i = 0; i < childsNum; ++i) { |
| SolrInputDocument child = new SolrInputDocument(); |
| child.addField("key", (i + 5) * atLeast(4)); |
| String childKey = String.format(Locale.ROOT, "child%d", i); |
| topDocument.addField(childKey, child); |
| children.put(childKey, child); |
| } |
| |
| ByteArrayOutputStream os = new ByteArrayOutputStream(); |
| try (JavaBinCodec jbc = new JavaBinCodec()) { |
| jbc.marshal(topDocument, os); |
| } |
| byte[] buffer = os.toByteArray(); |
| //now read the Object back |
| SolrInputDocument result; |
| try (JavaBinCodec jbc = new JavaBinCodec(); InputStream is = new ByteArrayInputStream(buffer)) { |
| result = (SolrInputDocument) jbc.unmarshal(is); |
| } |
| |
| assertTrue(compareSolrInputDocument(topDocument, result)); |
| } |
| |
| |
| @Test |
| public void testJavaBinCodec() throws IOException { //actually this test must be in other test class |
| SolrInputDocument topDocument = new SolrInputDocument(); |
| topDocument.addField("parent_f1", "v1"); |
| topDocument.addField("parent_f2", "v2"); |
| |
| int childsNum = atLeast(10); |
| for (int index = 0; index < childsNum; ++index) { |
| addChildren("child", topDocument, index, false); |
| } |
| |
| ByteArrayOutputStream os = new ByteArrayOutputStream(); |
| try (JavaBinCodec jbc = new JavaBinCodec()) { |
| jbc.marshal(topDocument, os); |
| } |
| byte[] buffer = os.toByteArray(); |
| //now read the Object back |
| SolrInputDocument result; |
| try (JavaBinCodec jbc = new JavaBinCodec(); InputStream is = new ByteArrayInputStream(buffer)) { |
| result = (SolrInputDocument) jbc.unmarshal(is); |
| } |
| assertEquals(2, result.size()); |
| assertEquals("v1", result.getFieldValue("parent_f1")); |
| assertEquals("v2", result.getFieldValue("parent_f2")); |
| |
| List<SolrInputDocument> resultChilds = result.getChildDocuments(); |
| int resultChildsSize = resultChilds == null ? 0 : resultChilds.size(); |
| assertEquals(childsNum, resultChildsSize); |
| |
| for (int childIndex = 0; childIndex < childsNum; ++childIndex) { |
| SolrInputDocument child = resultChilds.get(childIndex); |
| for (int fieldNum = 0; fieldNum < childIndex; ++fieldNum) { |
| assertEquals(childIndex + "value" + fieldNum, child.getFieldValue(childIndex + "child" + fieldNum)); |
| } |
| |
| List<SolrInputDocument> grandChilds = child.getChildDocuments(); |
| int grandChildsSize = grandChilds == null ? 0 : grandChilds.size(); |
| |
| assertEquals(childIndex * 2, grandChildsSize); |
| for (int grandIndex = 0; grandIndex < childIndex * 2; ++grandIndex) { |
| SolrInputDocument grandChild = grandChilds.get(grandIndex); |
| assertFalse(grandChild.hasChildDocuments()); |
| for (int fieldNum = 0; fieldNum < grandIndex; ++fieldNum) { |
| assertEquals(grandIndex + "value" + fieldNum, grandChild.getFieldValue(grandIndex + "grand" + fieldNum)); |
| } |
| } |
| } |
| } |
| |
| private void addChildren(String prefix, SolrInputDocument topDocument, int childIndex, boolean lastLevel) { |
| SolrInputDocument childDocument = new SolrInputDocument(); |
| for (int index = 0; index < childIndex; ++index) { |
| childDocument.addField(childIndex + prefix + index, childIndex + "value"+ index); |
| } |
| |
| if (!lastLevel) { |
| for (int i = 0; i < childIndex * 2; ++i) { |
| addChildren("grand", childDocument, i, true); |
| } |
| } |
| topDocument.addChildDocument(childDocument); |
| } |
| |
| /** |
| * on the given abcD it generates one parent doc, taking D from the tail and |
| * two subdocs relaitons ab and c uniq ids are supplied also |
| * |
| * <pre> |
| * {@code |
| * <add> |
| * <doc> |
| * <field name="parent_s">D</field> |
| * <doc> |
| * <field name="child_s">a</field> |
| * <field name="type_s">1</field> |
| * </doc> |
| * <doc> |
| * <field name="child_s">b</field> |
| * <field name="type_s">1</field> |
| * </doc> |
| * <doc> |
| * <field name="child_s">c</field> |
| * <field name="type_s">2</field> |
| * </doc> |
| * </doc> |
| * </add> |
| * } |
| * </pre> |
| * */ |
| private Document block(String string) throws ParserConfigurationException { |
| Document document = getDocument(); |
| Element root = document.createElement("add"); |
| document.appendChild(root); |
| Element doc = document.createElement("doc"); |
| root.appendChild(doc); |
| |
| if (string.length() > 0) { |
| // last character is a top parent |
| attachField(document, doc, parent, |
| String.valueOf(string.charAt(string.length() - 1))); |
| attachField(document, doc, "id", id()); |
| |
| // add subdocs |
| int type = 1; |
| for (int i = 0; i < string.length() - 1; i += 2) { |
| String relation = string.substring(i, |
| Math.min(i + 2, string.length() - 1)); |
| attachSubDocs(document, doc, relation, type); |
| type++; |
| } |
| } |
| |
| return document; |
| } |
| |
| private void attachSubDocs(Document document, Element parent, String relation, int typeValue) { |
| for (int j = 0; j < relation.length(); j++) { |
| Element doc = document.createElement("doc"); |
| parent.appendChild(doc); |
| attachField(document, doc, child, String.valueOf(relation.charAt(j))); |
| attachField(document, doc, "id", id()); |
| attachField(document, doc, type, String.valueOf(typeValue)); |
| } |
| } |
| |
| private void indexSolrInputDocumentsDirectly(SolrInputDocument ... docs) throws IOException { |
| SolrQueryRequest coreReq = new LocalSolrQueryRequest(h.getCore(), new ModifiableSolrParams()); |
| AddUpdateCommand updateCmd = new AddUpdateCommand(coreReq); |
| for (SolrInputDocument doc: docs) { |
| updateCmd.solrDoc = doc; |
| h.getCore().getUpdateHandler().addDoc(updateCmd); |
| updateCmd.clear(); |
| } |
| assertU(commit()); |
| } |
| |
| /** |
| * Merges two documents like |
| * |
| * <pre> |
| * {@code <add>...</add> + <add>...</add> = <add>... + ...</add>} |
| * </pre> |
| * |
| * @param doc1 |
| * first document |
| * @param doc2 |
| * second document |
| * @return merged document |
| */ |
| private Document merge(Document doc1, Document doc2) { |
| NodeList doc2ChildNodes = doc2.getDocumentElement().getChildNodes(); |
| for(int i = 0; i < doc2ChildNodes.getLength(); i++) { |
| Node doc2ChildNode = doc2ChildNodes.item(i); |
| doc1.getDocumentElement().appendChild(doc1.importNode(doc2ChildNode, true)); |
| doc2.getDocumentElement().removeChild(doc2ChildNode); |
| } |
| |
| return doc1; |
| } |
| |
| private void attachField(Document document, Element root, String fieldName, String value) { |
| Element field = document.createElement("field"); |
| field.setAttribute("name", fieldName); |
| field.setTextContent(value); |
| root.appendChild(field); |
| } |
| |
| private static String id() { |
| return "" + counter.incrementAndGet(); |
| } |
| |
| private String one(String string) { |
| return "" + string.charAt(random().nextInt(string.length())); |
| } |
| |
| protected void assertSingleParentOf(final SolrIndexSearcher searcher, |
| final String childTerm, String parentExp) throws IOException { |
| final TopDocs docs = searcher.search(join(childTerm), 10); |
| assertEquals(1, docs.totalHits.value); |
| final String pAct = searcher.doc(docs.scoreDocs[0].doc).get(parent); |
| assertEquals(parentExp, pAct); |
| } |
| |
| protected ToParentBlockJoinQuery join(final String childTerm) { |
| return new ToParentBlockJoinQuery( |
| new TermQuery(new Term(child, childTerm)), new QueryBitSetProducer( |
| new TermRangeQuery(parent, null, null, false, false)), ScoreMode.None); |
| } |
| |
| private Collection<? extends Callable<Void>> callables(List<Document> blocks) { |
| final List<Callable<Void>> rez = new ArrayList<>(); |
| for (Document block : blocks) { |
| final String msg = getStringFromDocument(block); |
| if (msg.length() > 0) { |
| rez.add(() -> { |
| assertBlockU(msg); |
| return null; |
| }); |
| if (rarely()) { |
| rez.add(() -> { |
| assertBlockU(commit()); |
| return null; |
| }); |
| } |
| } |
| } |
| return rez; |
| } |
| |
| private void assertBlockU(final String msg) { |
| assertBlockU(msg, "0"); |
| } |
| |
| private void assertFailedBlockU(final String msg) { |
| expectThrows(Exception.class, () -> assertBlockU(msg, "1")); |
| } |
| |
| private void assertBlockU(final String msg, String expected) { |
| try { |
| String res = h.checkUpdateStatus(msg, expected); |
| if (res != null) { |
| fail("update was not successful: " + res + " expected: " + expected); |
| } |
| } catch (SAXException e) { |
| throw new RuntimeException("Invalid XML", e); |
| } |
| } |
| |
| public static String getStringFromDocument(Document doc) { |
| try (StringWriter writer = new StringWriter()){ |
| TransformerFactory tf = TransformerFactory.newInstance(); |
| Transformer transformer = tf.newTransformer(); |
| transformer.transform(new DOMSource(doc), new StreamResult(writer)); |
| return writer.toString(); |
| } catch (TransformerException | IOException e) { |
| throw new IllegalStateException(e); |
| } |
| } |
| } |