Index: oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentStore.java
===================================================================
--- oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentStore.java	(revision 1729598)
+++ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentStore.java	(working copy)
@@ -166,6 +166,25 @@
                                      Map<String, Map<UpdateOp.Key, UpdateOp.Condition>> toRemove);
 
     /**
+     * Batch remove documents based on an indexed property.
+     *
+     * @param <T>
+     *            the document type
+     * @param collection
+     *            the collection.
+     * @param indexedProperty
+     *            the name of the indexed property
+     * @param startValue
+     *            the minimum value of the indexed property (use
+     *            {@code Long#MIN_VALUE} when there is no limit)
+     * @param endValue
+     *            the maximum value of the indexed property (use
+     *            {@code Long#MAX_VALUE} when there is no limit)
+     * @return the number of removed documents.
+     */
+    <T extends Document> int remove(Collection<T> collection, String indexedProperty, long startValue, long endValue);
+
+    /**
      * Try to create a list of documents. This method returns {@code true} iff
      * none of the documents existed before and the create was successful. This
      * method will return {@code false} if one of the documents already exists
Index: oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/UnsupportedDocumentStoreOperationException.java
===================================================================
--- oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/UnsupportedDocumentStoreOperationException.java	(revision 0)
+++ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/UnsupportedDocumentStoreOperationException.java	(working copy)
@@ -0,0 +1,39 @@
+/*
+ * 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;
+
+/**
+ * A {@linkplain DocumentStoreException} signalling that the requested operation
+ * is not supported by the implementation.
+ */
+public class UnsupportedDocumentStoreOperationException extends DocumentStoreException {
+
+    private static final long serialVersionUID = 7591221165089369961L;
+
+    public UnsupportedDocumentStoreOperationException(String message) {
+        super(message);
+    }
+
+    public UnsupportedDocumentStoreOperationException(Throwable cause) {
+        super(cause);
+    }
+
+    public UnsupportedDocumentStoreOperationException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+}

Property changes on: oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/UnsupportedDocumentStoreOperationException.java
___________________________________________________________________
Added: svn:executable
## -0,0 +1 ##
+*
\ No newline at end of property
Index: oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/memory/MemoryDocumentStore.java
===================================================================
--- oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/memory/MemoryDocumentStore.java	(revision 1729598)
+++ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/memory/MemoryDocumentStore.java	(working copy)
@@ -202,6 +202,40 @@
         return num;
     }
 
+    @Override
+    public <T extends Document> int remove(Collection<T> collection, String indexedProperty, long startValue, long endValue) {
+        int num = 0;
+        Lock lock = rwLock.writeLock();
+        lock.lock();
+        try {
+            ConcurrentSkipListMap<String, T> map = getMap(collection);
+            for (T doc : map.values()) {
+                if (indexedProperty != null) {
+                    Object value = doc.get(indexedProperty);
+                    if (value instanceof Boolean) {
+                        long test = ((Boolean) value) ? 1 : 0;
+                        if (test < startValue || test > endValue) {
+                            continue;
+                        }
+                    } else if (value instanceof Long) {
+                        if ((Long) value < startValue || (Long) value > endValue) {
+                            continue;
+                        }
+                    } else if (value != null) {
+                        throw new DocumentStoreException(
+                                "unexpected type for property " + indexedProperty + ": " + value.getClass());
+                    }
+                    if (map.remove(doc.getId()) != null) {
+                        num++;
+                    }
+                }
+            }
+        } finally {
+            lock.unlock();
+        }
+        return num;
+    }
+
     @CheckForNull
     @Override
     public <T extends Document> T createOrUpdate(Collection<T> collection, UpdateOp update) {
Index: oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/mongo/MongoDocumentStore.java
===================================================================
--- oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/mongo/MongoDocumentStore.java	(revision 1729598)
+++ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/mongo/MongoDocumentStore.java	(working copy)
@@ -63,6 +63,7 @@
 import org.apache.jackrabbit.oak.plugins.document.NodeDocument;
 import org.apache.jackrabbit.oak.plugins.document.Revision;
 import org.apache.jackrabbit.oak.plugins.document.StableRevisionComparator;
+import org.apache.jackrabbit.oak.plugins.document.UnsupportedDocumentStoreOperationException;
 import org.apache.jackrabbit.oak.plugins.document.UpdateOp;
 import org.apache.jackrabbit.oak.plugins.document.UpdateOp.Condition;
 import org.apache.jackrabbit.oak.plugins.document.UpdateOp.Key;
@@ -720,6 +721,11 @@
         return num;
     }
 
