diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentStore.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentStore.java index fea5a92..761822f 100644 --- a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentStore.java +++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentStore.java @@ -217,6 +217,23 @@ public interface DocumentStore { T createOrUpdate(Collection collection, UpdateOp update); /** + * Create or unconditionally update a number of documents. + * + * An implementation does not have to guarantee that all changes are applied + * atomically, together. In case of an exception (e.g. when a communication + * error occurs) only some changes may have been applied. Then it is the + * responsibility of the caller to check, which updateOps were applied and + * take appropriate action. + * + * @param the document type + * @param collection the collection + * @param updateOps the update operation list + * @return the list containing old documents or null values if they didn't exist + * before. + */ + List createOrUpdate(Collection collection, List updateOps); + + /** * Performs a conditional update (e.g. using * {@link UpdateOp.Condition.Type#EXISTS} and only updates the * document if the condition is true. The returned document is diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/memory/MemoryDocumentStore.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/memory/MemoryDocumentStore.java index 6d15c32..e613b89 100644 --- a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/memory/MemoryDocumentStore.java +++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/memory/MemoryDocumentStore.java @@ -210,6 +210,15 @@ public class MemoryDocumentStore implements DocumentStore { } @Override + public List createOrUpdate(Collection collection, List updateOps) { + List result = new ArrayList(updateOps.size()); + for (UpdateOp update : updateOps) { + result.add(createOrUpdate(collection, update)); + } + return result; + } + + @Override public T findAndUpdate(Collection collection, UpdateOp update) { return internalCreateOrUpdate(collection, update, true); } diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/mongo/MongoDocumentStore.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/mongo/MongoDocumentStore.java index e6cf30a..63304e7 100644 --- a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/mongo/MongoDocumentStore.java +++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/mongo/MongoDocumentStore.java @@ -792,6 +792,15 @@ public class MongoDocumentStore implements DocumentStore { } @Override + public List createOrUpdate(Collection collection, List updateOps) { + List result = new ArrayList(updateOps.size()); + for (UpdateOp update : updateOps) { + result.add(createOrUpdate(collection, update)); + } + return result; + } + + @Override public T findAndUpdate(Collection collection, UpdateOp update) throws DocumentStoreException { log("findAndUpdate", update); diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/rdb/RDBDocumentStore.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/rdb/RDBDocumentStore.java index 098dce9..2e168f5 100755 --- a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/rdb/RDBDocumentStore.java +++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/rdb/RDBDocumentStore.java @@ -289,6 +289,15 @@ public class RDBDocumentStore implements DocumentStore { } @Override + public List createOrUpdate(Collection collection, List updateOps) { + List result = new ArrayList(updateOps.size()); + for (UpdateOp update : updateOps) { + result.add(createOrUpdate(collection, update)); + } + return result; + } + + @Override public T findAndUpdate(Collection collection, UpdateOp update) { return internalCreateOrUpdate(collection, update, false, true); } diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/util/LeaseCheckDocumentStoreWrapper.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/util/LeaseCheckDocumentStoreWrapper.java index d567acb..ef25237 100644 --- a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/util/LeaseCheckDocumentStoreWrapper.java +++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/util/LeaseCheckDocumentStoreWrapper.java @@ -127,6 +127,13 @@ public final class LeaseCheckDocumentStoreWrapper implements DocumentStore { } @Override + public List createOrUpdate(Collection collection, + List updateOps) { + performLeaseCheck(); + return delegate.createOrUpdate(collection, updateOps); + } + + @Override public final T findAndUpdate(Collection collection, UpdateOp update) { performLeaseCheck(); diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/util/LoggingDocumentStoreWrapper.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/util/LoggingDocumentStoreWrapper.java index aed8e5e..8afee18 100644 --- a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/util/LoggingDocumentStoreWrapper.java +++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/util/LoggingDocumentStoreWrapper.java @@ -225,6 +225,23 @@ public class LoggingDocumentStoreWrapper implements DocumentStore { } @Override + public List createOrUpdate(final Collection collection, + final List updateOps) { + try { + logMethod("createOrUpdate", collection, updateOps); + return logResult(new Callable>() { + @Override + public List call() throws Exception { + return store.createOrUpdate(collection, updateOps); + } + }); + } catch (Exception e) { + logException(e); + throw convert(e); + } + } + + @Override public T findAndUpdate(final Collection collection, final UpdateOp update) { try { diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/util/SynchronizingDocumentStoreWrapper.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/util/SynchronizingDocumentStoreWrapper.java index 9f3a544..ce59b88 100644 --- a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/util/SynchronizingDocumentStoreWrapper.java +++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/util/SynchronizingDocumentStoreWrapper.java @@ -97,6 +97,11 @@ public class SynchronizingDocumentStoreWrapper implements DocumentStore { } @Override + public synchronized List createOrUpdate(Collection collection, List updateOps) { + return store.createOrUpdate(collection, updateOps); + } + + @Override public synchronized T findAndUpdate(final Collection collection, final UpdateOp update) { return store.findAndUpdate(collection, update); } @@ -142,7 +147,7 @@ public class SynchronizingDocumentStoreWrapper implements DocumentStore { } @Override - public Map getMetadata() { + public synchronized Map getMetadata() { return store.getMetadata(); } } diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/util/TimingDocumentStoreWrapper.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/util/TimingDocumentStoreWrapper.java index 18f8fa2..bd71e48 100644 --- a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/util/TimingDocumentStoreWrapper.java +++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/util/TimingDocumentStoreWrapper.java @@ -16,6 +16,7 @@ */ package org.apache.jackrabbit.oak.plugins.document.util; +import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; @@ -256,6 +257,25 @@ public class TimingDocumentStoreWrapper implements DocumentStore { } @Override + public List createOrUpdate(Collection collection, List updateOps) { + try { + long start = now(); + List result = base.createOrUpdate(collection, updateOps); + updateAndLogTimes("createOrUpdate", start, 0, size(result)); + if (logCommonCall()) { + List ids = new ArrayList(); + for (UpdateOp op : updateOps) { + ids.add(op.getId()); + } + logCommonCall(start, "createOrUpdate " + collection + " " + updateOps + " " + ids); + } + return result; + } catch (Exception e) { + throw convert(e); + } + } + + @Override @CheckForNull public T findAndUpdate(Collection collection, UpdateOp update) { try { diff --git a/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/BasicDocumentStoreTest.java b/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/BasicDocumentStoreTest.java index e69ea65..6d19033 100644 --- a/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/BasicDocumentStoreTest.java +++ b/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/BasicDocumentStoreTest.java @@ -181,7 +181,7 @@ public class BasicDocumentStoreTest extends AbstractDocumentStoreTest { } @Test - public void testConditionalupdateForbidden() { + public void testConditionalUpdateForbidden() { String id = this.getClass().getName() + ".testConditionalupdateForbidden"; // remove if present @@ -221,6 +221,17 @@ public class BasicDocumentStoreTest extends AbstractDocumentStoreTest { UpdateOp up = new UpdateOp(id, false); up.set("_id", id); up.equals("foo", "bar"); + super.ds.createOrUpdate(Collection.NODES, Collections.singletonList(up)); + fail("conditional createOrUpdate should fail"); + } + catch (IllegalArgumentException expected) { + // reported by DocumentStore + } + + try { + UpdateOp up = new UpdateOp(id, false); + up.set("_id", id); + up.equals("foo", "bar"); super.ds.update(Collection.NODES, Collections.singletonList(id), up); fail("conditional update should fail"); } diff --git a/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/BulkCreateOrUpdateTest.java b/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/BulkCreateOrUpdateTest.java new file mode 100644 index 0000000..3548670 --- /dev/null +++ b/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/BulkCreateOrUpdateTest.java @@ -0,0 +1,148 @@ +/* + * 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.jackrabbit.oak.plugins.document; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.Test; + +public class BulkCreateOrUpdateTest extends AbstractDocumentStoreTest { + + public BulkCreateOrUpdateTest(DocumentStoreFixture dsf) { + super(dsf); + } + + /** + * This tests create multiple items using createOrUpdate() method. The + * return value should be a list of null values. + */ + @Test + public void testCreateMultiple() { + final int amount = 100; + + List updates = new ArrayList(amount); + + for (int i = 0; i < amount; i++) { + String id = this.getClass().getName() + ".testCreateMultiple" + i; + UpdateOp up = new UpdateOp(id, true); + up.set("_id", id); + updates.add(up); + removeMe.add(id); + } + + List docs = ds.createOrUpdate(Collection.NODES, updates); + assertEquals(amount, docs.size()); + for (int i = 0; i < amount; i++) { + assertNull("There shouldn't be a value for created doc", docs.get(i)); + assertNotNull("The node hasn't been created", ds.find(Collection.NODES, updates.get(i).getId())); + } + } + + /** + * This method updates multiple items using createOrUpdate() method. The + * return value should be a list of items before the update. + */ + @Test + public void testUpdateMultiple() { + final int amount = 100; + List updates = new ArrayList(amount); + + for (int i = 0; i < amount; i++) { + String id = this.getClass().getName() + ".testUpdateMultiple" + i; + UpdateOp up = new UpdateOp(id, true); + up.set("_id", id); + up.set("prop", 100); + updates.add(up); + removeMe.add(id); + } + + ds.create(Collection.NODES, updates); + + for (int i = 0; i < amount; i++) { + UpdateOp up = updates.get(i).copy(); + up.set("prop", 200); + updates.set(i, up); + } + + List docs = ds.createOrUpdate(Collection.NODES, updates); + assertEquals(amount, docs.size()); + for (int i = 0; i < amount; i++) { + NodeDocument oldDoc = docs.get(i); + String id = oldDoc.getId(); + NodeDocument newDoc = ds.find(Collection.NODES, id); + assertEquals("The result list order is incorrect", updates.get(i).getId(), id); + assertEquals("The old value is not correct", 100l, oldDoc.get("prop")); + assertEquals("The document hasn't been updated", 200l, newDoc.get("prop")); + } + } + + /** + * This method creates or updates multiple items using createOrUpdate() + * method. New items have odd indexes and updates items have even indexes. + * The return value should be a list of old documents (for the updates) or + * nulls (for the inserts). + */ + @Test + public void testCreateOrUpdateMultiple() { + int amount = 100; + List updates = new ArrayList(amount); + + // create even items + for (int i = 0; i < amount; i += 2) { + String id = this.getClass().getName() + ".testCreateOrUpdateMultiple" + i; + UpdateOp up = new UpdateOp(id, true); + up.set("_id", id); + up.set("prop", 100); + updates.add(up); + removeMe.add(id); + } + ds.create(Collection.NODES, updates); + updates.clear(); + + // createOrUpdate all items + for (int i = 0; i < amount; i++) { + String id = this.getClass().getName() + ".testCreateOrUpdateMultiple" + i; + UpdateOp up = new UpdateOp(id, true); + up.set("_id", id); + up.set("prop", 200); + updates.add(up); + removeMe.add(id); + } + List docs = ds.createOrUpdate(Collection.NODES, updates); + + assertEquals(amount, docs.size()); + for (int i = 0; i < amount; i++) { + String id = this.getClass().getName() + ".testCreateOrUpdateMultiple" + i; + + NodeDocument oldDoc = docs.get(i); + NodeDocument newDoc = ds.find(Collection.NODES, id); + if (i % 2 == 1) { + assertNull("The returned value should be null for created doc", oldDoc); + } else { + assertNotNull("The returned doc shouldn't be null for updated doc", oldDoc); + assertEquals("The old value is not correct", 100l, oldDoc.get("prop")); + assertEquals("The result list order is incorrect", updates.get(i).getId(), oldDoc.getId()); + } + assertEquals("The document hasn't been updated", 200l, newDoc.get("prop")); + } + } +} diff --git a/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/CountingDocumentStore.java b/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/CountingDocumentStore.java index 75fe0a1..e88b727 100644 --- a/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/CountingDocumentStore.java +++ b/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/CountingDocumentStore.java @@ -171,6 +171,13 @@ public class CountingDocumentStore implements DocumentStore { } @Override + public List createOrUpdate(Collection collection, + List updateOps) { + getStats(collection).numCreateOrUpdateCalls++; + return delegate.createOrUpdate(collection, updateOps); + } + + @Override public T findAndUpdate(Collection collection, UpdateOp update) { getStats(collection).numCreateOrUpdateCalls++; diff --git a/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/DocumentStoreWrapper.java b/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/DocumentStoreWrapper.java index 7739809..861c4d7 100644 --- a/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/DocumentStoreWrapper.java +++ b/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/DocumentStoreWrapper.java @@ -107,6 +107,12 @@ public class DocumentStoreWrapper implements DocumentStore { } @Override + public List createOrUpdate(Collection collection, + List updateOps) { + return store.createOrUpdate(collection, updateOps); + } + + @Override public T findAndUpdate(Collection collection, UpdateOp update) { return store.findAndUpdate(collection, update);