/** * Original iOS version by Jens Alfke * Ported to Android by Marty Schoch * <p/> * Copyright (c) 2012 Couchbase, Inc. All rights reserved. * <p/> * Licensed 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 * <p/> * http://www.apache.org/licenses/LICENSE-2.0 * <p/> * 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 com.couchbase.lite; import com.couchbase.lite.View.TDViewCollation; import com.couchbase.lite.internal.RevisionInternal; import com.couchbase.lite.store.Store; import com.couchbase.lite.store.StoreDelegate; import com.couchbase.lite.util.CountDown; import com.couchbase.lite.util.Log; import junit.framework.Assert; import java.io.IOException; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.Date; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; public class ViewsTest extends LiteTestCaseWithDB { public static final String TAG = "ViewsTest"; public void testQueryDefaultIndexUpdateMode() { View view = database.getView("aview"); Query query = view.createQuery(); assertEquals(Query.IndexUpdateMode.BEFORE, query.getIndexUpdateMode()); } /** * - (void) test01_Create */ public void testViewCreation() { Assert.assertNull(database.getExistingView("aview")); View view = database.getView("aview"); Assert.assertNotNull(view); Assert.assertEquals(database, view.getDatabase()); Assert.assertEquals("aview", view.getName()); Assert.assertNull(view.getMap()); Assert.assertEquals(view, database.getExistingView("aview")); boolean changed = view.setMapReduce(new Mapper() { @Override public void map(Map<String, Object> document, Emitter emitter) { //no-op } }, null, "1"); Assert.assertTrue(changed); Assert.assertEquals(1, database.getAllViews().size()); Assert.assertEquals(view, database.getAllViews().get(0)); changed = view.setMapReduce(new Mapper() { @Override public void map(Map<String, Object> document, Emitter emitter) { //no-op } }, null, "1"); Assert.assertFalse(changed); changed = view.setMapReduce(new Mapper() { @Override public void map(Map<String, Object> document, Emitter emitter) { //no-op } }, null, "2"); Assert.assertTrue(changed); } //https://github.com/couchbase/couchbase-lite-android/issues/952 public void testViewCreationAndRecall() { try { String expectedVersion = "1.0"; String viewName = "test/view"; Database db1 = startDatabase(); putDocs(db1); View view1 = db1.getView(viewName); view1.setMap(new Mapper() { @Override public void map(Map<String, Object> document, Emitter emitter) { if (document.get("key") != null) { emitter.emit(document.get("key"), null); } } }, expectedVersion); view1.updateIndex(); db1.close(); Database db2 = manager.getExistingDatabase(DEFAULT_TEST_DB); View view2 = db2.getExistingView(viewName); Assert.assertNotNull(view2); view2.setMap(new Mapper() { @Override public void map(Map<String, Object> document, Emitter emitter) { if (document.get("key") != null) { emitter.emit(document.get("key"), null); } } }, expectedVersion); assertEquals(Status.NOT_MODIFIED,view2.updateIndex().getCode()); db2.close(); } catch (CouchbaseLiteException e) { e.printStackTrace(); } } //https://github.com/couchbase/couchbase-lite-java-core/issues/219 public void testDeleteView() { List<View> views = database.getAllViews(); for (View view : views) { view.delete(); } Assert.assertEquals(0, database.getAllViews().size()); Assert.assertEquals(null, database.getExistingView("viewToDelete")); View view = database.getView("viewToDelete"); Assert.assertNotNull(view); Assert.assertEquals(database, view.getDatabase()); Assert.assertEquals("viewToDelete", view.getName()); Assert.assertNull(view.getMap()); Assert.assertEquals(view, database.getExistingView("viewToDelete")); // NOTE: Forestdb view storage creates view db when constructor is called. // But SQLite view storage does not create view record when constructor is called. if(isUseForestDB()) Assert.assertEquals(1, database.getAllViews().size()); else Assert.assertEquals(0, database.getAllViews().size()); boolean changed = view.setMapReduce(new Mapper() { @Override public void map(Map<String, Object> document, Emitter emitter) { //no-op } }, null, "1"); Assert.assertTrue(changed); Assert.assertEquals(1, database.getAllViews().size()); Assert.assertEquals(view, database.getAllViews().get(0)); view.delete(); //Status status = database.deleteViewNamed("viewToDelete"); //Assert.assertEquals(Status.OK, status.getCode()); Assert.assertEquals(0, database.getAllViews().size()); View nullView = database.getExistingView("viewToDelete"); Assert.assertNull("cached View is not deleted", nullView); view.delete(); //status = database.deleteViewNamed("viewToDelete"); //Assert.assertEquals(Status.NOT_FOUND, status.getCode()); } private RevisionInternal putDoc(Database db, Map<String, Object> props) throws CouchbaseLiteException { RevisionInternal rev = new RevisionInternal(props); Status status = new Status(); rev = db.putRevision(rev, null, false, status); Assert.assertTrue(status.isSuccessful()); return rev; } private void putDocViaUntitledDoc(Database db, Map<String, Object> props) throws CouchbaseLiteException { Document document = db.createDocument(); document.putProperties(props); } public List<RevisionInternal> putDocs(Database db) throws CouchbaseLiteException { List<RevisionInternal> result = new ArrayList<RevisionInternal>(); Map<String, Object> dict2 = new HashMap<String, Object>(); dict2.put("_id", "22222"); dict2.put("key", "two"); result.add(putDoc(db, dict2)); Map<String, Object> dict4 = new HashMap<String, Object>(); dict4.put("_id", "44444"); dict4.put("key", "four"); result.add(putDoc(db, dict4)); Map<String, Object> dict1 = new HashMap<String, Object>(); dict1.put("_id", "11111"); dict1.put("key", "one"); result.add(putDoc(db, dict1)); Map<String, Object> dict3 = new HashMap<String, Object>(); dict3.put("_id", "33333"); dict3.put("key", "three"); result.add(putDoc(db, dict3)); Map<String, Object> dict5 = new HashMap<String, Object>(); dict5.put("_id", "55555"); dict5.put("key", "five"); result.add(putDoc(db, dict5)); return result; } // http://wiki.apache.org/couchdb/Introduction_to_CouchDB_views#Linked_documents public List<RevisionInternal> putLinkedDocs(Database db) throws CouchbaseLiteException { List<RevisionInternal> result = new ArrayList<RevisionInternal>(); Map<String, Object> dict1 = new HashMap<String, Object>(); dict1.put("_id", "11111"); result.add(putDoc(db, dict1)); Map<String, Object> dict2 = new HashMap<String, Object>(); dict2.put("_id", "22222"); dict2.put("value", "hello"); dict2.put("ancestors", new String[]{"11111"}); result.add(putDoc(db, dict2)); Map<String, Object> dict3 = new HashMap<String, Object>(); dict3.put("_id", "33333"); dict3.put("value", "world"); dict3.put("ancestors", new String[]{"22222", "11111"}); result.add(putDoc(db, dict3)); return result; } public void putNDocs(Database db, int n) throws CouchbaseLiteException { for (int i = 0; i < n; i++) { Map<String, Object> doc = new HashMap<String, Object>(); doc.put("_id", String.format(Locale.ENGLISH, "%d", i)); List<String> key = new ArrayList<String>(); for (int j = 0; j < 256; j++) { key.add("key"); } key.add(String.format(Locale.ENGLISH, "key-%d", i)); doc.put("key", key); putDocViaUntitledDoc(db, doc); } } // - (CBLView*) createView public static View createView(Database db) { return createView(db, "aview"); } // - (CBLView*) createViewNamed: (NSString*)name public static View createView(Database db, String name) { View view = db.getView(name); if (view != null) { view.setMapReduce(new Mapper() { @Override public void map(Map<String, Object> document, Emitter emitter) { assertNotNull(document.get("_id")); assertNotNull(document.get("_rev")); assertNotNull(document.get("_local_seq")); assertTrue(document.get("_local_seq") instanceof Number); if (document.get("key") != null) emitter.emit(document.get("key"), null); } }, null, "1"); } return view; } /** * in ViewInternal_Tests.m * - (void) test02_Index */ public void testViewIndex() throws CouchbaseLiteException { int numTimesMapFunctionInvoked = 0; Map<String, Object> dict1 = new HashMap<String, Object>(); dict1.put("key", "one"); Map<String, Object> dict2 = new HashMap<String, Object>(); dict2.put("key", "two"); Map<String, Object> dict3 = new HashMap<String, Object>(); dict3.put("key", "three"); Map<String, Object> dictW = new HashMap<String, Object>(); dictW.put("_id", "_design/foo"); Map<String, Object> dictX = new HashMap<String, Object>(); dictX.put("clef", "quatre"); RevisionInternal rev1 = putDoc(database, dict1); RevisionInternal rev2 = putDoc(database, dict2); RevisionInternal rev3 = putDoc(database, dict3); putDoc(database, dictW); putDoc(database, dictX); class InstrumentedMapBlock implements Mapper { int numTimesInvoked = 0; @Override public void map(Map<String, Object> document, Emitter emitter) { numTimesInvoked += 1; Assert.assertNotNull(document.get("_id")); Assert.assertNotNull(document.get("_rev")); if (document.get("key") != null) { emitter.emit(document.get("key"), null); } } public int getNumTimesInvoked() { return numTimesInvoked; } } View view = database.getView("aview"); InstrumentedMapBlock mapBlock = new InstrumentedMapBlock(); view.setMap(mapBlock, "1"); Assert.assertTrue(view.isStale()); view.updateIndex(); List<Map<String, Object>> dumpResult = view.dump(); Log.v(TAG, "View dump: " + dumpResult); Assert.assertEquals(3, dumpResult.size()); Assert.assertEquals("\"one\"", dumpResult.get(0).get("key")); Assert.assertEquals(1, ((Number)dumpResult.get(0).get("seq")).intValue()); Assert.assertEquals("\"two\"", dumpResult.get(2).get("key")); Assert.assertEquals(2, ((Number)dumpResult.get(2).get("seq")).intValue()); Assert.assertEquals("\"three\"", dumpResult.get(1).get("key")); Assert.assertEquals(3, ((Number)dumpResult.get(1).get("seq")).intValue()); //no-op reindex Assert.assertFalse(view.isStale()); view.updateIndex(); // Now add a doc and update a doc: RevisionInternal threeUpdated = new RevisionInternal(rev3.getDocID(), rev3.getRevID(), false); numTimesMapFunctionInvoked = mapBlock.getNumTimesInvoked(); Map<String, Object> newdict3 = new HashMap<String, Object>(); newdict3.put("key", "3hree"); threeUpdated.setProperties(newdict3); Status status = new Status(); rev3 = database.putRevision(threeUpdated, rev3.getRevID(), false, status); Assert.assertTrue(status.isSuccessful()); // Reindex again: Assert.assertTrue(view.isStale()); view.updateIndex(); // Make sure the map function was only invoked one more time (for the document that was added) Assert.assertEquals(numTimesMapFunctionInvoked + 1, mapBlock.getNumTimesInvoked()); Map<String, Object> dict4 = new HashMap<String, Object>(); dict4.put("key", "four"); RevisionInternal rev4 = putDoc(database, dict4); RevisionInternal twoDeleted = new RevisionInternal(rev2.getDocID(), rev2.getRevID(), true); database.putRevision(twoDeleted, rev2.getRevID(), false, status); Assert.assertTrue(status.isSuccessful()); // Reindex again: Assert.assertTrue(view.isStale()); view.updateIndex(); dumpResult = view.dump(); Log.v(TAG, "View dump: " + dumpResult); Assert.assertEquals(3, dumpResult.size()); Assert.assertEquals("\"one\"", dumpResult.get(2).get("key")); Assert.assertEquals(1, ((Number)dumpResult.get(2).get("seq")).intValue()); Assert.assertEquals("\"3hree\"", dumpResult.get(0).get("key")); Assert.assertEquals(6, ((Number)dumpResult.get(0).get("seq")).intValue()); Assert.assertEquals("\"four\"", dumpResult.get(1).get("key")); Assert.assertEquals(7, ((Number)dumpResult.get(1).get("seq")).intValue()); // Now do a real query: List<QueryRow> rows = view.query(null); Assert.assertEquals(3, rows.size()); Assert.assertEquals("one", rows.get(2).getKey()); Assert.assertEquals(rev1.getDocID(), rows.get(2).getDocumentId()); Assert.assertEquals("3hree", rows.get(0).getKey()); Assert.assertEquals(rev3.getDocID(), rows.get(0).getDocumentId()); Assert.assertEquals("four", rows.get(1).getKey()); Assert.assertEquals(rev4.getDocID(), rows.get(1).getDocumentId()); view.deleteIndex(); } public void testViewIndexSkipsDesignDocs() throws CouchbaseLiteException { View view = createView(database); Map<String, Object> designDoc = new HashMap<String, Object>(); designDoc.put("_id", "_design/test"); designDoc.put("key", "value"); putDoc(database, designDoc); view.updateIndex(); List<QueryRow> rows = view.query(null); assertEquals(0, rows.size()); } /** * https://github.com/couchbase/couchbase-lite-java-core/issues/214 */ public void testViewIndexSkipsConflictingDesignDocs() throws CouchbaseLiteException { View view = createView(database); Map<String, Object> designDoc = new HashMap<String, Object>(); designDoc.put("_id", "_design/test"); designDoc.put("key", "value"); RevisionInternal rev1 = putDoc(database, designDoc); designDoc.put("_rev", rev1.getRevID()); designDoc.put("key", "value2a"); RevisionInternal rev2a = new RevisionInternal(designDoc); database.putRevision(rev2a, rev1.getRevID(), true); designDoc.put("key", "value2b"); RevisionInternal rev2b = new RevisionInternal(designDoc); database.putRevision(rev2b, rev1.getRevID(), true); view.updateIndex(); List<QueryRow> rows = view.query(null); assertEquals(0, rows.size()); } /** * - (void) test08_Query */ public void testViewQuery() throws CouchbaseLiteException { putDocs(database); View view = createView(database); view.updateIndex(); // Query all rows: QueryOptions options = new QueryOptions(); List<QueryRow> rows = view.query(options); List<Object> expectedRows = new ArrayList<Object>(); Map<String, Object> dict5 = new HashMap<String, Object>(); dict5.put("id", "55555"); dict5.put("key", "five"); expectedRows.add(dict5); Map<String, Object> dict4 = new HashMap<String, Object>(); dict4.put("id", "44444"); dict4.put("key", "four"); expectedRows.add(dict4); Map<String, Object> dict1 = new HashMap<String, Object>(); dict1.put("id", "11111"); dict1.put("key", "one"); expectedRows.add(dict1); Map<String, Object> dict3 = new HashMap<String, Object>(); dict3.put("id", "33333"); dict3.put("key", "three"); expectedRows.add(dict3); Map<String, Object> dict2 = new HashMap<String, Object>(); dict2.put("id", "22222"); dict2.put("key", "two"); expectedRows.add(dict2); Assert.assertEquals(5, rows.size()); Assert.assertEquals(dict5.get("key"), rows.get(0).getKey()); Assert.assertEquals(dict5.get("value"), rows.get(0).getValue()); Assert.assertEquals(dict4.get("key"), rows.get(1).getKey()); Assert.assertEquals(dict4.get("value"), rows.get(1).getValue()); Assert.assertEquals(dict1.get("key"), rows.get(2).getKey()); Assert.assertEquals(dict1.get("value"), rows.get(2).getValue()); Assert.assertEquals(dict3.get("key"), rows.get(3).getKey()); Assert.assertEquals(dict3.get("value"), rows.get(3).getValue()); Assert.assertEquals(dict2.get("key"), rows.get(4).getKey()); Assert.assertEquals(dict2.get("value"), rows.get(4).getValue()); // Start/end key query: options = new QueryOptions(); options.setStartKey("a"); options.setEndKey("one"); rows = view.query(options); expectedRows = new ArrayList<Object>(); expectedRows.add(dict5); expectedRows.add(dict4); expectedRows.add(dict1); Assert.assertEquals(3, rows.size()); Assert.assertEquals(dict5.get("key"), rows.get(0).getKey()); Assert.assertEquals(dict5.get("value"), rows.get(0).getValue()); Assert.assertEquals(dict4.get("key"), rows.get(1).getKey()); Assert.assertEquals(dict4.get("value"), rows.get(1).getValue()); Assert.assertEquals(dict1.get("key"), rows.get(2).getKey()); Assert.assertEquals(dict1.get("value"), rows.get(2).getValue()); // Start/end query without inclusive start: options.setInclusiveStart(false); options.setStartKey("five"); rows = view.query(options); Assert.assertEquals(2, rows.size()); Assert.assertEquals(dict4.get("key"), rows.get(0).getKey()); Assert.assertEquals(dict4.get("value"), rows.get(0).getValue()); Assert.assertEquals(dict1.get("key"), rows.get(1).getKey()); Assert.assertEquals(dict1.get("value"), rows.get(1).getValue()); // Start/end query without inclusive end: options.setStartKey("a"); options.setInclusiveStart(true); options.setInclusiveEnd(false); rows = view.query(options); expectedRows = new ArrayList<Object>(); expectedRows.add(dict5); expectedRows.add(dict4); Assert.assertEquals(2, rows.size()); Assert.assertEquals(dict5.get("key"), rows.get(0).getKey()); Assert.assertEquals(dict5.get("value"), rows.get(0).getValue()); Assert.assertEquals(dict4.get("key"), rows.get(1).getKey()); Assert.assertEquals(dict4.get("value"), rows.get(1).getValue()); // Reversed: options.setDescending(true); options.setStartKey("o"); options.setEndKey("five"); options.setInclusiveEnd(true); rows = view.query(options); expectedRows = new ArrayList<Object>(); expectedRows.add(dict4); expectedRows.add(dict5); Assert.assertEquals(2, rows.size()); Assert.assertEquals(dict4.get("key"), rows.get(0).getKey()); Assert.assertEquals(dict4.get("value"), rows.get(0).getValue()); Assert.assertEquals(dict5.get("key"), rows.get(1).getKey()); Assert.assertEquals(dict5.get("value"), rows.get(1).getValue()); // Reversed, no inclusive end: options.setInclusiveEnd(false); rows = view.query(options); expectedRows = new ArrayList<Object>(); expectedRows.add(dict4); Assert.assertEquals(1, rows.size()); Assert.assertEquals(dict4.get("key"), rows.get(0).getKey()); Assert.assertEquals(dict4.get("value"), rows.get(0).getValue()); // Limit: options = new QueryOptions(); options.setLimit(2); rows = view.query(options); Assert.assertEquals(2, rows.size()); Assert.assertEquals(dict5.get("key"), rows.get(0).getKey()); Assert.assertEquals(dict5.get("value"), rows.get(0).getValue()); Assert.assertEquals(dict4.get("key"), rows.get(1).getKey()); Assert.assertEquals(dict4.get("value"), rows.get(1).getValue()); // Skip rows: options = new QueryOptions(); options.setSkip(2); rows = view.query(options); Assert.assertEquals(3, rows.size()); Assert.assertEquals(dict1.get("key"), rows.get(0).getKey()); Assert.assertEquals(dict1.get("value"), rows.get(0).getValue()); Assert.assertEquals(dict3.get("key"), rows.get(1).getKey()); Assert.assertEquals(dict3.get("value"), rows.get(1).getValue()); Assert.assertEquals(dict2.get("key"), rows.get(2).getKey()); Assert.assertEquals(dict2.get("value"), rows.get(2).getValue()); // Skip + limit: options.setLimit(1); rows = view.query(options); Assert.assertEquals(1, rows.size()); Assert.assertEquals(dict1.get("key"), rows.get(0).getKey()); Assert.assertEquals(dict1.get("value"), rows.get(0).getValue()); // Specific keys: options = new QueryOptions(); List<Object> keys = new ArrayList<Object>(); keys.add("two"); keys.add("four"); options.setKeys(keys); rows = view.query(options); expectedRows = new ArrayList<Object>(); expectedRows.add(dict2); expectedRows.add(dict4); assertNotNull(rows); Assert.assertEquals(2, rows.size()); Assert.assertEquals(dict2.get("key"), rows.get(0).getKey()); Assert.assertEquals(dict2.get("value"), rows.get(0).getValue()); Assert.assertEquals(dict4.get("key"), rows.get(1).getKey()); Assert.assertEquals(dict4.get("value"), rows.get(1).getValue()); } //https://github.com/couchbase/couchbase-lite-android/issues/314 public void testViewQueryWithDictSentinel() throws CouchbaseLiteException { List<String> key1 = new ArrayList<String>(); key1.add("red"); key1.add("model1"); Map<String, Object> dict1 = new HashMap<String, Object>(); dict1.put("id", "11"); dict1.put("key", key1); putDoc(database, dict1); List<String> key2 = new ArrayList<String>(); key2.add("red"); key2.add("model2"); Map<String, Object> dict2 = new HashMap<String, Object>(); dict2.put("id", "12"); dict2.put("key", key2); putDoc(database, dict2); List<String> key3 = new ArrayList<String>(); key3.add("green"); key3.add("model1"); Map<String, Object> dict3 = new HashMap<String, Object>(); dict3.put("id", "21"); dict3.put("key", key3); putDoc(database, dict3); List<String> key4 = new ArrayList<String>(); key4.add("yellow"); key4.add("model2"); Map<String, Object> dict4 = new HashMap<String, Object>(); dict4.put("id", "31"); dict4.put("key", key4); putDoc(database, dict4); View view = createView(database); view.updateIndex(); // Query all rows: QueryOptions options = new QueryOptions(); List<QueryRow> rows = view.query(options); Assert.assertEquals(4, rows.size()); Assert.assertTrue(Arrays.equals(new Object[]{"green", "model1"}, ((List) rows.get(0).getKey()).toArray())); Assert.assertTrue(Arrays.equals(new Object[]{"red", "model1"}, ((List) rows.get(1).getKey()).toArray())); Assert.assertTrue(Arrays.equals(new Object[]{"red", "model2"}, ((List) rows.get(2).getKey()).toArray())); Assert.assertTrue(Arrays.equals(new Object[]{"yellow", "model2"}, ((List) rows.get(3).getKey()).toArray())); // Start/end key query: options = new QueryOptions(); options.setStartKey("a"); options.setEndKey(Arrays.asList("red", new HashMap<String, Object>())); rows = view.query(options); Assert.assertEquals(3, rows.size()); Assert.assertTrue(Arrays.equals(new Object[]{"green", "model1"}, ((List) rows.get(0).getKey()).toArray())); Assert.assertTrue(Arrays.equals(new Object[]{"red", "model1"}, ((List) rows.get(1).getKey()).toArray())); Assert.assertTrue(Arrays.equals(new Object[]{"red", "model2"}, ((List) rows.get(2).getKey()).toArray())); // Start/end query without inclusive end: options.setEndKey(Arrays.asList("red", "model1")); options.setInclusiveEnd(false); rows = view.query(options); Assert.assertEquals(1, rows.size()); //1 Assert.assertTrue(Arrays.equals(new Object[]{"green", "model1"}, ((List) rows.get(0).getKey()).toArray())); // Reversed: options = new QueryOptions(); options.setStartKey(Arrays.asList("red", new HashMap<String, Object>())); options.setEndKey(Arrays.asList("green", "model1")); options.setDescending(true); rows = view.query(options); Assert.assertEquals(3, rows.size()); Assert.assertTrue(Arrays.equals(new Object[]{"red", "model2"}, ((List) rows.get(0).getKey()).toArray())); Assert.assertTrue(Arrays.equals(new Object[]{"red", "model1"}, ((List) rows.get(1).getKey()).toArray())); Assert.assertTrue(Arrays.equals(new Object[]{"green", "model1"}, ((List) rows.get(2).getKey()).toArray())); // Reversed, no inclusive end: options.setInclusiveEnd(false); rows = view.query(options); Assert.assertEquals(2, rows.size()); Assert.assertTrue(Arrays.equals(new Object[]{"red", "model2"}, ((List) rows.get(0).getKey()).toArray())); Assert.assertTrue(Arrays.equals(new Object[]{"red", "model1"}, ((List) rows.get(1).getKey()).toArray())); // Specific keys: options = new QueryOptions(); List<Object> keys = new ArrayList<Object>(); keys.add(new Object[]{"red", "model1"}); keys.add(new Object[]{"red", "model2"}); options.setKeys(keys); rows = view.query(options); assertNotNull(rows); Assert.assertEquals(2, rows.size()); Assert.assertTrue(Arrays.equals(new Object[]{"red", "model1"}, ((List) rows.get(0).getKey()).toArray())); Assert.assertTrue(Arrays.equals(new Object[]{"red", "model2"}, ((List) rows.get(1).getKey()).toArray())); } /** * https://github.com/couchbase/couchbase-lite-android/issues/139 * test based on https://github.com/couchbase/couchbase-lite-ios/blob/master/Source/CBL_View_Tests.m#L358 */ public void testViewQueryStartKeyDocID() throws CouchbaseLiteException { putDocs(database); List<RevisionInternal> result = new ArrayList<RevisionInternal>(); Map<String, Object> dict = new HashMap<String, Object>(); dict.put("_id", "11112"); dict.put("key", "one"); result.add(putDoc(database, dict)); View view = createView(database); view.updateIndex(); QueryOptions options = new QueryOptions(); options.setStartKey("one"); options.setStartKeyDocId("11112"); options.setEndKey("three"); List<QueryRow> rows = view.query(options); assertEquals(2, rows.size()); assertEquals("11112", rows.get(0).getDocumentId()); assertEquals("one", rows.get(0).getKey()); assertEquals("33333", rows.get(1).getDocumentId()); assertEquals("three", rows.get(1).getKey()); options = new QueryOptions(); options.setEndKey("one"); options.setEndKeyDocId("11111"); rows = view.query(options); Log.d(TAG, "rows: " + rows); assertEquals(3, rows.size()); assertEquals("55555", rows.get(0).getDocumentId()); assertEquals("five", rows.get(0).getKey()); assertEquals("44444", rows.get(1).getDocumentId()); assertEquals("four", rows.get(1).getKey()); assertEquals("11111", rows.get(2).getDocumentId()); assertEquals("one", rows.get(2).getKey()); options.setStartKey("one"); options.setStartKeyDocId("11111"); rows = view.query(options); assertEquals(1, rows.size()); assertEquals("11111", rows.get(0).getDocumentId()); assertEquals("one", rows.get(0).getKey()); } /** * - (void) test13_NumericKeys in ViewInternal_Tests.m * https://github.com/couchbase/couchbase-lite-android/issues/260 */ public void testViewNumericKeys() throws CouchbaseLiteException { Map<String, Object> dict = new HashMap<String, Object>(); dict.put("_id", "22222"); dict.put("referenceNumber", 33547239); dict.put("title", "this is the title"); putDoc(database, dict); View view = createView(database); view.setMap(new Mapper() { @Override public void map(Map<String, Object> document, Emitter emitter) { if (document.containsKey("referenceNumber")) { emitter.emit(document.get("referenceNumber"), document); } } }, "1"); Query query = view.createQuery(); query.setStartKey(33547239); query.setEndKey(33547239); QueryEnumerator rows = query.run(); assertEquals(1, rows.getCount()); assertEquals(33547239, ((Number)rows.getRow(0).getKey()).intValue()); } public void testAllDocsQuery() throws CouchbaseLiteException { List<RevisionInternal> docs = putDocs(database); List<QueryRow> expectedRow = new ArrayList<QueryRow>(); for (RevisionInternal rev : docs) { Map<String, Object> value = new HashMap<String, Object>(); value.put("rev", rev.getRevID()); value.put("_conflicts", new ArrayList<String>()); QueryRow queryRow = new QueryRow(rev.getDocID(), 0, rev.getDocID(), value, null); expectedRow.add(queryRow); } QueryOptions options = new QueryOptions(); Map<String, Object> allDocs = database.getAllDocs(options); List<QueryRow> expectedRows = new ArrayList<QueryRow>(); expectedRows.add(expectedRow.get(2)); expectedRows.add(expectedRow.get(0)); expectedRows.add(expectedRow.get(3)); expectedRows.add(expectedRow.get(1)); expectedRows.add(expectedRow.get(4)); Map<String, Object> expectedQueryResult = createExpectedQueryResult(expectedRows, 0); Assert.assertEquals(expectedQueryResult, allDocs); // Start/end key query: options = new QueryOptions(); options.setStartKey("2"); options.setEndKey("44444"); allDocs = database.getAllDocs(options); expectedRows = new ArrayList<QueryRow>(); expectedRows.add(expectedRow.get(0)); expectedRows.add(expectedRow.get(3)); expectedRows.add(expectedRow.get(1)); expectedQueryResult = createExpectedQueryResult(expectedRows, 0); Assert.assertEquals(expectedQueryResult, allDocs); // Start/end query without inclusive end: options.setInclusiveEnd(false); allDocs = database.getAllDocs(options); expectedRows = new ArrayList<QueryRow>(); expectedRows.add(expectedRow.get(0)); expectedRows.add(expectedRow.get(3)); expectedQueryResult = createExpectedQueryResult(expectedRows, 0); Assert.assertEquals(expectedQueryResult, allDocs); // Get all documents: with default QueryOptions options = new QueryOptions(); allDocs = database.getAllDocs(options); expectedRows = new ArrayList<QueryRow>(); expectedRows.add(expectedRow.get(2)); expectedRows.add(expectedRow.get(0)); expectedRows.add(expectedRow.get(3)); expectedRows.add(expectedRow.get(1)); expectedRows.add(expectedRow.get(4)); expectedQueryResult = createExpectedQueryResult(expectedRows, 0); Assert.assertEquals(expectedQueryResult, allDocs); // Get specific documents: options = new QueryOptions(); List<Object> docIds = new ArrayList<Object>(); QueryRow expected2 = expectedRow.get(2); docIds.add(expected2.getDocumentId()); //docIds.add(expected2.getDocument().getId()); options.setKeys(docIds); allDocs = database.getAllDocs(options); expectedRows = new ArrayList<QueryRow>(); expectedRows.add(expected2); expectedQueryResult = createExpectedQueryResult(expectedRows, 0); Assert.assertEquals(expectedQueryResult, allDocs); } private Map<String, Object> createExpectedQueryResult(List<QueryRow> rows, int offset) { Map<String, Object> result = new HashMap<String, Object>(); result.put("rows", rows); result.put("total_rows", rows.size()); result.put("offset", offset); return result; } /** * NOTE: ChangeNotification should not be fired for 0 match query. */ public void testAllDocumentsLiveQuery() throws CouchbaseLiteException { final AtomicInteger changeCount = new AtomicInteger(); Database db = startDatabase(); LiveQuery query = db.createAllDocumentsQuery().toLiveQuery(); query.setStartKey("1"); query.setEndKey("10"); query.addChangeListener(new LiveQuery.ChangeListener() { @Override public void changed(LiveQuery.ChangeEvent event) { changeCount.incrementAndGet(); } }); query.start(); query.waitForRows(); assertNull(query.getLastError()); QueryEnumerator rows = query.getRows(); assertNotNull(rows); assertEquals(0, rows.getCount()); // A change event is sent the first time a query finishes loading assertEquals(1, changeCount.get()); db.getDocument("a").createRevision().save(); query.waitForRows(); // A change event is not sent, if the query results remain the same assertEquals(1, changeCount.get()); rows = query.getRows(); assertNotNull(rows); assertEquals(0, rows.getCount()); db.getDocument("1").createRevision().save(); // NOTE: checking changeCount value this timing is inaccurate because of multi-threadings. query.waitForRows(); assertEquals(2, changeCount.get()); rows = query.getRows(); assertNotNull(rows); assertEquals(1, rows.getCount()); assertNull(query.getLastError()); query.stop(); } /** * in ViewInternal_Tests.m * - (void) test16_Reduce */ public void testViewReduce() throws CouchbaseLiteException { Map<String, Object> docProperties1 = new HashMap<String, Object>(); docProperties1.put("_id", "CD"); docProperties1.put("cost", 8.99); putDoc(database, docProperties1); Map<String, Object> docProperties2 = new HashMap<String, Object>(); docProperties2.put("_id", "App"); docProperties2.put("cost", 1.95); putDoc(database, docProperties2); Map<String, Object> docProperties3 = new HashMap<String, Object>(); docProperties3.put("_id", "Dessert"); docProperties3.put("cost", 6.50); putDoc(database, docProperties3); View view = database.getView("totaler"); view.setMapReduce( new Mapper() { @Override public void map(Map<String, Object> document, Emitter emitter) { Assert.assertNotNull(document.get("_id")); Assert.assertNotNull(document.get("_rev")); Object cost = document.get("cost"); if (cost != null) { emitter.emit(document.get("_id"), cost); } } }, new Reducer() { @Override public Object reduce(List<Object> keys, List<Object> values, boolean rereduce) { return View.totalValues(values); } }, "1" ); view.updateIndex(); List<Map<String, Object>> dumpResult = view.dump(); Assert.assertEquals(3, dumpResult.size()); Assert.assertEquals("\"App\"", dumpResult.get(0).get("key")); Assert.assertEquals(1.95, dumpResult.get(0).get("value") instanceof String ? Double.parseDouble((String) dumpResult.get(0).get("value")) : dumpResult.get(0).get("value")); Assert.assertEquals(2, ((Number) dumpResult.get(0).get("seq")).intValue()); Assert.assertEquals("\"CD\"", dumpResult.get(1).get("key")); Assert.assertEquals(8.99, dumpResult.get(1).get("value") instanceof String ? Double.parseDouble((String) dumpResult.get(1).get("value")) : dumpResult.get(1).get("value")); Assert.assertEquals(1, ((Number) dumpResult.get(1).get("seq")).intValue()); Assert.assertEquals("\"Dessert\"", dumpResult.get(2).get("key")); Assert.assertEquals(6.5, dumpResult.get(2).get("value") instanceof String ? Double.parseDouble((String) dumpResult.get(2).get("value")) : dumpResult.get(2).get("value")); Assert.assertEquals(3, ((Number) dumpResult.get(2).get("seq")).intValue()); QueryOptions options = new QueryOptions(); options.setReduce(true); List<QueryRow> reduced = view.query(options); Assert.assertEquals(1, reduced.size()); Object value = reduced.get(0).getValue(); Number numberValue = (Number) value; Assert.assertTrue(Math.abs(numberValue.doubleValue() - 17.44) < 0.001); } public void testIndexUpdateMode() throws CouchbaseLiteException { View view = createView(database); Query query = view.createQuery(); query.setIndexUpdateMode(Query.IndexUpdateMode.BEFORE); int numRowsBefore = query.run().getCount(); assertEquals(0, numRowsBefore); // do a query and force re-indexing, number of results should be +4 putNDocs(database, 1); query.setIndexUpdateMode(Query.IndexUpdateMode.BEFORE); assertEquals(1, query.run().getCount()); // do a query without re-indexing, number of results should be the same putNDocs(database, 4); query.setIndexUpdateMode(Query.IndexUpdateMode.NEVER); assertEquals(1, query.run().getCount()); // do a query and force re-indexing, number of results should be +4 query.setIndexUpdateMode(Query.IndexUpdateMode.BEFORE); assertEquals(5, query.run().getCount()); // do a query which will kick off an async index putNDocs(database, 1); query.setIndexUpdateMode(Query.IndexUpdateMode.AFTER); assertEquals(5, query.run().getCount()); // wait until indexing is (hopefully) done try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } assertEquals(6, query.run().getCount()); } public void testViewGrouped() throws CouchbaseLiteException { Map<String, Object> docProperties1 = new HashMap<String, Object>(); docProperties1.put("_id", "1"); docProperties1.put("artist", "Gang Of Four"); docProperties1.put("album", "Entertainment!"); docProperties1.put("track", "Ether"); docProperties1.put("time", 231); putDoc(database, docProperties1); Map<String, Object> docProperties2 = new HashMap<String, Object>(); docProperties2.put("_id", "2"); docProperties2.put("artist", "Gang Of Four"); docProperties2.put("album", "Songs Of The Free"); docProperties2.put("track", "I Love A Man In Uniform"); docProperties2.put("time", 248); putDoc(database, docProperties2); Map<String, Object> docProperties3 = new HashMap<String, Object>(); docProperties3.put("_id", "3"); docProperties3.put("artist", "Gang Of Four"); docProperties3.put("album", "Entertainment!"); docProperties3.put("track", "Natural's Not In It"); docProperties3.put("time", 187); putDoc(database, docProperties3); Map<String, Object> docProperties4 = new HashMap<String, Object>(); docProperties4.put("_id", "4"); docProperties4.put("artist", "PiL"); docProperties4.put("album", "Metal Box"); docProperties4.put("track", "Memories"); docProperties4.put("time", 309); putDoc(database, docProperties4); Map<String, Object> docProperties5 = new HashMap<String, Object>(); docProperties5.put("_id", "5"); docProperties5.put("artist", "Gang Of Four"); docProperties5.put("album", "Entertainment!"); docProperties5.put("track", "Not Great Men"); docProperties5.put("time", 187); putDoc(database, docProperties5); View view = database.getView("grouper"); view.setMapReduce(new Mapper() { @Override public void map(Map<String, Object> document, Emitter emitter) { List<Object> key = new ArrayList<Object>(); key.add(document.get("artist")); key.add(document.get("album")); key.add(document.get("track")); emitter.emit(key, document.get("time")); } }, new Reducer() { @Override public Object reduce(List<Object> keys, List<Object> values, boolean rereduce) { return View.totalValues(values); } }, "1" ); Status status = new Status(); view.updateIndex(); QueryOptions options = new QueryOptions(); options.setReduce(true); List<QueryRow> rows = view.query(options); assertNotNull(rows); List<Map<String, Object>> expectedRows = new ArrayList<Map<String, Object>>(); Map<String, Object> row1 = new HashMap<String, Object>(); row1.put("key", null); row1.put("value", 1162.0); expectedRows.add(row1); Assert.assertEquals(row1.get("key"), rows.get(0).getKey()); Assert.assertEquals(row1.get("value"), rows.get(0).getValue()); //now group options.setGroup(true); status = new Status(); rows = view.query(options); expectedRows = new ArrayList<Map<String, Object>>(); row1 = new HashMap<String, Object>(); List<String> key1 = new ArrayList<String>(); key1.add("Gang Of Four"); key1.add("Entertainment!"); key1.add("Ether"); row1.put("key", key1); row1.put("value", 231.0); expectedRows.add(row1); Map<String, Object> row2 = new HashMap<String, Object>(); List<String> key2 = new ArrayList<String>(); key2.add("Gang Of Four"); key2.add("Entertainment!"); key2.add("Natural's Not In It"); row2.put("key", key2); row2.put("value", 187.0); expectedRows.add(row2); Map<String, Object> row3 = new HashMap<String, Object>(); List<String> key3 = new ArrayList<String>(); key3.add("Gang Of Four"); key3.add("Entertainment!"); key3.add("Not Great Men"); row3.put("key", key3); row3.put("value", 187.0); expectedRows.add(row3); Map<String, Object> row4 = new HashMap<String, Object>(); List<String> key4 = new ArrayList<String>(); key4.add("Gang Of Four"); key4.add("Songs Of The Free"); key4.add("I Love A Man In Uniform"); row4.put("key", key4); row4.put("value", 248.0); expectedRows.add(row4); Map<String, Object> row5 = new HashMap<String, Object>(); List<String> key5 = new ArrayList<String>(); key5.add("PiL"); key5.add("Metal Box"); key5.add("Memories"); row5.put("key", key5); row5.put("value", 309.0); expectedRows.add(row5); Assert.assertEquals(row1.get("key"), rows.get(0).getKey()); Assert.assertEquals(row1.get("value"), rows.get(0).getValue()); Assert.assertEquals(row2.get("key"), rows.get(1).getKey()); Assert.assertEquals(row2.get("value"), rows.get(1).getValue()); Assert.assertEquals(row3.get("key"), rows.get(2).getKey()); Assert.assertEquals(row3.get("value"), rows.get(2).getValue()); Assert.assertEquals(row4.get("key"), rows.get(3).getKey()); Assert.assertEquals(row4.get("value"), rows.get(3).getValue()); Assert.assertEquals(row5.get("key"), rows.get(4).getKey()); Assert.assertEquals(row5.get("value"), rows.get(4).getValue()); //group level 1 options.setGroupLevel(1); status = new Status(); rows = view.query(options); expectedRows = new ArrayList<Map<String, Object>>(); row1 = new HashMap<String, Object>(); key1 = new ArrayList<String>(); key1.add("Gang Of Four"); row1.put("key", key1); row1.put("value", 853.0); expectedRows.add(row1); row2 = new HashMap<String, Object>(); key2 = new ArrayList<String>(); key2.add("PiL"); row2.put("key", key2); row2.put("value", 309.0); expectedRows.add(row2); Assert.assertEquals(row1.get("key"), rows.get(0).getKey()); Assert.assertEquals(row1.get("value"), rows.get(0).getValue()); Assert.assertEquals(row2.get("key"), rows.get(1).getKey()); Assert.assertEquals(row2.get("value"), rows.get(1).getValue()); //group level 2 options.setGroupLevel(2); status = new Status(); rows = view.query(options); expectedRows = new ArrayList<Map<String, Object>>(); row1 = new HashMap<String, Object>(); key1 = new ArrayList<String>(); key1.add("Gang Of Four"); key1.add("Entertainment!"); row1.put("key", key1); row1.put("value", 605.0); expectedRows.add(row1); row2 = new HashMap<String, Object>(); key2 = new ArrayList<String>(); key2.add("Gang Of Four"); key2.add("Songs Of The Free"); row2.put("key", key2); row2.put("value", 248.0); expectedRows.add(row2); row3 = new HashMap<String, Object>(); key3 = new ArrayList<String>(); key3.add("PiL"); key3.add("Metal Box"); row3.put("key", key3); row3.put("value", 309.0); expectedRows.add(row3); Assert.assertEquals(row1.get("key"), rows.get(0).getKey()); Assert.assertEquals(row1.get("value"), rows.get(0).getValue()); Assert.assertEquals(row2.get("key"), rows.get(1).getKey()); Assert.assertEquals(row2.get("value"), rows.get(1).getValue()); Assert.assertEquals(row3.get("key"), rows.get(2).getKey()); Assert.assertEquals(row3.get("value"), rows.get(2).getValue()); } public void testViewGroupedStrings() throws CouchbaseLiteException { Map<String, Object> docProperties1 = new HashMap<String, Object>(); docProperties1.put("name", "Alice"); putDoc(database, docProperties1); Map<String, Object> docProperties2 = new HashMap<String, Object>(); docProperties2.put("name", "Albert"); putDoc(database, docProperties2); Map<String, Object> docProperties3 = new HashMap<String, Object>(); docProperties3.put("name", "Naomi"); putDoc(database, docProperties3); Map<String, Object> docProperties4 = new HashMap<String, Object>(); docProperties4.put("name", "Jens"); putDoc(database, docProperties4); Map<String, Object> docProperties5 = new HashMap<String, Object>(); docProperties5.put("name", "Jed"); putDoc(database, docProperties5); View view = database.getView("default/names"); view.setMapReduce(new Mapper() { @Override public void map(Map<String, Object> document, Emitter emitter) { String name = (String) document.get("name"); if (name != null) { emitter.emit(name.substring(0, 1), 1); } } }, new Reducer() { @Override public Object reduce(List<Object> keys, List<Object> values, boolean rereduce) { return values.size(); } }, "1.0" ); view.updateIndex(); QueryOptions options = new QueryOptions(); options.setGroupLevel(1); List<QueryRow> rows = view.query(options); assertNotNull(rows); List<Map<String, Object>> expectedRows = new ArrayList<Map<String, Object>>(); Map<String, Object> row1 = new HashMap<String, Object>(); row1.put("key", "A"); row1.put("value", 2); expectedRows.add(row1); Map<String, Object> row2 = new HashMap<String, Object>(); row2.put("key", "J"); row2.put("value", 2); expectedRows.add(row2); Map<String, Object> row3 = new HashMap<String, Object>(); row3.put("key", "N"); row3.put("value", 1); expectedRows.add(row3); Assert.assertEquals(row1.get("key"), rows.get(0).getKey()); Assert.assertEquals(row1.get("value"), rows.get(0).getValue()); Assert.assertEquals(row2.get("key"), rows.get(1).getKey()); Assert.assertEquals(row2.get("value"), rows.get(1).getValue()); Assert.assertEquals(row3.get("key"), rows.get(2).getKey()); Assert.assertEquals(row3.get("value"), rows.get(2).getValue()); } public void testViewGroupedNoReduce() throws CouchbaseLiteException { Map<String, Object> docProperties = new HashMap<String, Object>(); docProperties.put("_id", "1"); docProperties.put("type", "A"); putDoc(database, docProperties); docProperties = new HashMap<String, Object>(); docProperties.put("_id", "2"); docProperties.put("type", "A"); putDoc(database, docProperties); docProperties = new HashMap<String, Object>(); docProperties.put("_id", "3"); docProperties.put("type", "B"); putDoc(database, docProperties); docProperties = new HashMap<String, Object>(); docProperties.put("_id", "4"); docProperties.put("type", "B"); putDoc(database, docProperties); docProperties = new HashMap<String, Object>(); docProperties.put("_id", "5"); docProperties.put("type", "C"); putDoc(database, docProperties); docProperties = new HashMap<String, Object>(); docProperties.put("_id", "6"); docProperties.put("type", "C"); putDoc(database, docProperties); View view = database.getView("GroupByType"); view.setMap(new Mapper() { @Override public void map(Map<String, Object> document, Emitter emitter) { String type = (String) document.get("type"); if (type != null) { emitter.emit(type, null); } } }, "1.0" ); view.updateIndex(); QueryOptions options = new QueryOptions(); //setGroup without reduce function options.setGroupLevel(1); List<QueryRow> rows = view.query(options); assertNotNull(rows); assertEquals(3, rows.size()); Map<String, Object> row1 = new HashMap<String, Object>(); row1.put("key", "A"); row1.put("error", "not_found"); Map<String, Object> row2 = new HashMap<String, Object>(); row2.put("key", "B"); row2.put("error", "not_found"); Map<String, Object> row3 = new HashMap<String, Object>(); row3.put("key", "C"); row3.put("error", "not_found"); Assert.assertEquals(row1.get("key"), rows.get(0).getKey()); Assert.assertEquals(row1.get("error"), rows.get(0).asJSONDictionary().get("error")); Assert.assertEquals(row2.get("key"), rows.get(1).getKey()); Assert.assertEquals(row2.get("error"), rows.get(1).asJSONDictionary().get("error")); Assert.assertEquals(row3.get("key"), rows.get(2).getKey()); Assert.assertEquals(row3.get("error"), rows.get(2).asJSONDictionary().get("error")); } /** * https://github.com/couchbase/couchbase-lite-java-core/issues/413 */ public void testViewGroupedNoReduceWithoutDocs() throws CouchbaseLiteException { View view = database.getView("GroupByType"); view.setMap(new Mapper() { @Override public void map(Map<String, Object> document, Emitter emitter) { String type = (String) document.get("type"); if (type != null) { emitter.emit(type, null); } } }, "1.0" ); view.updateIndex(); QueryOptions options = new QueryOptions(); options.setGroupLevel(1); List<QueryRow> rows = view.query(options); assertNotNull(rows); assertEquals(0, rows.size()); } public void testViewGroupedVariableLengthKey() throws CouchbaseLiteException { Map<String, Object> docProperties1 = new HashMap<String, Object>(); docProperties1.put("_id", "H"); docProperties1.put("atomic_number", 1); docProperties1.put("name", "Hydrogen"); docProperties1.put("electrons", new Integer[]{1}); putDoc(database, docProperties1); Map<String, Object> docProperties2 = new HashMap<String, Object>(); docProperties2.put("_id", "He"); docProperties2.put("atomic_number", 2); docProperties2.put("name", "Helium"); docProperties2.put("electrons", new Integer[]{2}); putDoc(database, docProperties2); Map<String, Object> docProperties3 = new HashMap<String, Object>(); docProperties3.put("_id", "Ne"); docProperties3.put("atomic_number", 10); docProperties3.put("name", "Neon"); docProperties3.put("electrons", new Integer[]{2, 8}); putDoc(database, docProperties3); Map<String, Object> docProperties4 = new HashMap<String, Object>(); docProperties4.put("_id", "Na"); docProperties4.put("atomic_number", 11); docProperties4.put("name", "Sodium"); docProperties4.put("electrons", new Integer[]{2, 8, 1}); putDoc(database, docProperties4); Map<String, Object> docProperties5 = new HashMap<String, Object>(); docProperties5.put("_id", "Mg"); docProperties5.put("atomic_number", 12); docProperties5.put("name", "Magnesium"); docProperties5.put("electrons", new Integer[]{2, 8, 2}); putDoc(database, docProperties5); Map<String, Object> docProperties6 = new HashMap<String, Object>(); docProperties6.put("_id", "Cr"); docProperties6.put("atomic_number", 24); docProperties6.put("name", "Chromium"); docProperties6.put("electrons", new Integer[]{2, 8, 13, 1}); putDoc(database, docProperties6); Map<String, Object> docProperties7 = new HashMap<String, Object>(); docProperties7.put("_id", "Zn"); docProperties7.put("atomic_number", 30); docProperties7.put("name", "Zinc"); docProperties7.put("electrons", new Integer[]{2, 8, 18, 2}); putDoc(database, docProperties7); /* expected key-value pairs at group level 2: [1] -> 1 [2] -> 1 [2, 8] -> 5 */ View view = database.getView("electrons"); view.setMapReduce(new Mapper() { @Override public void map(Map<String, Object> document, Emitter emitter) { emitter.emit(document.get("electrons"), 1); } }, new Reducer() { @Override public Object reduce(List<Object> keys, List<Object> values, boolean rereduce) { return View.totalValues(values); } }, "1" ); view.updateIndex(); QueryOptions options = new QueryOptions(); options.setReduce(true); options.setGroupLevel(2); List<QueryRow> rows = view.query(options); assertNotNull(rows); assertEquals(3, rows.size()); List<Map<String, Object>> expectedRows = new ArrayList<Map<String, Object>>(); Map<String, Object> row1 = new HashMap<String, Object>(); row1.put("key", Arrays.asList(new Integer[]{1})); row1.put("value", 1.0); expectedRows.add(row1); Map<String, Object> row2 = new HashMap<String, Object>(); row2.put("key", Arrays.asList(new Integer[]{2})); row2.put("value", 1.0); expectedRows.add(row2); Map<String, Object> row3 = new HashMap<String, Object>(); row3.put("key", Arrays.asList(new Integer[]{2, 8})); row3.put("value", 5.0); expectedRows.add(row3); Assert.assertEquals(row1.get("key"), toIntegerList((List<Number>) rows.get(0).getKey())); Assert.assertEquals(row1.get("value"), rows.get(0).getValue()); Assert.assertEquals(row2.get("key"), toIntegerList((List<Number>) rows.get(1).getKey())); Assert.assertEquals(row2.get("value"), rows.get(1).getValue()); Assert.assertEquals(row3.get("key"), toIntegerList((List<Number>) rows.get(2).getKey())); Assert.assertEquals(row3.get("value"), rows.get(2).getValue()); } private static List<Integer> toIntegerList(List<Number> src){ if(src == null) return null; List<Integer> dest = new ArrayList<Integer>(src.size()); for(Number n : src){ dest.add(n.intValue()); } return dest; } public void testViewCollation() throws CouchbaseLiteException { List<Object> list1 = new ArrayList<Object>(); list1.add("a"); List<Object> list2 = new ArrayList<Object>(); list2.add("b"); List<Object> list3 = new ArrayList<Object>(); list3.add("b"); list3.add("c"); List<Object> list4 = new ArrayList<Object>(); list4.add("b"); list4.add("c"); list4.add("a"); List<Object> list5 = new ArrayList<Object>(); list5.add("b"); list5.add("d"); List<Object> list6 = new ArrayList<Object>(); list6.add("b"); list6.add("d"); list6.add("e"); // Based on CouchDB's "view_collation.js" test List<Object> testKeys = new ArrayList<Object>(); testKeys.add(null); testKeys.add(false); testKeys.add(true); testKeys.add(0); testKeys.add(2.5); testKeys.add(10); testKeys.add(" "); testKeys.add("_"); testKeys.add("~"); testKeys.add("a"); testKeys.add("A"); testKeys.add("aa"); testKeys.add("b"); testKeys.add("B"); testKeys.add("ba"); testKeys.add("bb"); testKeys.add(list1); testKeys.add(list2); testKeys.add(list3); testKeys.add(list4); testKeys.add(list5); testKeys.add(list6); int i = 0; for (Object key : testKeys) { Map<String, Object> docProperties = new HashMap<String, Object>(); docProperties.put("_id", Integer.toString(i++)); docProperties.put("name", key); putDoc(database, docProperties); } View view = database.getView("default/names"); view.setMapReduce(new Mapper() { @Override public void map(Map<String, Object> document, Emitter emitter) { emitter.emit(document.get("name"), null); } }, null, "1.0"); QueryOptions options = new QueryOptions(); List<QueryRow> rows = view.query(options); i = 0; for (QueryRow row : rows) { Assert.assertEquals(testKeys.get(i++), row.getKey()); } } public void testViewCollationRaw() throws CouchbaseLiteException { List<Object> list1 = new ArrayList<Object>(); list1.add("a"); List<Object> list2 = new ArrayList<Object>(); list2.add("b"); List<Object> list3 = new ArrayList<Object>(); list3.add("b"); list3.add("c"); List<Object> list4 = new ArrayList<Object>(); list4.add("b"); list4.add("c"); list4.add("a"); List<Object> list5 = new ArrayList<Object>(); list5.add("b"); list5.add("d"); List<Object> list6 = new ArrayList<Object>(); list6.add("b"); list6.add("d"); list6.add("e"); // Based on CouchDB's "view_collation.js" test List<Object> testKeys = new ArrayList<Object>(); testKeys.add(0); testKeys.add(2.5); testKeys.add(10); testKeys.add(false); testKeys.add(null); testKeys.add(true); testKeys.add(list1); testKeys.add(list2); testKeys.add(list3); testKeys.add(list4); testKeys.add(list5); testKeys.add(list6); testKeys.add(" "); testKeys.add("A"); testKeys.add("B"); testKeys.add("_"); testKeys.add("a"); testKeys.add("aa"); testKeys.add("b"); testKeys.add("ba"); testKeys.add("bb"); testKeys.add("~"); int i = 0; for (Object key : testKeys) { Map<String, Object> docProperties = new HashMap<String, Object>(); docProperties.put("_id", Integer.toString(i++)); docProperties.put("name", key); putDoc(database, docProperties); } View view = database.getView("default/names"); view.setMapReduce(new Mapper() { @Override public void map(Map<String, Object> document, Emitter emitter) { emitter.emit(document.get("name"), null); } }, null, "1.0"); view.setCollation(TDViewCollation.TDViewCollationRaw); QueryOptions options = new QueryOptions(); List<QueryRow> rows = view.query(options); i = 0; for (QueryRow row : rows) { Assert.assertEquals(testKeys.get(i++), row.getKey()); } database.close(); } public void testLargerViewQuery() throws CouchbaseLiteException { putNDocs(database, 4); View view = createView(database); view.updateIndex(); // Query all rows: QueryOptions options = new QueryOptions(); Status status = new Status(); List<QueryRow> rows = view.query(options); } // http://wiki.apache.org/couchdb/Introduction_to_CouchDB_views#Linked_documents public void testViewLinkedDocs() throws CouchbaseLiteException { putLinkedDocs(database); View view = database.getView("linked"); view.setMapReduce(new Mapper() { @Override public void map(Map<String, Object> document, Emitter emitter) { if (document.containsKey("value")) { emitter.emit(new Object[]{document.get("value"), 0}, null); } if (document.containsKey("ancestors")) { List<Object> ancestors = (List<Object>) document.get("ancestors"); for (int i = 0; i < ancestors.size(); i++) { Map<String, Object> value = new HashMap<String, Object>(); value.put("_id", ancestors.get(i)); emitter.emit(new Object[]{document.get("value"), i + 1}, value); } } } }, null, "1"); view.updateIndex(); QueryOptions options = new QueryOptions(); options.setIncludeDocs(true); // required for linked documents List<QueryRow> rows = view.query(options); Assert.assertNotNull(rows); Assert.assertEquals(5, rows.size()); Object[][] expected = new Object[][]{ /* id, key0, key1, value._id, doc._id */ new Object[]{"22222", "hello", 0, null, "22222"}, new Object[]{"22222", "hello", 1, "11111", "11111"}, new Object[]{"33333", "world", 0, null, "33333"}, new Object[]{"33333", "world", 1, "22222", "22222"}, new Object[]{"33333", "world", 2, "11111", "11111"}, }; for (int i = 0; i < rows.size(); i++) { QueryRow row = rows.get(i); Map<String, Object> rowAsJson = row.asJSONDictionary(); Log.d(TAG, "" + rowAsJson); List<Object> key = (List<Object>) rowAsJson.get("key"); Map<String, Object> doc = (Map<String, Object>) rowAsJson.get("doc"); String id = (String) rowAsJson.get("id"); Assert.assertEquals(expected[i][0], id); Assert.assertEquals(2, key.size()); Assert.assertEquals(expected[i][1], key.get(0)); Assert.assertEquals(expected[i][2], ((Number) key.get(1)).intValue()); if (expected[i][3] == null) { Assert.assertNull(row.getValue()); } else { Assert.assertEquals(expected[i][3], ((Map<String, Object>) row.getValue()).get("_id")); } Assert.assertEquals(expected[i][4], doc.get("_id")); } } /** * https://github.com/couchbase/couchbase-lite-java-core/issues/29 */ public void testRunLiveQueriesWithReduce() throws Exception { final Database db = startDatabase(); // run a live query View view = db.getView("vu"); view.setMapReduce(new Mapper() { @Override public void map(Map<String, Object> document, Emitter emitter) { emitter.emit(document.get("sequence"), 1); } }, new Reducer() { @Override public Object reduce(List<Object> keys, List<Object> values, boolean rereduce) { return View.totalValues(values); } }, "1" ); final LiveQuery query = view.createQuery().toLiveQuery(); View view1 = db.getView("vu1"); view1.setMapReduce(new Mapper() { @Override public void map(Map<String, Object> document, Emitter emitter) { emitter.emit(document.get("sequence"), 1); } }, new Reducer() { @Override public Object reduce(List<Object> keys, List<Object> values, boolean rereduce) { return View.totalValues(values); } }, "1" ); final LiveQuery query1 = view1.createQuery().toLiveQuery(); final int kNDocs = 10; createDocumentsAsync(db, kNDocs); assertNull(query.getRows()); query.start(); final CountDownLatch gotExpectedQueryResult = new CountDownLatch(1); query.addChangeListener(new LiveQuery.ChangeListener() { @Override public void changed(LiveQuery.ChangeEvent event) { if (event.getError() != null) { Log.e(TAG, "LiveQuery change event had error", event.getError()); } else if (event.getRows().getCount() == 1 && ((Double) event.getRows().getRow(0).getValue()).intValue() == kNDocs) { gotExpectedQueryResult.countDown(); } } }); boolean success = gotExpectedQueryResult.await(30, TimeUnit.SECONDS); Assert.assertTrue(success); query.stop(); query1.start(); createDocumentsAsync(db, kNDocs + 5);//10 + 10 + 5 final CountDownLatch gotExpectedQuery1Result = new CountDownLatch(1); query1.addChangeListener(new LiveQuery.ChangeListener() { @Override public void changed(LiveQuery.ChangeEvent event) { if (event.getError() != null) { Log.e(TAG, "LiveQuery change event had error", event.getError()); } else if (event.getRows().getCount() == 1 && ((Double) event.getRows().getRow(0).getValue()).intValue() == 2 * kNDocs + 5) { gotExpectedQuery1Result.countDown(); } } }); success = gotExpectedQuery1Result.await(30, TimeUnit.SECONDS); Assert.assertTrue(success); query1.stop(); assertEquals(2 * kNDocs + 5, db.getDocumentCount()); // 25 - OK } private SavedRevision createTestRevisionNoConflicts(Document doc, String val) throws Exception { UnsavedRevision unsavedRev = doc.createRevision(); Map<String, Object> props = new HashMap<String, Object>(); props.put("key", val); unsavedRev.setUserProperties(props); return unsavedRev.save(); } /** * https://github.com/couchbase/couchbase-lite-java-core/issues/131 */ public void testViewWithConflict() throws Exception { // Create doc and add some revs Document doc = database.createDocument(); SavedRevision rev1 = createTestRevisionNoConflicts(doc, "1"); SavedRevision rev2a = createTestRevisionNoConflicts(doc, "2a"); SavedRevision rev3 = createTestRevisionNoConflicts(doc, "3"); // index the view View view = createView(database); QueryEnumerator rows = view.createQuery().run(); assertEquals(1, rows.getCount()); QueryRow row = rows.next(); assertEquals(row.getKey(), "3"); // assertNotNull(row.getDocumentRevisionId()); -- TODO: why is this null? // Create a conflict UnsavedRevision rev2bUnsaved = rev1.createRevision(); Map<String, Object> props = new HashMap<String, Object>(); props.put("key", "2b"); rev2bUnsaved.setUserProperties(props); SavedRevision rev2b = rev2bUnsaved.save(true); // re-run query view.updateIndex(); rows = view.createQuery().run(); // we should only see one row, with key=3. // if we see key=2b then it's a bug. assertEquals(1, rows.getCount()); row = rows.next(); assertEquals(row.getKey(), "3"); } /** * https://github.com/couchbase/couchbase-lite-java-core/issues/226 */ public void testViewSecondQuery() throws Exception { // Create doc and add some revs final Document doc = database.createDocument(); String jsonString = "{\n" + " \"name\":\"praying mantis\",\n" + " \"wikipedia\":{\n" + " \"behavior\":{\n" + " \"style\":\"predatory\",\n" + " \"attack\":\"ambush\"\n" + " },\n" + " \"evolution\":{\n" + " \"ancestor\":\"proto-roaches\",\n" + " \"cousin\":\"termite\"\n" + " } \n" + " } \n" + "\n" + "}"; Map jsonObject = (Map) Manager.getObjectMapper().readValue(jsonString, Object.class); doc.putProperties(jsonObject); View view = database.getView("testViewSecondQueryView"); view.setMapReduce(new Mapper() { @Override public void map(Map<String, Object> document, Emitter emitter) { if (document.get("name") != null) { emitter.emit(document.get("name"), document); } } }, null, "1"); for (int i = 0; i < 2; i++) { Query query = view.createQuery(); QueryEnumerator rows = query.run(); for (Iterator<QueryRow> it = rows; it.hasNext(); ) { QueryRow row = it.next(); Map wikipediaField = (Map) row.getDocument().getProperty("wikipedia"); assertTrue(wikipediaField.containsKey("behavior")); assertTrue(wikipediaField.containsKey("evolution")); Map behaviorField = (Map) wikipediaField.get("behavior"); assertTrue(behaviorField.containsKey("style")); assertTrue(behaviorField.containsKey("attack")); } } } public void testStringPrefixMatch() throws Exception { putDocs(database); View view = createView(database); view.updateIndex(); QueryOptions options = new QueryOptions(); options.setPrefixMatchLevel(1); options.setStartKey("f"); options.setEndKey("f"); List<QueryRow> rows = view.query(options); Assert.assertEquals(2, rows.size()); Assert.assertEquals("five", rows.get(0).getKey()); Assert.assertEquals("four", rows.get(1).getKey()); } public void testArrayPrefixMatch() throws Exception { putDocs(database); View view = database.getView("aview"); view.setMapReduce(new Mapper() { @Override public void map(Map<String, Object> document, Emitter emitter) { Assert.assertNotNull(document.get("_id")); Assert.assertNotNull(document.get("_rev")); String key = (String) document.get("key"); if (key != null) { String first = key.substring(0, 1); Log.w(TAG, "first=%s", first); emitter.emit(Arrays.asList(first, key), null); } } }, null, "1"); view.updateIndex(); QueryOptions options = new QueryOptions(); options.setPrefixMatchLevel(1); options.setStartKey(Arrays.asList("f")); options.setEndKey(options.getStartKey()); List<QueryRow> rows = view.query(options); assertNotNull(rows); Assert.assertEquals(2, rows.size()); Assert.assertEquals(Arrays.asList("f", "five"), rows.get(0).getKey()); Assert.assertEquals(Arrays.asList("f", "four"), rows.get(1).getKey()); } public void testAllDocsPrefixMatch() throws CouchbaseLiteException { database.getDocument("aaaaaaa").createRevision().save(); database.getDocument("a11zzzzz").createRevision().save(); database.getDocument("a七乃又直ந்த").createRevision().save(); database.getDocument("A1").createRevision().save(); database.getDocument("bcd").createRevision().save(); database.getDocument("01234").createRevision().save(); QueryOptions options = new QueryOptions(); options.setPrefixMatchLevel(1); options.setStartKey("a"); options.setEndKey("a"); Map<String, Object> result = database.getAllDocs(options); assertNotNull(result); List<QueryRow> rows = (List<QueryRow>) result.get("rows"); assertNotNull(rows); // 1 < a < 七 - order is ascending by default assertEquals(3, rows.size()); assertEquals("a11zzzzz", rows.get(0).getKey()); assertEquals("aaaaaaa", rows.get(1).getKey()); assertEquals("a七乃又直ந்த", rows.get(2).getKey()); } /** * in View_Tests.m * - (void) test06_ViewCustomFilter * * https://github.com/couchbase/couchbase-lite-java-core/issues/303 */ public void testViewCustomFilter() throws Exception { View view = database.getView("vu"); view.setMapReduce(new Mapper() { @Override public void map(Map<String, Object> document, Emitter emitter) { emitter.emit(document.get("name"), document.get("skin")); } }, null, "1"); Map<String, Object> docProperties1 = new HashMap<String, Object>(); docProperties1.put("name", "Barry"); docProperties1.put("skin", "none"); putDoc(database, docProperties1); Map<String, Object> docProperties2 = new HashMap<String, Object>(); docProperties2.put("name", "Terry"); docProperties2.put("skin", "furry"); putDoc(database, docProperties2); Map<String, Object> docProperties3 = new HashMap<String, Object>(); docProperties3.put("name", "Wanda"); docProperties3.put("skin", "scaly"); putDoc(database, docProperties3); // match all Query query = view.createQuery(); Predicate<QueryRow> postFilterAll = new Predicate<QueryRow>() { public boolean apply(QueryRow type) { return true; } }; query.setPostFilter(postFilterAll); QueryEnumerator rows = query.run(); assertEquals(3, rows.getCount()); for (int i = 0; i < rows.getCount(); i++) { Log.d(Log.TAG_QUERY, "" + rows.getRow(i).getKey() + " => " + rows.getRow(i).getValue()); } assertEquals(docProperties1.get("name"), rows.getRow(0).getKey()); assertEquals(docProperties1.get("skin"), rows.getRow(0).getValue()); assertEquals(docProperties2.get("name"), rows.getRow(1).getKey()); assertEquals(docProperties2.get("skin"), rows.getRow(1).getValue()); assertEquals(docProperties3.get("name"), rows.getRow(2).getKey()); assertEquals(docProperties3.get("skin"), rows.getRow(2).getValue()); // match zero Predicate<QueryRow> postFilterNone = new Predicate<QueryRow>() { public boolean apply(QueryRow type) { return false; } }; query.setPostFilter(postFilterNone); rows = query.run(); assertEquals(0, rows.getCount()); // match two Predicate<QueryRow> postFilter = new Predicate<QueryRow>() { public boolean apply(QueryRow type) { if (type.getValue() instanceof String) { String val = (String) type.getValue(); if (val != null && val.endsWith("y")) { return true; } } return false; } }; query.setPostFilter(postFilter); rows = query.run(); assertEquals(2, rows.getCount()); assertEquals(docProperties2.get("name"), rows.getRow(0).getKey()); assertEquals(docProperties2.get("skin"), rows.getRow(0).getValue()); assertEquals(docProperties3.get("name"), rows.getRow(1).getKey()); assertEquals(docProperties3.get("skin"), rows.getRow(1).getValue()); } /** * in View_Tests.m * - (void) test06_AllDocsCustomFilter * * https://github.com/couchbase/couchbase-lite-java-core/issues/303 */ public void testAllDocsCustomFilter() throws Exception { Map<String, Object> docProperties1 = new HashMap<String, Object>(); docProperties1.put("_id", "1"); docProperties1.put("name", "Barry"); docProperties1.put("skin", "none"); putDoc(database, docProperties1); Map<String, Object> docProperties2 = new HashMap<String, Object>(); docProperties2.put("_id", "2"); docProperties2.put("name", "Terry"); docProperties2.put("skin", "furry"); putDoc(database, docProperties2); Map<String, Object> docProperties3 = new HashMap<String, Object>(); docProperties3.put("_id", "3"); docProperties3.put("name", "Wanda"); docProperties3.put("skin", "scaly"); putDoc(database, docProperties3); database.clearDocumentCache(); Log.d(TAG, "---- QUERYIN' ----"); Query query = database.createAllDocumentsQuery(); query.setPostFilter(new Predicate<QueryRow>() { public boolean apply(QueryRow type) { if (type.getDocument().getProperty("skin") != null && type.getDocument().getProperty("skin") instanceof String) { String skin = (String) type.getDocument().getProperty("skin"); if (skin.endsWith("y")) { return true; } } return false; } }); QueryEnumerator rows = query.run(); assertEquals(2, rows.getCount()); assertEquals(docProperties2.get("_id"), rows.getRow(0).getKey()); assertEquals(docProperties3.get("_id"), rows.getRow(1).getKey()); } public void testQueryEnumerationImplementsIterable() { assertTrue(new QueryEnumerator(null, new ArrayList<QueryRow>(), 0) instanceof Iterable); } /** * int ViewInternal_Tests.m * - (void) test_ConflictWinner */ public void testConflictWinner() throws CouchbaseLiteException { // If a view is re-indexed, and a document in the view has gone into conflict, // rows emitted by the earlier 'losing' revision shouldn't appear in the view. List<RevisionInternal> docs = putDocs(database); RevisionInternal leaf1 = docs.get(1); View view = createView(database); view.updateIndex(); List<Map<String, Object>> dump = view.dump(); Log.d(TAG, "View dump: " + dump); Assert.assertEquals(5, dump.size()); Assert.assertEquals("\"five\"", dump.get(0).get("key")); Assert.assertEquals(5, ((Number)dump.get(0).get("seq")).intValue()); Assert.assertEquals("\"four\"", dump.get(1).get("key")); Assert.assertEquals(2, ((Number)dump.get(1).get("seq")).intValue()); Assert.assertEquals("\"one\"", dump.get(2).get("key")); Assert.assertEquals(3, ((Number)dump.get(2).get("seq")).intValue()); Assert.assertEquals("\"three\"", dump.get(3).get("key")); Assert.assertEquals(4, ((Number)dump.get(3).get("seq")).intValue()); Assert.assertEquals("\"two\"", dump.get(4).get("key")); Assert.assertEquals(1, ((Number)dump.get(4).get("seq")).intValue()); // Create a conflict, won by the new revision: Map<String, Object> props = new HashMap<String, Object>(); props.put("_id", "44444"); //props.put("_rev", "1-~~~~~"); // higher revID, will win conflict props.put("_rev", "1-ffffff"); // higher revID, will win conflict props.put("key", "40ur"); RevisionInternal leaf2 = new RevisionInternal(props); database.forceInsert(leaf2, new ArrayList<String>(), null); Assert.assertEquals(leaf1.getDocID(), leaf2.getDocID()); // Update the view -- should contain only the key from the new rev, not the old: view.updateIndex(); dump = view.dump(); Log.d(TAG, "View dump: " + dump); Assert.assertEquals(5, dump.size()); Assert.assertEquals("\"40ur\"", dump.get(0).get("key")); Assert.assertEquals(6, ((Number)dump.get(0).get("seq")).intValue()); Assert.assertEquals("\"five\"", dump.get(1).get("key")); Assert.assertEquals(5, ((Number)dump.get(1).get("seq")).intValue()); Assert.assertEquals("\"one\"", dump.get(2).get("key")); Assert.assertEquals(3, ((Number)dump.get(2).get("seq")).intValue()); Assert.assertEquals("\"three\"", dump.get(3).get("key")); Assert.assertEquals(4, ((Number)dump.get(3).get("seq")).intValue()); Assert.assertEquals("\"two\"", dump.get(4).get("key")); Assert.assertEquals(1, ((Number)dump.get(4).get("seq")).intValue()); } /** * int ViewInternal_Tests.m * - (void) test_ConflictWinner * * https://github.com/couchbase/couchbase-lite-android/issues/494 */ public void testConflictLoser() throws CouchbaseLiteException { // Like the ConflictWinner test, except the newer revision is the loser, // so it shouldn't be indexed at all. Instead, the older still-winning revision // should be indexed again. List<RevisionInternal> docs = putDocs(database); RevisionInternal leaf1 = docs.get(1); View view = createView(database); view.updateIndex(); List<Map<String, Object>> dump = view.dump(); Log.d(TAG, "View dump: " + dump); Assert.assertEquals(5, dump.size()); Assert.assertEquals("\"five\"", dump.get(0).get("key")); Assert.assertEquals(5, ((Number)dump.get(0).get("seq")).intValue()); Assert.assertEquals("\"four\"", dump.get(1).get("key")); Assert.assertEquals(2, ((Number)dump.get(1).get("seq")).intValue()); Assert.assertEquals("\"one\"", dump.get(2).get("key")); Assert.assertEquals(3, ((Number)dump.get(2).get("seq")).intValue()); Assert.assertEquals("\"three\"", dump.get(3).get("key")); Assert.assertEquals(4, ((Number)dump.get(3).get("seq")).intValue()); Assert.assertEquals("\"two\"", dump.get(4).get("key")); Assert.assertEquals(1, ((Number)dump.get(4).get("seq")).intValue()); // Create a conflict, won by the new revision: Map<String, Object> props = new HashMap<String, Object>(); props.put("_id", "44444"); //props.put("_rev", "1-...."); // lower revID, will lose conflict props.put("_rev", "1-0000"); // lower revID, will lose conflict props.put("key", "40ur"); RevisionInternal leaf2 = new RevisionInternal(props); database.forceInsert(leaf2, new ArrayList<String>(), null); Assert.assertEquals(leaf1.getDocID(), leaf2.getDocID()); // Update the view -- should contain only the key from the new rev, not the old: view.updateIndex(); dump = view.dump(); Log.d(TAG, "View dump: " + dump); Assert.assertEquals(5, dump.size()); Assert.assertEquals("\"five\"", dump.get(0).get("key")); Assert.assertEquals(5, ((Number)dump.get(0).get("seq")).intValue()); Assert.assertEquals("\"four\"", dump.get(1).get("key")); Assert.assertEquals(2, ((Number)dump.get(1).get("seq")).intValue()); Assert.assertEquals("\"one\"", dump.get(2).get("key")); Assert.assertEquals(3, ((Number)dump.get(2).get("seq")).intValue()); Assert.assertEquals("\"three\"", dump.get(3).get("key")); Assert.assertEquals(4, ((Number)dump.get(3).get("seq")).intValue()); Assert.assertEquals("\"two\"", dump.get(4).get("key")); Assert.assertEquals(1, ((Number)dump.get(4).get("seq")).intValue()); } /** * https://github.com/couchbase/couchbase-lite-android/issues/494 * * in ViewInternal_tests.m * - (void) test_IndexingOlderRevision */ public void testIndexingOlderRevision() throws CouchbaseLiteException { // In case conflictWinner was deleted, conflict loser should be indexed. // create documents List<RevisionInternal> docs = putDocs(database); RevisionInternal leaf1 = docs.get(1); Assert.assertEquals("four", database.getDocument("44444").getProperty("key")); // update index View view = createView(database); view.updateIndex(); List<Map<String, Object>> dump = view.dump(); Log.d(TAG, "View dump: " + dump); Assert.assertEquals(5, dump.size()); Assert.assertEquals("\"five\"", dump.get(0).get("key")); Assert.assertEquals(5, ((Number)dump.get(0).get("seq")).intValue()); Assert.assertEquals("\"four\"", dump.get(1).get("key")); Assert.assertEquals(2, ((Number)dump.get(1).get("seq")).intValue()); Assert.assertEquals("\"one\"", dump.get(2).get("key")); Assert.assertEquals(3, ((Number)dump.get(2).get("seq")).intValue()); Assert.assertEquals("\"three\"", dump.get(3).get("key")); Assert.assertEquals(4, ((Number)dump.get(3).get("seq")).intValue()); Assert.assertEquals("\"two\"", dump.get(4).get("key")); Assert.assertEquals(1, ((Number)dump.get(4).get("seq")).intValue()); // Create a conflict, won by the new revision: Map<String, Object> props = new HashMap<String, Object>(); props.put("_id", "44444"); props.put("_rev", "1-FFFFFFFF"); // higher revID, will win conflict props.put("key", "40ur"); RevisionInternal leaf2 = new RevisionInternal(props); database.forceInsert(leaf2, new ArrayList<String>(), null); Assert.assertEquals(leaf1.getDocID(), leaf2.getDocID()); Assert.assertEquals("40ur", database.getDocument("44444").getProperty("key")); // Update the view -- should contain only the key from the new rev, not the old: view.updateIndex(); dump = view.dump(); Log.d(TAG, "View dump: " + dump); Assert.assertEquals(5, dump.size()); Assert.assertEquals("\"40ur\"", dump.get(0).get("key")); Assert.assertEquals(6, ((Number)dump.get(0).get("seq")).intValue()); Assert.assertEquals("\"five\"", dump.get(1).get("key")); Assert.assertEquals(5, ((Number)dump.get(1).get("seq")).intValue()); Assert.assertEquals("\"one\"", dump.get(2).get("key")); Assert.assertEquals(3, ((Number)dump.get(2).get("seq")).intValue()); Assert.assertEquals("\"three\"", dump.get(3).get("key")); Assert.assertEquals(4, ((Number)dump.get(3).get("seq")).intValue()); Assert.assertEquals("\"two\"", dump.get(4).get("key")); Assert.assertEquals(1, ((Number)dump.get(4).get("seq")).intValue()); // create new revision with delete RevisionInternal leaf3 = new RevisionInternal("44444", null, true); leaf3 = database.putRevision(leaf3, leaf2.getRevID(), true); Assert.assertEquals(leaf1.getDocID(), leaf3.getDocID()); Assert.assertEquals(true, leaf3.isDeleted()); Assert.assertEquals("four", database.getDocument("44444").getProperty("key")); view.updateIndex(); dump = view.dump(); Log.d(TAG, "View dump: " + dump); Assert.assertEquals(5, dump.size()); long forSeq = (isSQLiteDB()?leaf1.getSequence():leaf3.getSequence()); Assert.assertEquals("\"five\"", dump.get(0).get("key")); Assert.assertEquals(5, ((Number)dump.get(0).get("seq")).intValue()); Assert.assertEquals("\"four\"", dump.get(1).get("key")); Assert.assertEquals(forSeq, ((Number) dump.get(1).get("seq")).intValue()); Assert.assertEquals("\"one\"", dump.get(2).get("key")); Assert.assertEquals(3, ((Number)dump.get(2).get("seq")).intValue()); Assert.assertEquals("\"three\"", dump.get(3).get("key")); Assert.assertEquals(4, ((Number)dump.get(3).get("seq")).intValue()); Assert.assertEquals("\"two\"", dump.get(4).get("key")); Assert.assertEquals(1, ((Number)dump.get(4).get("seq")).intValue()); } /** * LiveQuery should re-run query from scratch after options are changed * https://github.com/couchbase/couchbase-lite-ios/issues/596 * <p/> * in View_Tests.m * - (void) test13_LiveQuery_UpdateWhenQueryOptionsChanged */ public void testLiveQueryUpdateWhenQueryOptionsChanged() throws CouchbaseLiteException, InterruptedException { View view = database.getView("vu"); view.setMap(new Mapper() { @Override public void map(Map<String, Object> document, Emitter emitter) { emitter.emit(document.get("sequence"), null); } }, "1"); createDocuments(database, 5); Query query = view.createQuery(); QueryEnumerator rows = query.run(); assertEquals(5, rows.getCount()); int expectedKey = 0; for (QueryRow row : rows) { assertEquals(((Number)row.getKey()).intValue(), expectedKey++); } LiveQuery liveQuery = view.createQuery().toLiveQuery(); final CountDownLatch latch1 = new CountDownLatch(1); final CountDownLatch latch2 = new CountDownLatch(2); liveQuery.addChangeListener(new LiveQuery.ChangeListener() { @Override public void changed(LiveQuery.ChangeEvent event) { Log.d(TAG, "---- changed ---"); latch1.countDown(); latch2.countDown(); } }); liveQuery.start(); boolean success1 = latch1.await(3, TimeUnit.SECONDS); assertTrue(success1); rows = liveQuery.getRows(); assertNotNull(rows); assertEquals(5, rows.getCount()); expectedKey = 0; for (QueryRow row : rows) { assertEquals(((Number)row.getKey()).intValue(), expectedKey++); } liveQuery.setStartKey(2); liveQuery.queryOptionsChanged(); boolean success2 = latch2.await(3, TimeUnit.SECONDS); assertTrue(success2); rows = liveQuery.getRows(); assertNotNull(rows); assertEquals(3, rows.getCount()); expectedKey = 2; for (QueryRow row : rows) { assertEquals(((Number)row.getKey()).intValue(), expectedKey++); } liveQuery.stop(); } /** * Tests that when converting from a Query to a LiveQuery, all properties are preserved. * More speficially tests that the Query copy constructor copies all fields. * * Regression test for couchbase/couchbase-lite-java-core#585. */ public void testQueryToLiveQuery() throws Exception { View view = database.getView("vu"); Query query = view.createQuery(); query.setAllDocsMode(Query.AllDocsMode.INCLUDE_DELETED); assertTrue(query.shouldIncludeDeleted()); query.setPrefetch(true); query.setPrefixMatchLevel(1); query.setStartKey(Arrays.asList("hello", "wo")); query.setStartKey(Arrays.asList("hello", "wo", Collections.EMPTY_MAP)); query.setKeys(Arrays.<Object>asList("1", "5", "aaaaa")); query.setGroupLevel(2); query.setPrefixMatchLevel(2); query.setStartKeyDocId("1"); query.setEndKeyDocId("123456789"); query.setDescending(true); query.setLimit(10); query.setIndexUpdateMode(Query.IndexUpdateMode.NEVER); query.setSkip(2); query.setPostFilter(new Predicate<QueryRow>() { @Override public boolean apply(QueryRow type) { return true; } }); query.setMapOnly(true); // Lets also test the copy constructor itself // But first ensure our assumptions hold if (Modifier.isAbstract(Query.class.getModifiers())) { fail("Assumption failed: test needs to be updated"); } if (Query.class.getSuperclass() != Object.class) { // sameFields(Object, Object) does not compare fields of superclasses fail("Assumption failed: test needs to be updated"); } Constructor<Query> copyConstructor = null; try { copyConstructor = Query.class.getDeclaredConstructor(Database.class, Query.class); } catch (NoSuchMethodException e) { fail("Copy constructor not found"); } if (Modifier.isPrivate(copyConstructor.getModifiers())) { fail("Copy constructor is private"); } // Constructor is package protected copyConstructor.setAccessible(true); // Now make some copies LiveQuery copy1 = query.toLiveQuery(); Query copy2 = copyConstructor.newInstance(query.getDatabase(), query); sameFields(query, copy1); sameFields(query, copy2); } private static <T> void sameFields(T expected, T actual) throws Exception { // Compare the fields in the expected class (Query) // and not the ones in actual, as it may be a subclass (LiveQuery) assertNotNull(actual); Class<?> clazz = expected.getClass(); Field[] fields = clazz.getDeclaredFields(); boolean compared = false; for (Field field : fields) { if (Modifier.isStatic(field.getModifiers())) { continue; } field.setAccessible(true); Object expectedObj = field.get(expected); Object actualObj = field.get(actual); assertEquals(expectedObj, actualObj); compared = true; } assertTrue("No fields to compare?!?", compared); } public void testDeleteIndexTest() { View newView = database.getView("newview"); newView.deleteIndex(); newView.setMap( new Mapper() { @Override public void map(Map<String, Object> documentProperties, Emitter emitter) { String documentId = (String) documentProperties.get("_id"); emitter.emit(documentId, "Document id is " + documentId); } }, "1" ); Query query = database.getView("newview").createQuery(); QueryEnumerator queryResult = null; try { queryResult = query.run(); } catch (CouchbaseLiteException e) { Log.e(TAG, "Failed to run Query.run() for indexDeleted", e); fail("Failed to run Query.run() for indexDeleted"); } for (Iterator<QueryRow> it = queryResult.iterator(); it.hasNext(); ) { QueryRow row = it.next(); Log.i("deleteIndexTest", (String) row.getValue()); } } /** * View update skips winning revisions * https://github.com/couchbase/couchbase-lite-java-core/issues/709 */ public void testViewUpdateSkipsWinningRevisions() throws CouchbaseLiteException{ // seq | doc_id | revid | parent | current | deleted // -----+--------+-------+--------+---------+-------- // 2753 | 172 | 1-6b | NULL | 0 | 0 // 2754 | 172 | 2-1a | 2753 | 0 | 0 // 2761 | 172 | 3-d7 | 2754 | 0 | 0 // 2763 | 172 | 3-06 | 2754 | 1 | 0 // 2764 | 172 | 4-80 | 2761 | 1 | 1 // create view View view = createView(database); // crete doc Map<String, Object> props = new HashMap<String, Object>(); props.put("_id", "172"); props.put("key", "1"); RevisionInternal rev1 = new RevisionInternal(props); RevisionInternal leaf1 = database.putRevision(rev1, null, false); Log.i(TAG, String.format(Locale.ENGLISH, "leaf1: seq=%d, doc_id=%s, rev_id=%s deleted=%s", leaf1.getSequence(), leaf1.getDocID(), leaf1.getRevID(), leaf1.isDeleted() ? "true" : "false")); // Need to override StoreDelegate to control revision ID for generation 2-. Store store = database.getStore(); StoreDelegate delegate = store.getDelegate(); // set Revision ID "2-0002" store.setDelegate(new StoreDelegate() { @Override public void storageExitedTransaction(boolean committed) { } @Override public void databaseStorageChanged(DocumentChange change) { } @Override public String generateRevID(byte[] json, boolean deleted, String prevRevID) { return "2-0002"; } @Override public boolean runFilter(ReplicationFilter filter, Map<String, Object> filterParams, RevisionInternal rev) { return false; } }); // create conflicts rev2a and rev2b props.put("_rev", leaf1.getRevID()); props.put("key", "2a"); RevisionInternal rev2a = new RevisionInternal(props); RevisionInternal leaf2a = database.putRevision(rev2a, leaf1.getRevID(), true); Log.i(TAG, String.format(Locale.ENGLISH, "leaf2a: seq=%d, doc_id=%s, rev_id=%s deleted=%s", leaf2a.getSequence(), leaf2a.getDocID(), leaf2a.getRevID(), leaf2a.isDeleted() ? "true" : "false")); // set Revision ID "2-0001" which is same generation but lower revision ID than previous one store.setDelegate(new StoreDelegate() { @Override public void storageExitedTransaction(boolean committed) { } @Override public void databaseStorageChanged(DocumentChange change) { } @Override public String generateRevID(byte[] json, boolean deleted, String prevRevID) { return "2-0001"; } @Override public boolean runFilter(ReplicationFilter filter, Map<String, Object> filterParams, RevisionInternal rev) { return false; } }); props.put("key", "2b"); RevisionInternal rev2b = new RevisionInternal(props); RevisionInternal leaf2b = database.putRevision(rev2b, leaf1.getRevID(), true); Log.i(TAG, String.format(Locale.ENGLISH, "leaf2b: seq=%d, doc_id=%s, rev_id=%s deleted=%s", leaf2b.getSequence(), leaf2b.getDocID(), leaf2b.getRevID(), leaf2b.isDeleted() ? "true" : "false")); store.setDelegate(delegate); // make sure which rev wins RevisionInternal winning = leaf2a.getRevID().compareTo(leaf2b.getRevID()) > 0 ? leaf2a : leaf2b; // Should be leaf2a RevisionInternal looser = leaf2a.getRevID().compareTo(leaf2b.getRevID()) < 0 ? leaf2a : leaf2b; // Should be leaf2b assertEquals(leaf2a.getRevID(), winning.getRevID()); assertEquals(leaf2b.getRevID(), looser.getRevID()); // update index view.updateIndex(); List<QueryRow> rows = view.query(null); Log.i(TAG, rows.toString()); assertEquals(winning.getProperties().get("key"), rows.get(0).getKey()); // Delete winning rev RevisionInternal leaf3 = new RevisionInternal("172", null, true); leaf3 = database.putRevision(leaf3, winning.getRevID(), true); Log.i(TAG, String.format(Locale.ENGLISH, "leaf3: seq=%d, doc_id=%s, rev_id=%s deleted=%s", leaf3.getSequence(), leaf3.getDocID(), leaf3.getRevID(), leaf3.isDeleted() ? "true" : "false")); // update index view.updateIndex(); rows = view.query(null); Log.i(TAG, rows.toString()); assertEquals(looser.getProperties().get("key"), rows.get(0).getKey()); } /** * Views broken with concurrent update and delete * https://github.com/couchbase/couchbase-lite-java-core/issues/952 */ public void testViewUpdateWinningRevisionIsNotIndexed() throws CouchbaseLiteException { // // revid 3-c should be indexed with folloiwng condition. // NOTE: As 3-c < 3-d (deleted), So this might cause indexing problem // // seq | doc_id | revid | parent | current | deleted // -----+--------+-------+--------+---------+-------- // 1 | doc1 | 1-x | NULL | 0 | 0 // 2 | doc1 | 2-a | 1 | 0 | 0 // 3 | doc1 | 2-b | 1 | 0 | 0 // 4 | doc1 | 3-c | 2 | 1 | 0 // 5 | doc1 | 3-d | 3 | 1 | 1 // create view View view = createView(database); // crete doc Map<String, Object> props = new HashMap<String, Object>(); props.put("_id", "doc1"); props.put("key", "1-x"); RevisionInternal rev1 = new RevisionInternal(props); RevisionInternal leaf1 = database.putRevision(rev1, null, false); Log.i(TAG, String.format(Locale.ENGLISH, "leaf1: seq=%d, doc_id=%s, rev_id=%s deleted=%s", leaf1.getSequence(), leaf1.getDocID(), leaf1.getRevID(), leaf1.isDeleted() ? "true" : "false")); // create conflicts rev2a and rev2b props.put("_rev", leaf1.getRevID()); props.put("key", "2-a"); RevisionInternal rev2a = new RevisionInternal(props); RevisionInternal leaf2a = database.putRevision(rev2a, leaf1.getRevID(), true); Log.i(TAG, String.format(Locale.ENGLISH, "leaf2a: seq=%d, doc_id=%s, rev_id=%s deleted=%s", leaf2a.getSequence(), leaf2a.getDocID(), leaf2a.getRevID(), leaf2a.isDeleted() ? "true" : "false")); props.put("key", "2-b"); RevisionInternal rev2b = new RevisionInternal(props); RevisionInternal leaf2b = database.putRevision(rev2b, leaf1.getRevID(), true); Log.i(TAG, String.format(Locale.ENGLISH, "leaf2b: seq=%d, doc_id=%s, rev_id=%s deleted=%s", leaf2b.getSequence(), leaf2b.getDocID(), leaf2b.getRevID(), leaf2b.isDeleted() ? "true" : "false")); // update index view.updateIndex(); List<QueryRow> rows = view.query(null); assertNotNull(rows); Log.i(TAG, rows.toString()); assertEquals(1, rows.size()); // one 2 must win // Need to override StoreDelegate to control revision ID for generation 2-. Store store = database.getStore(); // set Revision ID "3-cccc" store.setDelegate(new StoreDelegate() { @Override public void storageExitedTransaction(boolean committed) { } @Override public void databaseStorageChanged(DocumentChange change) { } @Override public String generateRevID(byte[] json, boolean deleted, String prevRevID) { return "3-cccc";// 3-c is not appropriate revision id for forestdb } @Override public boolean runFilter(ReplicationFilter filter, Map<String, Object> filterParams, RevisionInternal rev) { return false; } }); // create rev3c from rev2a props.put("_rev", leaf1.getRevID()); props.put("key", "3-c"); RevisionInternal rev3c = new RevisionInternal(props); RevisionInternal leaf3c = database.putRevision(rev3c, leaf2a.getRevID(), true); Log.i(TAG, String.format(Locale.ENGLISH, "leaf3c: seq=%d, doc_id=%s, rev_id=%s deleted=%s", leaf3c.getSequence(), leaf3c.getDocID(), leaf3c.getRevID(), leaf3c.isDeleted() ? "true" : "false")); // set Revision ID "3-dddd" store.setDelegate(new StoreDelegate() { @Override public void storageExitedTransaction(boolean committed) { } @Override public void databaseStorageChanged(DocumentChange change) { } @Override public String generateRevID(byte[] json, boolean deleted, String prevRevID) { return "3-dddd"; // 3-d is not appropriate revision id for forestdb } @Override public boolean runFilter(ReplicationFilter filter, Map<String, Object> filterParams, RevisionInternal rev) { return false; } }); // create rev3d from rev2b with delete RevisionInternal leaf3d = new RevisionInternal("doc1", null, true); leaf3d = database.putRevision(leaf3d, leaf2b.getRevID(), true); Log.i(TAG, String.format(Locale.ENGLISH, "leaf3d: seq=%d, doc_id=%s, rev_id=%s deleted=%s", leaf3d.getSequence(), leaf3d.getDocID(), leaf3d.getRevID(), leaf3d.isDeleted() ? "true" : "false")); assertTrue(leaf3d.isDeleted()); // make sure 3-d is higher revision than 3-c assertTrue(leaf3d.getRevID().compareTo(leaf3c.getRevID()) > 0); // update index again ... now we must receive 3-c view.updateIndex(); rows = view.query(null); assertNotNull(rows); Log.i(TAG, rows.toString()); assertEquals(1, rows.size()); assertEquals("3-c", rows.get(0).getKey()); } // test21_TotalRows in View_Tests.m public void testTotalRows() throws Exception { View view = database.getView("vu"); assertNotNull(view); view.setMap(new Mapper() { @Override public void map(Map<String, Object> document, Emitter emitter) { emitter.emit(document.get("sequence"), null); } }, "1"); assertNotNull(view.getMap()); assertEquals(0, view.getTotalRows()); // Add 20 documents: createDocuments(database, 20); assertTrue(view.isStale()); assertEquals(20, view.getTotalRows()); assertTrue(!view.isStale()); // Add another 20 documents, query, and check totalRows: createDocuments(database, 20); Query query = view.createQuery(); QueryEnumerator rows = query.run(); assertEquals(40, rows.getCount()); assertEquals(40, view.getTotalRows()); } // test04_IndexMultiple in ViewInternal_Tests.m public void testIndexMultipleViews() throws Exception { View v1 = createView(database, "agroup/view1"); View v2 = createView(database, "other/view2"); View v3 = createView(database, "other/view3"); View vX = createView(database, "other/viewX"); View v4 = createView(database, "view4"); View v5 = createView(database, "view5"); View[] v1Groups = sortViews(v1.getViewsInGroup()); assertTrue(Arrays.equals(new View[] {v1}, v1Groups)); View[] v2Groups = sortViews(v2.getViewsInGroup()); assertTrue(Arrays.equals(new View[] {v2, v3, vX}, v2Groups)); View[] v3Groups = sortViews(v3.getViewsInGroup()); assertTrue(Arrays.equals(new View[] {v2, v3, vX}, v3Groups)); View[] vXGroups = sortViews(vX.getViewsInGroup()); assertTrue(Arrays.equals(new View[] {v2, v3, vX}, vXGroups)); View[] v4Groups = sortViews(v4.getViewsInGroup()); assertTrue(Arrays.equals(new View[] {v4}, v4Groups)); View[] v5Groups = sortViews(v5.getViewsInGroup()); assertTrue(Arrays.equals(new View[] {v5}, v5Groups)); final int numDocs = 10; for (int i = 0; i < numDocs; i++) { Map <String, Object> props = new HashMap<String, Object>(); props.put("key", i); putDoc(database, props); if (i == numDocs/2) { Status status = v1.updateIndex(); assertEquals(Status.OK, status.getCode()); } } Status status = v2.updateIndexAlone(); assertEquals(Status.OK, status.getCode()); status = v2.updateIndex(); assertEquals(Status.NOT_MODIFIED, status.getCode()); List<View> views = Arrays.asList(new View[] {v1, v2, v3}); status = v3.updateIndexes(views); assertEquals(Status.OK, status.getCode()); for (View view : new View[] {v2, v3}) { assertEquals(numDocs, view.getLastSequenceIndexed()); } } private View[] sortViews(List<View> views) { List<View> result = new ArrayList<View>(views); Collections.sort(result, new Comparator<View>() { @Override public int compare(View lhs, View rhs) { return lhs.getName().compareTo(rhs.getName()); } }); return result.toArray(new View[result.size()]); } public void testIndexMultipleViewsDifferentMaps() throws Exception { View view1 = database.getView("a/1"); view1.setMap(new Mapper() { @Override public void map(Map<String, Object> document, Emitter emitter) { emitter.emit(document.get("_id"), "a/1"); } }, "1"); View view2 = database.getView("a/2"); view2.setMap(new Mapper() { @Override public void map(Map<String, Object> document, Emitter emitter) { emitter.emit(document.get("_id"), "a/2"); } }, "1"); View view3 = database.getView("b/1"); view3.setMap(new Mapper() { @Override public void map(Map<String, Object> document, Emitter emitter) { emitter.emit(document.get("_id"), "b/1"); } }, "1"); createDocuments(database, 5); assertTrue(view1.isStale()); assertTrue(view2.isStale()); assertTrue(view3.isStale()); Status status = view1.updateIndex(); assertEquals(Status.OK, status.getCode()); status = view2.updateIndex(); assertEquals(Status.NOT_MODIFIED, status.getCode()); status = view3.updateIndex(); assertEquals(Status.OK, status.getCode()); assertEquals(5, view1.getTotalRows()); assertEquals(5, view2.getTotalRows()); assertEquals(5, view3.getTotalRows()); Query query1 = view1.createQuery(); QueryEnumerator rows1 = query1.run(); assertEquals(5, rows1.getCount()); while (rows1.hasNext()) { QueryRow row = rows1.next(); assertEquals("a/1", row.getValue()); } Query query2 = view2.createQuery(); QueryEnumerator rows2 = query2.run(); assertEquals(5, rows1.getCount()); while (rows2.hasNext()) { QueryRow row = rows2.next(); assertEquals("a/2", row.getValue()); } Query query3 = view3.createQuery(); QueryEnumerator rows3 = query3.run(); assertEquals(5, rows1.getCount()); while (rows3.hasNext()) { QueryRow row = rows3.next(); assertEquals("b/1", row.getValue()); } } // test20_DocTypes in View_Tests.m public void testDocTypes() throws Exception { View view1 = database.getView("test/peepsNames"); view1.setDocumentType("person"); view1.setMap(new Mapper() { @Override public void map(Map<String, Object> document, Emitter emitter) { assertEquals("person", document.get("type")); emitter.emit(document.get("name"), null); } }, "1"); View view2 = database.getView("test/aardvarks"); view2.setDocumentType("aardvark"); view2.setMap(new Mapper() { @Override public void map(Map<String, Object> document, Emitter emitter) { assertEquals("aardvark", document.get("type")); emitter.emit(document.get("name"), null); } }, "1"); Map<String, Object> properties = new HashMap<String, Object>(); properties.put("type", "person"); properties.put("name", "mick"); createDocWithProperties(properties); properties = new HashMap<String, Object>(); properties.put("type", "person"); properties.put("name", "keef"); createDocWithProperties(properties); properties.put("type", "aardvark"); properties.put("name", "cerebus"); final Document doc = createDocWithProperties(properties); Query query = view1.createQuery(); QueryEnumerator rows = query.run(); assertEquals(2, rows.getCount()); assertEquals("keef", rows.getRow(0).getKey()); assertEquals("mick", rows.getRow(1).getKey()); query = view2.createQuery(); rows = query.run(); assertEquals(1, rows.getCount()); assertEquals("cerebus", rows.getRow(0).getKey()); // Make sure that documents that are updated to no longer match the view's documentType get // removed from its index: SavedRevision rev = doc.update(new Document.DocumentUpdater() { @Override public boolean update(UnsavedRevision rev) { Map<String, Object> props = rev.getProperties(); props.put("type", "person"); rev.setProperties(props); return true; } }); assertNotNull(rev); rows = query.run(); assertEquals(0, rows.getCount()); // Make sure a view without a docType will coexist: properties = new HashMap<String, Object>(); properties.put("type", "elf"); properties.put("name", "regency elf"); createDocWithProperties(properties); View view3 = database.getView("test/all"); view3.setMap(new Mapper() { @Override public void map(Map<String, Object> document, Emitter emitter) { emitter.emit(document.get("name"), null); } }, "1"); query = view3.createQuery(); rows = query.run(); assertEquals(4, rows.getCount()); } // test22_MapFn_Conflicts in View_Tests.m public void testMapFnConflicts() throws Exception { View view = database.getView("vu"); assertNotNull(view); view.setMap(new Mapper() { @Override public void map(Map<String, Object> document, Emitter emitter) { emitter.emit(document.get("_id"), document.get("_conflicts")); } }, "1"); assertNotNull(view.getMap()); Map <String, Object> properties = new HashMap<String, Object>(); properties.put("foo", "bar"); Document doc = createDocWithProperties(properties); SavedRevision rev1 = doc.getCurrentRevision(); properties = new HashMap<String, Object>(doc.getProperties()); properties.put("tag", "1"); SavedRevision rev2a = doc.putProperties(properties); assertNotNull(rev2a); // No conflicts: Query query = view.createQuery(); QueryEnumerator rows = query.run(); assertEquals(1, rows.getCount()); QueryRow row = rows.getRow(0); assertEquals(doc.getId(), row.getKey()); assertNull(row.getValue()); // Create a conflict revision: properties = new HashMap<String, Object>(rev1.getProperties()); properties.put("tag", "2"); UnsavedRevision newRev = rev1.createRevision(); newRev.setProperties(properties); SavedRevision rev2b = newRev.save(true); assertNotNull(rev2b); rows = query.run(); assertEquals(1, rows.getCount()); row = rows.getRow(0); assertEquals(doc.getId(), row.getKey()); List<String> v = (List<String>)row.getValue(); assertNotNull(v); String conflictRevID; if (rev2b.getId().equals(doc.getCurrentRevisionId())) conflictRevID = rev2a.getId(); else conflictRevID = rev2b.getId(); assertTrue(Arrays.equals(new String[] {conflictRevID}, v.toArray(new String[v.size()]))); // Create another conflict revision: properties = new HashMap<String, Object>(rev1.getProperties()); properties.put("tag", "3"); newRev = rev1.createRevision(); newRev.setProperties(properties); SavedRevision rev2c = newRev.save(true); assertNotNull(rev2c); rows = query.run(); assertEquals(1, rows.getCount()); row = rows.getRow(0); assertEquals(doc.getId(), row.getKey()); v = (List<String>)row.getValue(); assertNotNull(v); // _conflicts in the map function are sorted by RevID desc. Therefore // the order will depend on the actual RevID string content. // // Currently there is an issue that the RevIDs generated on different Android APIs may // not be the same (https://github.com/couchbase/couchbase-lite-java-core/issues/878). // As a result we couldn't guarantee the order or the conflicting RevIDs when writing // the assertion tests. // // Workaround: sort the conflicting revs IDs for comparison: List<String> conflictRevs = new ArrayList<String>(); for (SavedRevision rev : doc.getConflictingRevisions()) { if (!rev.getId().equals(doc.getCurrentRevisionId())) conflictRevs.add(rev.getId()); } String[] conflicts = conflictRevs.toArray(new String[conflictRevs.size()]); Arrays.sort(conflicts); String[] _conflicts = v.toArray(new String[v.size()]); Arrays.sort(_conflicts); assertTrue(Arrays.equals(conflicts, _conflicts)); } public void testViewNameWithSpecialChars() throws Exception { // NOTE: On Windows, following characters are not allowed to use as folder name: "<>:\"/\\|?*" // view name start with period "." => Not Allow with forestdb View v1 = database.getView(".view"); if (isSQLiteDB()) assertNotNull(v1); else assertNull(v1); // view name that includes colon ":" => Not Allow with forestdb View v2 = database.getView("group:view"); if (isSQLiteDB()) assertNotNull(v2); else assertNull(v2); // view name that includes forward slash "/" => Allow View v3 = database.getView("group/view"); assertNotNull(v3); // view name that includes forward slash "\" View v4 = database.getView("group\\view"); assertNotNull(v4); // view name that includes less than & larger than "<>" View v5 = database.getView("<group>view"); assertNotNull(v5); // view name that includes double quote '"' View v6 = database.getView("\"group\"view"); assertNotNull(v6); // view name that includes vertical bar '|' View v7 = database.getView("group|view"); assertNotNull(v7); // view name that includes question mark '?' View v8 = database.getView("group?view"); assertNotNull(v8); // view name that includes asterisk '*' View v9 = database.getView("group*view"); assertNotNull(v9); } // https://github.com/couchbase/couchbase-lite-ios/issues/1082 public void test23_ViewWithDocDeletion() throws CouchbaseLiteException { _testViewWithDocDeletionOrPurge(false); } public void test24_ViewWithDocPurge() throws CouchbaseLiteException { _testViewWithDocDeletionOrPurge(true); } public void _testViewWithDocDeletionOrPurge(boolean purge) throws CouchbaseLiteException { View view = database.getView("vu"); assertNotNull(view); view.setMap(new Mapper() { @Override public void map(Map<String, Object> document, Emitter emitter) { if ("task".equals(document.get("type"))) { List<Object> keys = new ArrayList<Object>(); keys.add(document.get("list_id")); keys.add(document.get("created_at")); emitter.emit(keys, document); } } }, "1"); assertNotNull(view.getMap()); assertEquals(0, view.getCurrentTotalRows()); String listId = "list1"; // Create 3 documents: Map<String, Object> props1 = new HashMap<String, Object>(); props1.put("_id", "doc1"); props1.put("type", "task"); props1.put("created_at", "2016-01-29T22:25:01.000Z"); props1.put("list_id", listId); Document doc1 = createDocWithProperties(props1); Map<String, Object> props2 = new HashMap<String, Object>(); props2.put("_id", "doc2"); props2.put("type", "task"); props2.put("created_at", "2016-01-29T22:25:02.000Z"); props2.put("list_id", listId); Document doc2 = createDocWithProperties(props2); Map<String, Object> props3 = new HashMap<String, Object>(); props3.put("_id", "doc3"); props3.put("type", "task"); props3.put("created_at", "2016-01-29T22:25:03.000Z"); props3.put("list_id", listId); Document doc3 = createDocWithProperties(props3); // Check query result: Query query = view.createQuery(); query.setDescending(true); List<Object> startKeys = new ArrayList<Object>(); startKeys.add(listId); startKeys.add(new HashMap<String, Object>()); query.setStartKey(startKeys); List<Object> endKeys = new ArrayList<Object>(); endKeys.add(listId); query.setEndKey(endKeys); QueryEnumerator rows = query.run(); Log.i(TAG, "First query: rows.getCount() = " + rows.getCount()); assertEquals(3, rows.getCount()); assertEquals(doc3.getId(), rows.getRow(0).getDocumentId()); assertEquals(doc2.getId(), rows.getRow(1).getDocumentId()); assertEquals(doc1.getId(), rows.getRow(2).getDocumentId()); // Delete doc2: assertNotNull(doc2); if (purge) doc2.purge(); else assertTrue(doc2.delete()); Log.i(TAG, "Deleted doc2"); // Check ascending query result: query.setDescending(false); query.setStartKey(endKeys); query.setEndKey(startKeys); rows = query.run(); Log.i(TAG, "Ascending query: rows.getCount() = " + rows.getCount()); assertEquals(2, rows.getCount()); assertEquals(doc1.getId(), rows.getRow(0).getDocumentId()); assertEquals(doc3.getId(), rows.getRow(1).getDocumentId()); // Check descending query result: query.setDescending(true); query.setStartKey(startKeys); query.setEndKey(endKeys); rows = query.run(); Log.i(TAG, "Descending query: rows.getCount() = " + rows.getCount()); assertEquals(2, rows.getCount()); assertEquals(doc3.getId(), rows.getRow(0).getDocumentId()); assertEquals(doc1.getId(), rows.getRow(1).getDocumentId()); } /** * https://github.com/couchbase/couchbase-lite-java-core/issues/971 * * ForestDB: Query with descending order against empty db */ public void testViewDecendingOrderWithEmptyDB() throws Exception { View view = database.getView("vu"); assertNotNull(view); view.setMap(new Mapper() { @Override public void map(Map<String, Object> document, Emitter emitter) { if ("task".equals(document.get("type"))) { List<Object> keys = new ArrayList<Object>(); keys.add(document.get("list_id")); keys.add(document.get("created_at")); emitter.emit(keys, document); } } }, "1"); assertNotNull(view.getMap()); assertEquals(0, view.getCurrentTotalRows()); String listId = "list1"; // Check ascending query result: Query query = view.createQuery(); query.setDescending(false); List<Object> startKeys = new ArrayList<Object>(); startKeys.add(listId); startKeys.add(new HashMap<String, Object>()); List<Object> endKeys = new ArrayList<Object>(); endKeys.add(listId); query.setStartKey(endKeys); query.setEndKey(startKeys); QueryEnumerator rows = query.run(); Log.i(TAG, "Ascending query: rows.getCount() = " + rows.getCount()); assertEquals(0, rows.getCount()); // Check descending query result: query.setDescending(true); query.setStartKey(startKeys); query.setEndKey(endKeys); rows = query.run(); Log.i(TAG, "Descending query: rows.getCount() = " + rows.getCount()); assertEquals(0, rows.getCount()); } // - (void) test28_Nil_Key public void testEmitWithNullKey() throws Exception { // set up view View view = database.getView("vu"); assertNotNull(view); view.setMap(new Mapper() { @Override public void map(Map<String, Object> document, Emitter emitter) { // null key -> ignored emitter.emit(null, "foo"); emitter.emit("name", "bar"); } }, "1"); assertNotNull(view.getMap()); assertEquals(0, view.getCurrentTotalRows()); // insert 1 doc Map<String, Object> dict = new HashMap<String, Object>(); dict.put("_id", "11111"); assertNotNull(putDoc(database, dict)); // regular query Query query = view.createQuery(); assertNotNull(query); QueryEnumerator e = query.run(); assertNotNull(e); assertEquals(1, e.getCount()); QueryRow row = e.getRow(0); assertNotNull(row); assertEquals("name", row.getKey()); assertEquals("bar", row.getValue()); // query with null key. it should be ignored. this caused exception previously for sqlite query.setKeys(Collections.singletonList(null)); e = query.run(); assertNotNull(e); assertEquals(0, e.getCount()); } /** * Issue: https://github.com/couchbase/couchbase-lite-android/issues/709 * Documentation: http://developer.couchbase.com/documentation/mobile/1.2/develop/references/couchbase-lite/couchbase-lite/query/query/index.html#boolean-includedeleted--get-set- */ public void testAllDocsWithIncludeDeleted() throws CouchbaseLiteException { // create 5 docs putDocs(database); // create view View view = createView(database); // create regular query Query regQuery = view.createQuery(); // create all docs query Query allQuery = database.createAllDocumentsQuery(); // Regular query: should return 5 result QueryEnumerator e = regQuery.run(); assertEquals(5, e.getCount()); // All docs query: should return 5 result e = allQuery.run(); assertEquals(5, e.getCount()); // delete one doc Document doc = database.getDocument("33333"); doc.delete(); // Regular query: should return 4 result e = regQuery.run(); assertEquals(4, e.getCount()); // All docs query: should return 4 result e = allQuery.run(); assertEquals(4, e.getCount()); // Regular query: should return 4 result as IncludedDeleted does not effect for reg query regQuery.setIncludeDeleted(true); e = regQuery.run(); assertEquals(4, e.getCount()); // All docs query: should return 5 result allQuery.setIncludeDeleted(true); e = allQuery.run(); assertEquals(5, e.getCount()); } public void testViewWithCompoundKeys() throws CouchbaseLiteException { // Setup View View view = database.getView("client_by_name_device"); if (view != null) { view.setMap(new Mapper() { @Override public void map(Map<String, Object> document, Emitter emitter) { /* Emit data to matieralized view */ if (document.get("type") != null && "client".equals(document.get("type")) && "device".equals(document.get("origin")) && document.get("name") != null && document.get("phone_mobile") != null && document.get("time_delete") == null && document.get("device_imei") != null ) { HashMap<String, Object> value = new HashMap<String, Object>(); value.put("_id", document.get("_id")); emitter.emit(Arrays.asList( document.get("device_imei"), document.get("name") ), null); } } }, "1"); } final int num_docs = 200; // Insert Documents database.runInTransaction(new TransactionalTask() { @Override public boolean run() { try { for (int i = 0; i < num_docs; i++) { Map<String, Object> props = new HashMap<String, Object>(); props.put("time_create", new Date()); props.put("phone_mobile", String.format(Locale.ENGLISH, "*3816400%04d", i)); props.put("name", String.format(Locale.ENGLISH, "Miloš Micić - Micke - %d", i)); props.put("origin", "device"); props.put("id_origin", "266392"); props.put("type", "client"); props.put("device_imei", "352584060971220"); Document doc = database.createDocument(); doc.putProperties(props); } Map<String, Object> props = new HashMap<String, Object>(); props.put("time_create", new Date()); props.put("phone_mobile", String.format(Locale.ENGLISH, "*3816400%04d", 1)); props.put("name", String.format(Locale.ENGLISH, "Miloš Micić - Micke - %d", 1)); props.put("origin", "couchdb"); props.put("type", "client"); Document doc = database.createDocument(); doc.putProperties(props); props = new HashMap<String, Object>(); props.put("time_create", new Date()); props.put("phone_mobile", String.format(Locale.ENGLISH, "*3816400%04d", 1)); props.put("name", String.format(Locale.ENGLISH, "Miloš Micić - Micke - %d", 1)); props.put("type", "client"); props.put("device_imei", "352584060971220"); doc = database.createDocument(); doc.putProperties(props); props = new HashMap<String, Object>(); props.put("time_create", new Date()); props.put("phone_mobile", String.format(Locale.ENGLISH, "*3816400%04d", 1)); props.put("name", String.format(Locale.ENGLISH, "Miloš Micić - Micke - %d", 1)); props.put("type", "test_type"); props.put("device_imei", "352584060971220"); doc = database.createDocument(); doc.putProperties(props); } catch (CouchbaseLiteException e) { Log.e(TAG, "Failed to store document", e); return false; } return true; } }); // Query for (int i = 0; i < 2; i++) { String deviceIMEI = "352584060971220"; Query orderedQuery = view.createQuery(); orderedQuery.setStartKey(Arrays.asList(deviceIMEI, null)); orderedQuery.setEndKey(Arrays.asList(deviceIMEI, new HashMap<String, Object>())); Log.v(TAG, String.format(Locale.ENGLISH, "orderedQuery.run()")); QueryEnumerator results = orderedQuery.run(); /* Iterate through the rows to get the document ids */ int counter = 0; for (Iterator<QueryRow> it = results; it.hasNext(); ) { QueryRow row = it.next(); Log.v(TAG, String.format(Locale.ENGLISH, "[%d] Document ID: %s added to view %s", counter, row.getDocumentId(), "client_by_name_device")); counter++; } assertEquals(num_docs, counter); Log.v(TAG, String.format(Locale.ENGLISH, "Iterrator done")); } } /** * https://github.com/couchbase/couchbase-lite-android/issues/846 */ public void testSerbianClientNameQuery() throws CouchbaseLiteException { // Setup View View view = database.getView("client_by_name"); if (view != null) { view.setMap(new Mapper() { @Override public void map(Map<String, Object> document, Emitter emitter) { /* Emit data to matieralized view */ if (document.get("type") != null && "client".equals(document.get("type")) && document.get("name") != null) { emitter.emit(document.get("name"), null); } } }, "1"); } final String[] names = { "Nenad Stojnic", "Milos Micic", "Nenad Stojnić", "Miloš Micić", "Ненад Стојнић", "Милош Мицић", "øæåÆØÅ", "↪", "日本語" }; // Insert Documents database.runInTransaction(new TransactionalTask() { @Override public boolean run() { try { for (int i = 0; i < names.length; i++) { Map<String, Object> props = new HashMap<String, Object>(); props.put("time_create", new Date()); props.put("phone_mobile", String.format(Locale.ENGLISH, "*3816400%04d", i)); props.put("name", names[i]); props.put("origin", "device"); props.put("id_origin", "266392"); props.put("type", "client"); props.put("device_imei", "352584060971220"); Document doc = database.createDocument(); doc.putProperties(props); } } catch (CouchbaseLiteException e) { Log.e(TAG, "Failed to store document", e); return false; } return true; } }); // Query by Key for (int i = 0; i < names.length; i++) { Query orderedQuery = view.createQuery(); orderedQuery.setStartKey(names[i]); orderedQuery.setEndKey(names[i]); Log.v(TAG, String.format("orderedQuery.run()")); QueryEnumerator results = orderedQuery.run(); /* Iterate through the rows to get the document ids */ int counter = 0; for (Iterator<QueryRow> it = results; it.hasNext(); ) { QueryRow row = it.next(); Log.e(TAG, String.format(Locale.ENGLISH, "[%d] Document ID: %s added to view %s", counter, row.getDocumentId(), "client_by_name")); assertEquals(names[i], (String) row.getKey()); counter++; } assertEquals(1, counter); Log.v(TAG, String.format(Locale.ENGLISH, "Iterrator done")); } } /* * https://github.com/couchbase/couchbase-lite-java-core/issues/1249 */ public void testPurge() throws IOException, CouchbaseLiteException { View view = database.getView("bookmarks"); view.setMap(new Mapper() { @Override public void map(Map<String, Object> document, Emitter emitter) { String type = (String) document.get("type"); if (type.equals("bookmark")) { String name = (String) document.get("name"); emitter.emit(name, null); } } }, "1"); Document document1 = database.createDocument(); Map<String, Object> properties = new HashMap<String, Object>(); properties.put("type", "bookmark"); properties.put("name", "test"); document1.putProperties(properties); Document document2 = database.createDocument(); properties = new HashMap<String, Object>(); properties.put("type", "tag"); properties.put("name", "sample"); document2.putProperties(properties); Query query = database.createAllDocumentsQuery(); query.setAllDocsMode(Query.AllDocsMode.INCLUDE_DELETED); QueryEnumerator enumerator = query.run(); assertEquals(2, enumerator.getCount()); query = view.createQuery(); enumerator = query.run(); assertEquals(1, enumerator.getCount()); assertEquals("test", enumerator.next().getDocument().getProperty("name")); document1.purge(); document2.purge(); query = database.createAllDocumentsQuery(); query.setAllDocsMode(Query.AllDocsMode.INCLUDE_DELETED); enumerator = query.run(); assertEquals(0, enumerator.getCount()); query = view.createQuery(); enumerator = query.run(); assertEquals(0, enumerator.getCount()); } // TODO: test fails on Linux (Jenkins), needs to investigate // NOTE: May not be fixed. public void failingTestJapaneseAsKey() throws CouchbaseLiteException { // Setup View View view = database.getView("japanese"); if (view != null) { view.setMap(new Mapper() { @Override public void map(Map<String, Object> document, Emitter emitter) { emitter.emit(document.get("key"), null); } }, "1"); } final String[] keys = {"日本語", "あ", "い"}; final String[] expected = {"あ", "い", "日本語"}; // Insert Documents database.runInTransaction(new TransactionalTask() { @Override public boolean run() { try { for (int i = 0; i < keys.length; i++) { Map<String, Object> props = new HashMap<String, Object>(); props.put("key", keys[i]); Document doc = database.createDocument(); doc.putProperties(props); } } catch (CouchbaseLiteException e) { Log.e(TAG, "Failed to store document", e); return false; } return true; } }); // Query by Key QueryEnumerator results = view.createQuery().run(); int i = 0; for (Iterator<QueryRow> it = results; it.hasNext(); ) { QueryRow row = it.next(); Log.e(TAG, "row=" + row.asJSONDictionary()); assertEquals(expected[i], row.getKey()); i++; } assertEquals(expected.length, i); } // https://github.com/couchbase/couchbase-lite-android/issues/967 public void testSetSkip() throws CouchbaseLiteException { Map<String, Object> props = new HashMap<String, Object>(); props.put("data", "ok"); database.getDocument("123").putProperties(props); database.getDocument("147").putProperties(props); database.getDocument("258").putProperties(props); database.getDocument("369").putProperties(props); database.getDocument("456").putProperties(props); database.getDocument("789").putProperties(props); Query query = database.createAllDocumentsQuery(); query.setPrefetch(true); query.setSkip(2); QueryEnumerator result = query.run(); CountDown countDown = new CountDown(4); Log.i(TAG, "start"); while (result.hasNext()) { String docID = result.next().getDocumentId(); Log.i(TAG, "docID=" + docID); assertTrue(docID.compareTo("258") >= 0); // 258, 369, 456, and 789 countDown.countDown(); } Log.i(TAG, "stop"); assertEquals(0, countDown.getCount()); } public void testSetLimit() throws CouchbaseLiteException { Map<String, Object> props = new HashMap<String, Object>(); props.put("data", "ok"); database.getDocument("123").putProperties(props); database.getDocument("147").putProperties(props); database.getDocument("258").putProperties(props); database.getDocument("369").putProperties(props); database.getDocument("456").putProperties(props); database.getDocument("789").putProperties(props); Query query = database.createAllDocumentsQuery(); query.setPrefetch(true); query.setLimit(3); QueryEnumerator result = query.run(); CountDown countDown = new CountDown(3); // limit is 3 Log.i(TAG, "start"); while (result.hasNext()) { String docID = result.next().getDocumentId(); Log.i(TAG, "docID=" + docID); assertTrue(docID.compareTo("258") <= 0); countDown.countDown(); } Log.i(TAG, "stop"); assertEquals(0, countDown.getCount()); } public void testSetSkipAndLimit() throws CouchbaseLiteException { Map<String, Object> props = new HashMap<String, Object>(); props.put("data", "ok"); database.getDocument("123").putProperties(props); database.getDocument("147").putProperties(props); database.getDocument("258").putProperties(props); database.getDocument("369").putProperties(props); database.getDocument("456").putProperties(props); database.getDocument("789").putProperties(props); Query query = database.createAllDocumentsQuery(); query.setPrefetch(true); query.setSkip(2); query.setLimit(3); QueryEnumerator result = query.run(); CountDown countDown = new CountDown(3); // limit is 3 Log.i(TAG, "start"); while (result.hasNext()) { String docID = result.next().getDocumentId(); Log.i(TAG, "docID=" + docID); assertTrue(docID.compareTo("258") >= 0 && docID.compareTo("456") <= 0); countDown.countDown(); } Log.i(TAG, "stop"); assertEquals(0, countDown.getCount()); } /** * From https://github.com/couchbase/couchbase-lite-android/issues/969#issuecomment-244562943 * Adapted from testParallelViewQueries * Tests for https://github.com/couchbase/couchbase-lite-android/issues/969 */ public void testMultipleLiveQueries() { _testMultipleLiveQueries(""); } public void testMultipleLiveQueriesWithPrefix() { _testMultipleLiveQueries("prefix/"); } public void _testMultipleLiveQueries(String prefix) { final int batch_size = 50; final int n_batches = 4; LiveQuery lvu = null; LiveQuery lvu2 = null; try { // First LiveQuery View vu = database.getView(prefix + "vu"); vu.setMap(new Mapper() { @Override public void map(Map<String, Object> document, Emitter emitter) { Map<String, Object> key = new HashMap<String, Object>(); key.put("sequence", document.get("sequence")); emitter.emit(key, null); } }, "1.0"); Query qvu = vu.createQuery(); lvu = qvu.toLiveQuery(); List<Object> keys = new ArrayList<Object>(); Map<String, Object> key = new HashMap<String, Object>(); key.put("sequence", batch_size - 1); keys.add(key); lvu.setKeys(keys); final CountDownLatch latch = new CountDownLatch(1); lvu.addChangeListener(new LiveQuery.ChangeListener() { @Override public void changed(LiveQuery.ChangeEvent event) { assertNull(event.getError()); QueryEnumerator rows = event.getRows(); assertNotNull(rows); Log.w(TAG, String.format(Locale.ENGLISH, "Change Event - Count: %d - SN: %d", rows.getCount(), rows.getSequenceNumber())); while (rows.hasNext()) { QueryRow row = rows.next(); String str = row.getDocument().getProperties().toString(); Log.w(TAG, String.format(Locale.ENGLISH, "Doc: %s", str)); } //indexed up to the last sequence number without errors if (event.getRows().getSequenceNumber() >= batch_size * n_batches) { Log.w(TAG, String.format(Locale.ENGLISH, "releasing latch")); latch.countDown(); } else { Log.w(TAG, String.format(Locale.ENGLISH, "Not releasing latch")); } } }); lvu.start(); // Second LiveQuery View vu2 = database.getView(prefix + "vu2"); vu2.setMap(new Mapper() { @Override public void map(Map<String, Object> document, Emitter emitter) { Map<String, Object> key = new HashMap<String, Object>(); key.put("sequence", "FAKE"); emitter.emit(key, null); } }, "1.0"); lvu2 = vu2.createQuery().toLiveQuery(); lvu2.addChangeListener(new LiveQuery.ChangeListener() { @Override public void changed(LiveQuery.ChangeEvent event) { } }); lvu2.start(); // Create 100 documents 4 times -> total 400 documents for (int i = 0; i < n_batches; i++) { // 4 times Log.w(TAG, "Adding documents"); createDocuments(database, batch_size); // 100 } Log.w(TAG, "Waiting for livequery to receive the last update"); try { assertTrue(latch.await(60, TimeUnit.SECONDS)); } catch (InterruptedException e) { Log.e(TAG, "Error in CountDownLatch", e); } } finally { // Stop LiveQueries if (lvu2 != null) lvu2.stop(); if (lvu != null) lvu.stop(); } } }