+    @Override
+    public <T extends Document> int remove(Collection<T> collection, String indexedProperty, long startValue, long endValue) {
+        throw new UnsupportedDocumentStoreOperationException("OAK-3985");
+    }
+
     @SuppressWarnings("unchecked")
     @CheckForNull
     private <T extends Document> T findAndModify(Collection<T> collection,
Index: oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/rdb/RDBDocumentStore.java
===================================================================
--- oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/rdb/RDBDocumentStore.java	(revision 1729598)
+++ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/rdb/RDBDocumentStore.java	(working copy)
@@ -71,6 +71,7 @@
 import org.apache.jackrabbit.oak.plugins.document.DocumentStoreException;
 import org.apache.jackrabbit.oak.plugins.document.DocumentStoreStatsCollector;
 import org.apache.jackrabbit.oak.plugins.document.NodeDocument;
+import org.apache.jackrabbit.oak.plugins.document.UnsupportedDocumentStoreOperationException;
 import org.apache.jackrabbit.oak.plugins.document.UpdateOp;
 import org.apache.jackrabbit.oak.plugins.document.UpdateOp.Condition;
 import org.apache.jackrabbit.oak.plugins.document.UpdateOp.Key;
@@ -280,6 +281,12 @@
     }
 
     @Override
+    public <T extends Document> int remove(Collection<T> collection, String indexedProperty, long minValue, long maxValue) {
+        return deleteWithConditions(collection, indexedProperty, minValue, maxValue);
+        // TODO cache???
+    }
+
+    @Override
     public <T extends Document> boolean create(Collection<T> collection, List<UpdateOp> updateOps) {
         return internalCreate(collection, updateOps);
     }
@@ -1610,6 +1617,35 @@
         return numDeleted;
     }
 
