lucene-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From dsmi...@apache.org
Subject lucene-solr:master: SOLR-5211: Always populate _root_ (if defined). And, small refactor: Clarified how _version_ is transferred from root to children.
Date Mon, 26 Nov 2018 02:47:58 GMT
Repository: lucene-solr
Updated Branches:
  refs/heads/master d7b878e90 -> a346ba0d3


SOLR-5211:  Always populate _root_ (if defined).
And, small refactor: Clarified how _version_ is transferred from root to children.


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

Branch: refs/heads/master
Commit: a346ba0d3c371ec9f314c21fe67afeca64846cf0
Parents: d7b878e
Author: Moshe <moshebla@mail.com>
Authored: Sun Nov 25 21:47:49 2018 -0500
Committer: David Smiley <dsmiley@apache.org>
Committed: Sun Nov 25 21:47:49 2018 -0500

----------------------------------------------------------------------
 solr/CHANGES.txt                                |   8 ++
 .../apache/solr/update/AddUpdateCommand.java    |  31 +++--
 .../solr/update/DirectUpdateHandler2.java       |  23 ++--
 .../solr/cloud/TestCloudPseudoReturnFields.java |   8 +-
 .../apache/solr/cloud/TestRandomFlRTGCloud.java |   4 +
 .../apache/solr/handler/tagger/TaggerTest.java  |   6 +-
 .../solr/search/TestPseudoReturnFields.java     |   9 +-
 .../test/org/apache/solr/search/TestReload.java |   6 +-
 .../org/apache/solr/update/RootFieldTest.java   | 125 +++++++++++++++++++
 .../update/processor/AtomicUpdatesTest.java     |  18 +--
 .../solr/client/solrj/SolrExampleTests.java     |  91 ++++++++++++++
 11 files changed, 289 insertions(+), 40 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/a346ba0d/solr/CHANGES.txt
----------------------------------------------------------------------
diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt
index 9e63d66..1b5e1ad 100644
--- a/solr/CHANGES.txt
+++ b/solr/CHANGES.txt
@@ -72,6 +72,14 @@ New Features
 
 * SOLR-12593: The default configSet now includes an "ignored_*" dynamic field.  (David Smiley)
 
+Improvements
+----------------------
+
+* SOLR-5211: If _root_ is defined in the schema, it is now always populated automatically.
 This allows documents with