+    private <T extends Document> int deleteWithConditions(Collection<T> collection, String indexedProperty, long minValue,
+            long maxValue) {
+        int numDeleted = 0;
+        RDBTableMetaData tmd = getTable(collection);
+        List<QueryCondition> conditions = new ArrayList<QueryCondition>();
+        if (minValue == maxValue) {
+            conditions.add(new QueryCondition(indexedProperty, "=", minValue));
+        }
+        else {
+            if (minValue != Long.MIN_VALUE) {
+                conditions.add(new QueryCondition(indexedProperty, ">=", minValue));
+            }
+            if (maxValue != Long.MAX_VALUE) {
+                conditions.add(new QueryCondition(indexedProperty, "<=", maxValue));
+            }
+        }
+        Connection connection = null;
+        try {
+            connection = this.ch.getRWConnection();
+            numDeleted += db.deleteWithConditions(connection, tmd, conditions);
+            connection.commit();
+        } catch (Exception ex) {
+            throw new DocumentStoreException(ex);
+        } finally {
+            this.ch.closeConnection(connection);
+        }
+        return numDeleted;
+    }
+
     private <T extends Document> boolean updateDocument(@Nonnull Collection<T> collection, @Nonnull T document,
             @Nonnull UpdateOp update, Long oldmodcount) {
         Connection connection = null;
Index: oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/rdb/RDBDocumentStoreJDBC.java
===================================================================
--- oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/rdb/RDBDocumentStoreJDBC.java	(revision 1729598)
+++ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/rdb/RDBDocumentStoreJDBC.java	(working copy)
@@ -49,6 +49,7 @@
 import org.apache.jackrabbit.oak.plugins.document.Document;
 import org.apache.jackrabbit.oak.plugins.document.DocumentStoreException;
 import org.apache.jackrabbit.oak.plugins.document.NodeDocument;
+import org.apache.jackrabbit.oak.plugins.document.UnsupportedDocumentStoreOperationException;
 import org.apache.jackrabbit.oak.plugins.document.UpdateOp.Condition;
 import org.apache.jackrabbit.oak.plugins.document.UpdateOp.Key;
 import org.apache.jackrabbit.oak.plugins.document.rdb.RDBDocumentStore.QueryCondition;
@@ -77,6 +78,26 @@
     private final RDBDocumentSerializer ser;
     private final int queryHitsLimit, queryTimeLimit;
 
+    private final static Map<String, String> INDEXED_PROP_MAPPING;
+    static {
+        Map<String, String> tmp = new HashMap<String, String>();
+        tmp.put(MODIFIED, "MODIFIED");
+        tmp.put(NodeDocument.HAS_BINARY_FLAG, "HASBINARY");
+        tmp.put(NodeDocument.DELETED_ONCE, "DELETEDONCE");
+        INDEXED_PROP_MAPPING = Collections.unmodifiableMap(tmp);
+    }
+
+    private final static Set<String> SUPPORTED_OPS;
+    static {
+        Set<String> tmp = new HashSet<String>();
+        tmp.add(">=");
+        tmp.add(">");
+        tmp.add("<=");
+        tmp.add("<");
+        tmp.add("=");
+        SUPPORTED_OPS = Collections.unmodifiableSet(tmp);
+    }
+
     public RDBDocumentStoreJDBC(RDBDocumentStoreDB dbInfo, RDBDocumentSerializer ser, int queryHitsLimit, int queryTimeLimit) {
         this.dbInfo = dbInfo;
         this.ser = ser;
@@ -186,6 +207,43 @@
         return count;
     }
 
+    public int deleteWithConditions(Connection connection, RDBTableMetaData tmd, List<QueryCondition> conditions)
+            throws SQLException {
+
+        if (conditions.isEmpty()) {
+            throw new UnsupportedDocumentStoreOperationException("unsupported unconditional delete");
+        }
+
+        StringBuilder whereClause = new StringBuilder();
+        // dynamically build where clause
+        String whereSep = "";
+        for (QueryCondition cond : conditions) {
+            String op = cond.getOperator();
+            if (!SUPPORTED_OPS.contains(op)) {
+                throw new UnsupportedDocumentStoreOperationException("unsupported operator: " + op);
+            }
+            String indexedProperty = cond.getPropertyName();
+            String column = INDEXED_PROP_MAPPING.get(indexedProperty);
+            if (column != null) {
+                whereClause.append(whereSep).append(column).append(" ").append(op).append(" ?");
+                whereSep = " and ";
+            } else {
+                throw new UnsupportedDocumentStoreOperationException("unsupported indexed property: " + indexedProperty);
+            }
+        }
+
+        PreparedStatement stmt = connection.prepareStatement("delete from " + tmd.getName() + " where " + whereClause.toString());
+        try {
+            int si = 1;
+            for (QueryCondition cond : conditions) {
+                stmt.setLong(si++, cond.getValue());
+            }
+            return stmt.executeUpdate();
+        } finally {
+            stmt.close();
+        }
+    }
+
     public int delete(Connection connection, RDBTableMetaData tmd, Map<String, Map<Key, Condition>> toDelete)
             throws SQLException, DocumentStoreException {
         String or = "";
@@ -458,26 +516,6 @@
         }
     }
 
-    private final static Map<String, String> INDEXED_PROP_MAPPING;
-    static {
-        Map<String, String> tmp = new HashMap<String, String>();
-        tmp.put(MODIFIED, "MODIFIED");
-        tmp.put(NodeDocument.HAS_BINARY_FLAG, "HASBINARY");
-        tmp.put(NodeDocument.DELETED_ONCE, "DELETEDONCE");
-        INDEXED_PROP_MAPPING = Collections.unmodifiableMap(tmp);
-    }
-
-    private final static Set<String> SUPPORTED_OPS;
-    static {
-        Set<String> tmp = new HashSet<String>();
-        tmp.add(">=");
-        tmp.add(">");
-        tmp.add("<=");
-        tmp.add("<");
-        tmp.add("=");
-        SUPPORTED_OPS = Collections.unmodifiableSet(tmp);
-    }
-
     @Nonnull
     public List<RDBRow> query(Connection connection, RDBTableMetaData tmd, String minId, String maxId,
             List<String> excludeKeyPatterns, List<QueryCondition> conditions, int limit) throws SQLException {
Index: oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/util/LeaseCheckDocumentStoreWrapper.java
===================================================================
--- oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/util/LeaseCheckDocumentStoreWrapper.java	(revision 1729598)
+++ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/util/LeaseCheckDocumentStoreWrapper.java	(working copy)
@@ -106,6 +106,13 @@
     }
 
     @Override
+    public <T extends Document> int remove(Collection<T> collection, String indexedProperty, long startValue, long endValue) {
+        performLeaseCheck();
+        return delegate.remove(collection, indexedProperty, startValue, endValue);
+    }
+
+
+    @Override
     public final <T extends Document> boolean create(Collection<T> collection,
             List<UpdateOp> updateOps) {
         performLeaseCheck();
@@ -202,5 +209,4 @@
         performLeaseCheck();
         return delegate.determineServerTimeDifferenceMillis();
     }
-
 }
Index: oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/util/LoggingDocumentStoreWrapper.java
===================================================================
--- oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/util/LoggingDocumentStoreWrapper.java	(revision 1729598)
+++ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/util/LoggingDocumentStoreWrapper.java	(working copy)
@@ -170,7 +170,25 @@
         }
     }
 
+
     @Override
+    public <T extends Document> int remove(final Collection<T> collection, final String indexedProperty, final long startValue,
+            final long endValue) {
+        try {
+            logMethod("remove", collection, indexedProperty, startValue, endValue);
+            return logResult(new Callable<Integer>() {
+                @Override
+                public Integer call() throws Exception {
+                    return store.remove(collection, indexedProperty, startValue, endValue);
+                }
+            });
+        } catch (Exception e) {
+            logException(e);
+            throw convert(e);
+        }
+    }
+
+    @Override
     public <T extends Document> boolean create(final Collection<T> collection,
                                                final List<UpdateOp> updateOps) {
         try {
Index: oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/util/SynchronizingDocumentStoreWrapper.java
===================================================================
--- oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/util/SynchronizingDocumentStoreWrapper.java	(revision 1729598)
+++ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/util/SynchronizingDocumentStoreWrapper.java	(working copy)
@@ -81,6 +81,12 @@
     }
 
     @Override
+    public synchronized <T extends Document> int remove(Collection<T> collection, String indexedProperty, long startValue,
+            long endValue) {
+        return store.remove(collection, indexedProperty, startValue, endValue);
+    }
+
+    @Override
     public synchronized <T extends Document> boolean create(final Collection<T> collection, final List<UpdateOp> updateOps) {
         return store.create(collection, updateOps);
     }
Index: oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/util/TimingDocumentStoreWrapper.java
===================================================================
--- oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/util/TimingDocumentStoreWrapper.java	(revision 1729598)
+++ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/util/TimingDocumentStoreWrapper.java	(working copy)
@@ -209,7 +209,23 @@
         }
     }
 
+
     @Override