+ children to be updated with a document that does not have children, whereas before it would
break block-join queries.
+ If you don't use nested documents then _root_ can be removed from the schema.  (Dr Oleg
Savrasov, Moshe Bla,
+ David Smiley, Mikhail Khludnev)
+
 Optimizations
 ----------------------
 

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/a346ba0d/solr/core/src/java/org/apache/solr/update/AddUpdateCommand.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/update/AddUpdateCommand.java b/solr/core/src/java/org/apache/solr/update/AddUpdateCommand.java
index cfa937e..b644f73 100644
--- a/solr/core/src/java/org/apache/solr/update/AddUpdateCommand.java
+++ b/solr/core/src/java/org/apache/solr/update/AddUpdateCommand.java
@@ -95,11 +95,14 @@ public class AddUpdateCommand extends UpdateCommand {
    * Nested documents, if found, will cause an exception to be thrown.  Call {@link #getLuceneDocsIfNested()}
for that.
    * Any changes made to the returned Document will not be reflected in the SolrInputDocument,
or future calls to this
    * method.
-   * Note that the behavior of this is sensitive to {@link #isInPlaceUpdate()}.
-   */
+   * Note that the behavior of this is sensitive to {@link #isInPlaceUpdate()}.*/
    public Document getLuceneDocument() {
      final boolean ignoreNestedDocs = false; // throw an exception if found
-     return DocumentBuilder.toDocument(getSolrInputDocument(), req.getSchema(), isInPlaceUpdate(),
ignoreNestedDocs);
+     SolrInputDocument solrInputDocument = getSolrInputDocument();
+     if (!isInPlaceUpdate() && getReq().getSchema().isUsableForChildDocs()) {
+       addRootField(solrInputDocument, getHashableId());
+     }
+     return DocumentBuilder.toDocument(solrInputDocument, req.getSchema(), isInPlaceUpdate(),
ignoreNestedDocs);
    }
 
   /** Returns the indexed ID for this document.  The returned BytesRef is retained across
multiple calls, and should not be modified. */
@@ -194,13 +197,14 @@ public class AddUpdateCommand extends UpdateCommand {
       return null; // caller should call getLuceneDocument() instead
     }
 
-    String rootId = getHashableId();
-
-    boolean isVersion = version != 0;
+    final String rootId = getHashableId();
+    final SolrInputField versionSif = solrDoc.get(CommonParams.VERSION_FIELD);
 
     for (SolrInputDocument sdoc : all) {
-      sdoc.setField(IndexSchema.ROOT_FIELD_NAME, rootId);
-      if(isVersion) sdoc.setField(CommonParams.VERSION_FIELD, version);
+      addRootField(sdoc, rootId);
+      if (versionSif != null) {
+        addVersionField(sdoc, versionSif);
+      }
       // TODO: if possible concurrent modification exception (if SolrInputDocument not cloned
and is being forwarded to replicas)
       // then we could add this field to the generated lucene document instead.
     }
@@ -208,6 +212,17 @@ public class AddUpdateCommand extends UpdateCommand {
     return () -> all.stream().map(sdoc -> DocumentBuilder.toDocument(sdoc, req.getSchema())).iterator();
   }
 
+  private void addRootField(SolrInputDocument sdoc, String rootId) {
+    sdoc.setField(IndexSchema.ROOT_FIELD_NAME, rootId);
+  }
+
+  private void addVersionField(SolrInputDocument sdoc, SolrInputField versionSif) {
+    // Reordered delete-by-query assumes all documents have a version, see SOLR-10114
+    // all docs in hierarchy should have the same version.
+    // Either fetch the version from the root doc or compute it and propagate it.
+    sdoc.put(CommonParams.VERSION_FIELD, versionSif);
+  }
+
   private List<SolrInputDocument> flatten(SolrInputDocument root) {
     List<SolrInputDocument> unwrappedDocs = new ArrayList<>();
     flattenAnonymous(unwrappedDocs, root, true);

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/a346ba0d/solr/core/src/java/org/apache/solr/update/DirectUpdateHandler2.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/update/DirectUpdateHandler2.java b/solr/core/src/java/org/apache/solr/update/DirectUpdateHandler2.java
index e64ee8a..660df06 100644
--- a/solr/core/src/java/org/apache/solr/update/DirectUpdateHandler2.java
+++ b/solr/core/src/java/org/apache/solr/update/DirectUpdateHandler2.java
@@ -42,6 +42,7 @@ import org.apache.lucene.search.MatchAllDocsQuery;
 import org.apache.lucene.search.Query;
 import org.apache.lucene.search.TermQuery;
 import org.apache.lucene.store.AlreadyClosedException;
+import org.apache.lucene.util.BytesRef;
 import org.apache.lucene.util.BytesRefHash;
 import org.apache.solr.cloud.ZkController;
 import org.apache.solr.common.SolrException;
@@ -319,9 +320,9 @@ public class DirectUpdateHandler2 extends UpdateHandler implements SolrCoreState
     RefCounted<IndexWriter> iw = solrCoreState.getIndexWriter(core);
     try {
       IndexWriter writer = iw.get();
-      Iterable<Document> blockDocs = cmd.getLuceneDocsIfNested();
-      if (blockDocs != null) {
-        writer.addDocuments(blockDocs);
+      Iterable<Document> nestedDocs = cmd.getLuceneDocsIfNested();
+      if (nestedDocs != null) {
+        writer.addDocuments(nestedDocs);
       } else {
         writer.addDocument(cmd.getLuceneDocument());
       }
@@ -425,7 +426,7 @@ public class DirectUpdateHandler2 extends UpdateHandler implements SolrCoreState
       return;
     }
 
-    Term deleteTerm = new Term(idField.getName(), cmd.getIndexedId());
+    Term deleteTerm = getIdTerm(cmd.getIndexedId(), false);
     // SolrCore.verbose("deleteDocuments",deleteTerm,writer);
     RefCounted<IndexWriter> iw = solrCoreState.getIndexWriter(core);
     try {
@@ -951,13 +952,13 @@ public class DirectUpdateHandler2 extends UpdateHandler implements SolrCoreState
 
     } else { // more normal path
 
-      Iterable<Document> blockDocs = cmd.getLuceneDocsIfNested();
-      boolean isBlock = blockDocs != null; // AKA nested child docs
-      Term idTerm = new Term(isBlock ? IndexSchema.ROOT_FIELD_NAME : idField.getName(), cmd.getIndexedId());
+      Iterable<Document> nestedDocs = cmd.getLuceneDocsIfNested();
+      boolean isNested = nestedDocs != null; // AKA nested child docs
+      Term idTerm = getIdTerm(cmd.getIndexedId(), isNested);
       Term updateTerm = hasUpdateTerm ? cmd.updateTerm : idTerm;
-      if (isBlock) {
+      if (isNested) {
         log.debug("updateDocuments({})", cmd);
-        writer.updateDocuments(updateTerm, blockDocs);
+        writer.updateDocuments(updateTerm, nestedDocs);
       } else {
         Document luceneDocument = cmd.getLuceneDocument();
         log.debug("updateDocument({})", cmd);
@@ -975,6 +976,10 @@ public class DirectUpdateHandler2 extends UpdateHandler implements SolrCoreState
     }
   }
 
+  private Term getIdTerm(BytesRef indexedId, boolean isNested) {
+    boolean useRootId = isNested || core.getLatestSchema().isUsableForChildDocs();
+    return new Term(useRootId ? IndexSchema.ROOT_FIELD_NAME : idField.getName(), indexedId);
+  }
 
   /////////////////////////////////////////////////////////////////////
   // SolrInfoBean stuff: Statistics and Module Info

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/a346ba0d/solr/core/src/test/org/apache/solr/cloud/TestCloudPseudoReturnFields.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/cloud/TestCloudPseudoReturnFields.java b/solr/core/src/test/org/apache/solr/cloud/TestCloudPseudoReturnFields.java
index f7e6756..31d69cf 100644
--- a/solr/core/src/test/org/apache/solr/cloud/TestCloudPseudoReturnFields.java
+++ b/solr/core/src/test/org/apache/solr/cloud/TestCloudPseudoReturnFields.java
@@ -194,7 +194,7 @@ public class TestCloudPseudoReturnFields extends SolrCloudTestCase {
       SolrDocumentList docs = assertSearch(params("q", "*:*", "rows", "10", "fl",fl));
       // shouldn't matter what doc we pick...
       for (SolrDocument doc : docs) {
-        assertEquals(fl + " => " + doc, 4, doc.size());
+        assertEquals(fl + " => " + doc, 5, doc.size());
         assertTrue(fl + " => " + doc, doc.getFieldValue("id") instanceof String);
         assertTrue(fl + " => " + doc, doc.getFieldValue("val_i") instanceof Integer);
         assertTrue(fl + " => " + doc, doc.getFieldValue("subject") instanceof String);
@@ -208,7 +208,7 @@ public class TestCloudPseudoReturnFields extends SolrCloudTestCase {
     for (String fl : TestPseudoReturnFields.ALL_REAL_FIELDS) {
       for (int i : Arrays.asList(42, 43, 44, 45, 46, 99)) {
         SolrDocument doc = getRandClient(random()).getById(""+i, params("fl",fl));
-        assertEquals(fl + " => " + doc, 4, doc.size());
+        assertEquals(fl + " => " + doc, 5, doc.size());
         assertTrue(fl + " => " + doc, doc.getFieldValue("id") instanceof String);
         assertTrue(fl + " => " + doc, doc.getFieldValue("val_i") instanceof Integer);
         assertTrue(fl + " => " + doc, doc.getFieldValue("subject") instanceof String);
@@ -238,7 +238,7 @@ public class TestCloudPseudoReturnFields extends SolrCloudTestCase {
       SolrDocumentList docs = assertSearch(params("q", "*:*", "rows", "10", "fl",fl));
       // shouldn't matter what doc we pick...
       for (SolrDocument doc : docs) {
-        assertEquals(fl + " => " + doc, 5, doc.size());
+        assertEquals(fl + " => " + doc, 6, doc.size());
         assertTrue(fl + " => " + doc, doc.getFieldValue("id") instanceof String);
         assertTrue(fl + " => " + doc, doc.getFieldValue("score") instanceof Float);
         assertTrue(fl + " => " + doc, doc.getFieldValue("val_i") instanceof Integer);
@@ -253,7 +253,7 @@ public class TestCloudPseudoReturnFields extends SolrCloudTestCase {
     for (String fl : TestPseudoReturnFields.SCORE_AND_REAL_FIELDS) {
       for (int i : Arrays.asList(42, 43, 44, 45, 46, 99)) {
         SolrDocument doc = getRandClient(random()).getById(""+i, params("fl",fl));
-        assertEquals(fl + " => " + doc, 4, doc.size());
+        assertEquals(fl + " => " + doc, 5, doc.size());
         assertTrue(fl + " => " + doc, doc.getFieldValue("id") instanceof String);
         assertTrue(fl + " => " + doc, doc.getFieldValue("val_i") instanceof Integer);
         assertTrue(fl + " => " + doc, doc.getFieldValue("subject") instanceof String);

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/a346ba0d/solr/core/src/test/org/apache/solr/cloud/TestRandomFlRTGCloud.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/cloud/TestRandomFlRTGCloud.java b/solr/core/src/test/org/apache/solr/cloud/TestRandomFlRTGCloud.java
index 5a9db8f..6969883 100644
--- a/solr/core/src/test/org/apache/solr/cloud/TestRandomFlRTGCloud.java
+++ b/solr/core/src/test/org/apache/solr/cloud/TestRandomFlRTGCloud.java
@@ -70,6 +70,9 @@ public class TestRandomFlRTGCloud extends SolrCloudTestCase {
 
   /** Always included in fl so we can vet what doc we're looking at */
   private static final FlValidator ID_VALIDATOR = new SimpleFieldValueValidator("id");
+
+  /** Since nested documents are not tested, when _root_ is declared in schema, it is always
the same as id */
+  private static final FlValidator ROOT_VALIDATOR = new RenameFieldValueValidator("id" ,
"_root_");
   
   /** 
    * Types of things we will randomly ask for in fl param, and validate in response docs.
@@ -352,6 +355,7 @@ public class TestRandomFlRTGCloud extends SolrCloudTestCase {
     
     final Set<FlValidator> validators = new LinkedHashSet<>();
     validators.add(ID_VALIDATOR); // always include id so we can be confident which doc we're
looking at
+    validators.add(ROOT_VALIDATOR); // always added in a nested schema, with the same value
as id
     addRandomFlValidators(random(), validators);
     FlValidator.addParams(validators, params);
 

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/a346ba0d/solr/core/src/test/org/apache/solr/handler/tagger/TaggerTest.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/handler/tagger/TaggerTest.java b/solr/core/src/test/org/apache/solr/handler/tagger/TaggerTest.java
index 93b11b5..7a67b95 100644
--- a/solr/core/src/test/org/apache/solr/handler/tagger/TaggerTest.java
+++ b/solr/core/src/test/org/apache/solr/handler/tagger/TaggerTest.java
@@ -83,7 +83,8 @@ public class TaggerTest extends TaggerTestCase {
         "<result name=\"response\" numFound=\"1\" start=\"0\">\n" +
         "  <doc>\n" +
         "    <str name=\"id\">1</str>\n" +
-        "    <str name=\"name\">London Business School</str></doc>\n" +
+        "    <str name=\"name\">London Business School</str>\n" +
+        "    <str name=\"_root_\">1</str></doc>\n" +
         "</result>\n" +
         "</response>\n";
     assertEquals(expected, rspStr);
@@ -111,7 +112,8 @@ public class TaggerTest extends TaggerTestCase {
         "<result name=\"response\" numFound=\"1\" start=\"0\">\n" +
         "  <doc>\n" +
         "    <str name=\"id\">1</str>\n" +
-        "    <str name=\"name\">London Business School</str></doc>\n" +
+        "    <str name=\"name\">London Business School</str>\n" +
+        "    <str name=\"_root_\">1</str></doc>\n" +
         "</result>\n" +
         "</response>\n";
     assertEquals(expected, rspStr);

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/a346ba0d/solr/core/src/test/org/apache/solr/search/TestPseudoReturnFields.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/search/TestPseudoReturnFields.java b/solr/core/src/test/org/apache/solr/search/TestPseudoReturnFields.java
index 0a98734..1080354 100644
--- a/solr/core/src/test/org/apache/solr/search/TestPseudoReturnFields.java
+++ b/solr/core/src/test/org/apache/solr/search/TestPseudoReturnFields.java
@@ -126,7 +126,7 @@ public class TestPseudoReturnFields extends SolrTestCaseJ4 {
               ,"//result/doc/str[@name='ssto']"
               ,"//result/doc/str[@name='subject']"
               
-              ,"//result/doc[count(*)=4]"
+              ,"//result/doc[count(*)=5]"
               );
     }
   }
@@ -142,7 +142,7 @@ public class TestPseudoReturnFields extends SolrTestCaseJ4 {
                 ,"//doc/int[@name='val_i']"
                 ,"//doc/str[@name='ssto']"
                 ,"//doc/str[@name='subject']"
-                ,"//doc[count(*)=4]"
+                ,"//doc[count(*)=5]"
                 );
       }
     }
@@ -172,8 +172,7 @@ public class TestPseudoReturnFields extends SolrTestCaseJ4 {
               ,"//result/doc/str[@name='ssto']"
               ,"//result/doc/str[@name='subject']"
               ,"//result/doc/float[@name='score']"
-              
-              ,"//result/doc[count(*)=5]"
+              ,"//result/doc[count(*)=6]"
               );
     }
   }
@@ -190,7 +189,7 @@ public class TestPseudoReturnFields extends SolrTestCaseJ4 {
                 ,"//doc/int[@name='val_i']"
                 ,"//doc/str[@name='ssto']"
                 ,"//doc/str[@name='subject']"
-                ,"//doc[count(*)=4]"
+                ,"//doc[count(*)=5]"
                 );
       }
     }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/a346ba0d/solr/core/src/test/org/apache/solr/search/TestReload.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/search/TestReload.java b/solr/core/src/test/org/apache/solr/search/TestReload.java
index 872abec..13d78fc 100644
--- a/solr/core/src/test/org/apache/solr/search/TestReload.java
+++ b/solr/core/src/test/org/apache/solr/search/TestReload.java
@@ -36,13 +36,13 @@ public class TestReload extends TestRTGBase {
 
     assertU(commit("softCommit","true"));   // should cause a RTG searcher to be opened
 
-    assertJQ(req("qt","/get","id","1")
+    assertJQ(req("qt","/get","id","1", "fl", "id,_version_")
         ,"=={'doc':{'id':'1','_version_':" + version + "}}"
     );
 
     h.reload();
 
-    assertJQ(req("qt","/get","id","1")
+    assertJQ(req("qt","/get","id","1", "fl", "id,_version_")
         ,"=={'doc':{'id':'1','_version_':" + version + "}}"
     );
 
@@ -76,7 +76,7 @@ public class TestReload extends TestRTGBase {
       if (rand.nextBoolean()) {
         // RTG should always be able to see the last version
         // System.out.println("!!! rtg");
-        assertJQ(req("qt","/get","id","1")
+        assertJQ(req("qt","/get","id","1", "fl", "id,_version_")
             ,"=={'doc':{'id':'1','_version_':" + version + "}}"
         );
       }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/a346ba0d/solr/core/src/test/org/apache/solr/update/RootFieldTest.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/update/RootFieldTest.java b/solr/core/src/test/org/apache/solr/update/RootFieldTest.java
new file mode 100644
index 0000000..7d4ed08
--- /dev/null
+++ b/solr/core/src/test/org/apache/solr/update/RootFieldTest.java
@@ -0,0 +1,125 @@
+/*
+ * 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 java.util.List;
+
+import org.apache.solr.SolrJettyTestBase;
+import org.apache.solr.client.solrj.SolrClient;
+import org.apache.solr.client.solrj.SolrQuery;
+import org.apache.solr.common.SolrDocument;
+import org.apache.solr.common.SolrDocumentList;
+import org.apache.solr.common.SolrException;
+import org.apache.solr.common.SolrInputDocument;
+import org.apache.solr.common.params.CommonParams;
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+import static org.hamcrest.CoreMatchers.is;
+
+public class RootFieldTest extends SolrJettyTestBase {
+  private static boolean useRootSchema;
+  private static final String MESSAGE = "Update handler should create and process _root_
field " +
+      "unless there is no such a field in schema";
+
+  @Rule
+  public ExpectedException thrown = ExpectedException.none();
+
+  private static boolean expectRoot() {
+    return useRootSchema;
+  }
+
+  @BeforeClass
+  public static void beforeTest() throws Exception {
+    useRootSchema = random().nextBoolean();
+    // schema.xml declares _root_ field while schema11.xml does not.
+    String schema = useRootSchema ? "schema.xml" : "schema11.xml";
+    initCore("solrconfig.xml", schema);
+  }
+
+  @Test
+  public void testLegacyBlockProcessing() throws Exception
+  {
+    SolrClient client = getSolrClient();
+    client.deleteByQuery("*:*");// delete everything!
+
+    // Add child free doc
+    SolrInputDocument docToUpdate = new SolrInputDocument();
+    String docId = "11";
+    docToUpdate.addField( "id", docId);
+    docToUpdate.addField( "name", "child free doc" );
+    client.add(docToUpdate);
+    client.commit();
+
+    SolrQuery query = new SolrQuery();
+    query.setQuery( "*:*" );
+    query.set( CommonParams.FL, "id,name,_root_" );
+
+    SolrDocumentList results = client.query(query).getResults();
+    assertThat(results.getNumFound(), is(1L));
+    SolrDocument foundDoc = results.get( 0 );
+
+    // Check retrieved field values
+    assertThat(foundDoc.getFieldValue( "id" ), is(docId));
+    assertThat( ((List)foundDoc.getFieldValue( "name" )).get(0), is("child free doc"));
+
+    String expectedRootValue = expectRoot() ? docId : null;
+    assertThat(MESSAGE, foundDoc.getFieldValue( "_root_" ), is(expectedRootValue));
+
+    // Update the doc
+    docToUpdate.setField( "name", "updated doc" );
+    client.add(docToUpdate);
+    client.commit();
+
+    results = client.query(query).getResults();
+    assertEquals( 1, results.getNumFound() );
+    foundDoc = results.get( 0 );
+
+    // Check updated field values
+    assertThat(foundDoc.getFieldValue( "id" ), is(docId));
+    assertThat( ((List)foundDoc.getFieldValue( "name" )).get(0), is("updated doc"));
+    assertThat(MESSAGE, foundDoc.getFieldValue( "_root_" ), is(expectedRootValue));
+  }
+
+  @Test
+  public void testUpdateWithChildDocs() throws Exception {
+    SolrClient client = getSolrClient();
+    client.deleteByQuery("*:*");// delete everything!
+
+    // Add child free doc
+    SolrInputDocument docToUpdate = new SolrInputDocument();
+    String docId = "11";
+    docToUpdate.addField( "id", docId);
+    docToUpdate.addField( "name", "parent doc with a child" );
+    SolrInputDocument child = new SolrInputDocument();
+    child.addField("id", "111");
+    child.addField("name", "child doc");
+    docToUpdate.addChildDocument(child);
+    if (!useRootSchema) {
+      thrown.expect(SolrException.class);
+      thrown.expectMessage("Unable to index docs with children:" +
+          " the schema must include definitions for both a uniqueKey field" +
+          " and the '_root_' field, using the exact same fieldType");
+    }
+    client.add(docToUpdate);
+    client.commit();
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/a346ba0d/solr/core/src/test/org/apache/solr/update/processor/AtomicUpdatesTest.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/update/processor/AtomicUpdatesTest.java b/solr/core/src/test/org/apache/solr/update/processor/AtomicUpdatesTest.java
index 43a84e6..f72fd67 100644
--- a/solr/core/src/test/org/apache/solr/update/processor/AtomicUpdatesTest.java
+++ b/solr/core/src/test/org/apache/solr/update/processor/AtomicUpdatesTest.java
@@ -1215,7 +1215,7 @@ public class AtomicUpdatesTest extends SolrTestCaseJ4 {
               , "//doc/long[@name='_version_']"
               , "//doc/date[@name='timestamp']"
               , "//doc/arr[@name='multiDefault']/str[.='muLti-Default']"
-              , "count(//doc/*)=7"
+              , "count(//doc/*)=8"
               );
 
       // do atomic update
@@ -1230,7 +1230,7 @@ public class AtomicUpdatesTest extends SolrTestCaseJ4 {
               , "//doc/long[@name='_version_']"
               , "//doc/date[@name='timestamp']"
               , "//doc/arr[@name='multiDefault']/str[.='muLti-Default']"
-              , "count(//doc/*)=7"
+              , "count(//doc/*)=8"
               );
 
       assertU(commit());
@@ -1244,7 +1244,7 @@ public class AtomicUpdatesTest extends SolrTestCaseJ4 {
               , "//doc/long[@name='_version_']"
               , "//doc/date[@name='timestamp']"
               , "//doc/arr[@name='multiDefault']/str[.='muLti-Default']"
-              , "count(//doc/*)=7"
+              , "count(//doc/*)=8"
               );
     }
     
@@ -1267,7 +1267,7 @@ public class AtomicUpdatesTest extends SolrTestCaseJ4 {
               , "//doc/long[@name='_version_']"
               , "//doc/date[@name='timestamp']"
               , "//doc/arr[@name='multiDefault']/str[.='muLti-Default']"
-              , "count(//doc/*)=6"
+              , "count(//doc/*)=7"
               );
       // do atomic update
       assertU(adoc(sdoc("id", "7", fieldToUpdate, ImmutableMap.of("inc", -555))));
@@ -1281,7 +1281,7 @@ public class AtomicUpdatesTest extends SolrTestCaseJ4 {
               , "//doc/long[@name='_version_']"
               , "//doc/date[@name='timestamp']"
               , "//doc/arr[@name='multiDefault']/str[.='muLti-Default']"
-              , "count(//doc/*)=6"
+              , "count(//doc/*)=7"
               );
 
       // diff doc where we check that we can overwrite the default value
@@ -1296,7 +1296,7 @@ public class AtomicUpdatesTest extends SolrTestCaseJ4 {
               , "//doc/long[@name='_version_']"
               , "//doc/date[@name='timestamp']"
               , "//doc/arr[@name='multiDefault']/str[.='muLti-Default']"
-              , "count(//doc/*)=6"
+              , "count(//doc/*)=7"
               );
       // do atomic update
       assertU(adoc(sdoc("id", "8", fieldToUpdate, ImmutableMap.of("inc", -555))));
@@ -1310,7 +1310,7 @@ public class AtomicUpdatesTest extends SolrTestCaseJ4 {
               , "//doc/long[@name='_version_']"
               , "//doc/date[@name='timestamp']"
               , "//doc/arr[@name='multiDefault']/str[.='muLti-Default']"
-              , "count(//doc/*)=6"
+              , "count(//doc/*)=7"
               );
       
       assertU(commit());
@@ -1325,7 +1325,7 @@ public class AtomicUpdatesTest extends SolrTestCaseJ4 {
               , "//doc/long[@name='_version_']"
               , "//doc/date[@name='timestamp']"
               , "//doc/arr[@name='multiDefault']/str[.='muLti-Default']"
-              , "count(//doc/*)=6"
+              , "count(//doc/*)=7"
               );
       assertQ(fieldToUpdate + ": doc8 post commit RTG"
               , req("qt", "/get", "id", "8")
@@ -1337,7 +1337,7 @@ public class AtomicUpdatesTest extends SolrTestCaseJ4 {
               , "//doc/long[@name='_version_']"
               , "//doc/date[@name='timestamp']"
               , "//doc/arr[@name='multiDefault']/str[.='muLti-Default']"
-              , "count(//doc/*)=6"
+              , "count(//doc/*)=7"
               );
     }
     

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/a346ba0d/solr/solrj/src/test/org/apache/solr/client/solrj/SolrExampleTests.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/test/org/apache/solr/client/solrj/SolrExampleTests.java b/solr/solrj/src/test/org/apache/solr/client/solrj/SolrExampleTests.java
index b83be83..807757c 100644
--- a/solr/solrj/src/test/org/apache/solr/client/solrj/SolrExampleTests.java
+++ b/solr/solrj/src/test/org/apache/solr/client/solrj/SolrExampleTests.java
@@ -69,6 +69,7 @@ import org.apache.solr.common.params.FacetParams;
 import org.apache.solr.common.params.ModifiableSolrParams;
 import org.apache.solr.common.util.NamedList;
 import org.apache.solr.common.util.Pair;
+import org.junit.Before;
 import org.junit.Test;
 import org.noggit.JSONParser;
 import org.slf4j.Logger;
@@ -76,6 +77,7 @@ import org.slf4j.LoggerFactory;
 
 import static org.apache.solr.common.params.UpdateParams.ASSUME_CONTENT_TYPE;
 import static org.junit.internal.matchers.StringContains.containsString;
+import static org.hamcrest.CoreMatchers.is;
 
 /**
  * This should include tests against the example solr config
@@ -93,6 +95,15 @@ abstract public class SolrExampleTests extends SolrExampleTestsBase
   static {
     ignoreException("uniqueKey");
   }
+
+  @Before
+  public void emptyCollection() throws Exception {
+    SolrClient client = getSolrClient();
+    // delete everything!
+    client.deleteByQuery("*:*");
+    client.commit();
+  }
+
   /**
    * query the example
    */
@@ -2179,4 +2190,84 @@ abstract public class SolrExampleTests extends SolrExampleTestsBase
     }
     return sdoc;
   }
+
+  @Test
+  public void testAddChildToChildFreeDoc() throws IOException, SolrServerException, IllegalArgumentException,
IllegalAccessException, SecurityException, NoSuchFieldException {
+    SolrClient client = getSolrClient();
+    client.deleteByQuery("*:*");
+
+    SolrInputDocument docToUpdate = new SolrInputDocument();
+    docToUpdate.addField("id", "p0");
+    docToUpdate.addField("title_s", "i am a child free doc");
+    client.add(docToUpdate);
+    client.commit();
+
+    SolrQuery q = new SolrQuery("*:*");
+    q.set( CommonParams.FL, "id,title_s" );
+    q.addSort("id", SolrQuery.ORDER.desc);
+
+    SolrDocumentList results = client.query(q).getResults();
+    assertThat(results.getNumFound(), is(1L));
+    SolrDocument foundDoc = results.get(0);
+    assertThat(foundDoc.getFieldValue("title_s"), is("i am a child free doc"));
+
+    // Rewrite child free doc
+    docToUpdate.setField("title_s", "i am a parent");
+
+    SolrInputDocument child = new SolrInputDocument();
+    child.addField("id", "c0");
+    child.addField("title_s", "i am a child");
+
+    docToUpdate.addChildDocument(child);
+
+    client.add(docToUpdate);
+    client.commit();
+
+    results = client.query(q).getResults();
+
+    assertThat(results.getNumFound(), is(2L));
+    foundDoc = results.get(0);
+    assertThat(foundDoc.getFieldValue("title_s"), is("i am a parent"));
+    foundDoc = results.get(1);
+    assertThat(foundDoc.getFieldValue("title_s"), is("i am a child"));
+  }
+
+  @Test
+  public void testDeleteParentDoc() throws IOException, SolrServerException, IllegalArgumentException,
IllegalAccessException, SecurityException, NoSuchFieldException {
+    SolrClient client = getSolrClient();
+    client.deleteByQuery("*:*");
+
+    SolrInputDocument docToDelete = new SolrInputDocument();
+    docToDelete.addField("id", "p0");
+    docToDelete.addField("title_s", "parent doc");
+
+    SolrInputDocument child = new SolrInputDocument();
+    child.addField("id", "c0");
+    child.addField("title_s", "i am a child 0");
+    docToDelete.addChildDocument(child);
+
+    child = new SolrInputDocument();
+    child.addField("id", "c1");
+    child.addField("title_s", "i am a child 1");
+    docToDelete.addChildDocument(child);
+
+    child = new SolrInputDocument();
+    child.addField("id", "c2");
+    child.addField("title_s", "i am a child 2");
+    docToDelete.addChildDocument(child);
+
+    client.add(docToDelete);
+    client.commit();
+
+    SolrQuery q = new SolrQuery("*:*");
+    SolrDocumentList results = client.query(q).getResults();
+    assertThat(results.getNumFound(), is(4L));
+
+    client.deleteById("p0");
+    client.commit();
+
+    results = client.query(q).getResults();
+    assertThat("All the children are expected to be deleted together with parent",
+        results.getNumFound(), is(0L));
+  }
 }


Mime
View raw message