+    public <T extends Document> int remove(Collection<T> collection, String indexedProperty, long startValue, long endValue) {
+        try {
+            long start = now();
+            int result = base.remove(collection, indexedProperty, startValue, endValue);
+            updateAndLogTimes("remove", start, 0, 0);
+            if (logCommonCall()) {
+                logCommonCall(start, "remove " + collection + " " + startValue + " <= " + indexedProperty + " <= " + endValue);
+            }
+            return result;
+        } catch (Exception e) {
+            throw convert(e);
+        }
+    }
+
+    @Override
     public <T extends Document> boolean create(Collection<T> collection, List<UpdateOp> updateOps) {
         try {
             long start = now();
Index: oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/BasicDocumentStoreTest.java
===================================================================
--- oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/BasicDocumentStoreTest.java	(revision 1729598)
+++ oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/BasicDocumentStoreTest.java	(working copy)
@@ -911,6 +911,45 @@
         assertNull(ds.getIfCached(Collection.NODES, id));
     }
 
+    @Test
+    public void restRemoveWithIndexedProperty() throws Exception {
+        Set<String> existingDocs = new HashSet<String>();
+        for (NodeDocument doc : Utils.getAllDocuments(ds)) {
+            existingDocs.add(doc.getPath());
+        }
+
+        List<UpdateOp> docs = Lists.newArrayList();
+        docs.add(newDocument("/foo", 100));
+        removeMe.add(Utils.getIdFromPath("/foo"));
+        docs.add(newDocument("/bar", 200));
+        removeMe.add(Utils.getIdFromPath("/bar"));
+        docs.add(newDocument("/baz", 300));
+        removeMe.add(Utils.getIdFromPath("/baz"));
+        ds.create(Collection.NODES, docs);
+
+        for (UpdateOp op : docs) {
+            assertNotNull(ds.find(Collection.NODES, op.getId()));
+        }
+
+        try {
+            int deletedOne = ds.remove(Collection.NODES, NodeDocument.MODIFIED_IN_SECS, 200, 200);
+            assertEquals(1, deletedOne);
+            assertNull(ds.find(Collection.NODES, Utils.getIdFromPath("/bar"), 0));
+            assertNotNull(ds.find(Collection.NODES, Utils.getIdFromPath("/foo"), 0));
+            assertNotNull(ds.find(Collection.NODES, Utils.getIdFromPath("/baz"), 0));
+
+            int deletedRange = ds.remove(Collection.NODES, NodeDocument.MODIFIED_IN_SECS, 99, 101);
+            assertEquals(1, deletedRange);
+            assertNull(ds.find(Collection.NODES, Utils.getIdFromPath("/bar"), 0));
+            assertNull(ds.find(Collection.NODES, Utils.getIdFromPath("/foo"), 0));
+            assertNotNull(ds.find(Collection.NODES, Utils.getIdFromPath("/baz"), 0));
+        }
+        catch (UnsupportedDocumentStoreOperationException ex){
+            LOG.info(dsname + " does not support remove with indexed property condition");
+            System.err.println(dsname + " does not support remove with indexed property condition");
+        }
+    }
+
     // OAK-3932
     @Test
     public void getIfCachedNonExistingDocument() throws Exception {
Index: oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/CountingDocumentStore.java
===================================================================
--- oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/CountingDocumentStore.java	(revision 1729598)
+++ oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/CountingDocumentStore.java	(working copy)
@@ -148,7 +148,14 @@
         return delegate.remove(collection, toRemove);
     }
 
+
     @Override
+    public <T extends Document> int remove(Collection<T> collection, String indexedProperty, long startValue, long endValue) {
+        getStats(collection).numRemoveCalls++;
+        return delegate.remove(collection, indexedProperty, startValue, endValue);
+    }
+
+    @Override
     public <T extends Document> boolean create(Collection<T> collection,
                                                List<UpdateOp> updateOps) {
         getStats(collection).numCreateOrUpdateCalls++;
Index: oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/DocumentStoreWrapper.java
===================================================================
--- oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/DocumentStoreWrapper.java	(revision 1729598)
+++ oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/DocumentStoreWrapper.java	(working copy)
@@ -88,6 +88,11 @@
     }
 
     @Override
+    public <T extends Document> int remove(Collection<T> collection, String indexedProperty, long startValue, long endValue) {
+        return store.remove(collection, indexedProperty, startValue, endValue);
+    }
+
+    @Override
     public <T extends Document> boolean create(Collection<T> collection,
                                                List<UpdateOp> updateOps) {
         return store.create(collection, updateOps);
