/*
* Copyright (c) 2016 Couchbase, Inc.
*
* 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
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.couchbase.client.java;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.TimeUnit;
import com.couchbase.client.core.CouchbaseException;
import com.couchbase.client.java.document.JsonDocument;
import com.couchbase.client.java.document.RawJsonDocument;
import com.couchbase.client.java.document.json.JsonArray;
import com.couchbase.client.java.document.json.JsonObject;
import com.couchbase.client.java.error.DesignDocumentDoesNotExistException;
import com.couchbase.client.java.error.ViewDoesNotExistException;
import com.couchbase.client.java.util.CouchbaseTestContext;
import com.couchbase.client.java.view.AsyncViewResult;
import com.couchbase.client.java.view.AsyncViewRow;
import com.couchbase.client.java.view.DefaultView;
import com.couchbase.client.java.view.DesignDocument;
import com.couchbase.client.java.view.Stale;
import com.couchbase.client.java.view.ViewQuery;
import com.couchbase.client.java.view.ViewResult;
import com.couchbase.client.java.view.ViewRow;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import rx.Observable;
import rx.functions.Func1;
/**
* Runs end-to-end {@link ViewQuery}s and verifies their output.
*
* @author Michael Nitschinger
* @since 2.0.1
*/
public class ViewQueryTest {
public static final int STORED_DOCS = 1000;
private static CouchbaseTestContext ctx;
/**
* Populates th bucket with sample data and creates views for testing.
*/
@BeforeClass
public static void setupViews() {
ctx = CouchbaseTestContext.builder()
.adhoc(true)
.bucketQuota(100)
.bucketName("View")
.build();
Observable
.range(1, STORED_DOCS)
.flatMap(new Func1<Integer, Observable<JsonDocument>>() {
@Override
public Observable<JsonDocument> call(Integer id) {
JsonObject content = JsonObject.create()
.put("type", "user")
.put("name", "Mr. Foo Bar " + id)
.put("age", id % 100)
.put("active", (id % 2) == 0);
return ctx.bucket().async().insert(JsonDocument.create("user-" + id, content));
}
})
.last()
.toBlocking()
.single();
DesignDocument designDoc = DesignDocument.create(
"users",
Arrays.asList(
DefaultView.create("by_name", "function (doc, meta) { if (doc.type == \"user\") " +
"{ emit(doc.name, null); } }"),
DefaultView.create("by_age", "function (doc, meta) { if (doc.type == \"user\") " +
"{ emit(doc.age, null); } }", "_count")
)
);
try {
DesignDocument stored = ctx.bucketManager().getDesignDocument("users");
if (!stored.equals(designDoc)) {
ctx.bucketManager().upsertDesignDocument(designDoc);
}
} catch (DesignDocumentDoesNotExistException ex) {
ctx.bucketManager().upsertDesignDocument(designDoc);
}
}
@AfterClass
public static void cleanup() {
ctx.destroyBucketAndDisconnect();
}
@Test
public void shouldQueryNonReducedView() {
ViewResult result = ctx.bucket().query(ViewQuery.from("users", "by_name").stale(Stale.FALSE));
assertNull(result.debug());
assertNull(result.error());
assertTrue(result.success());
assertEquals(result.totalRows(), STORED_DOCS);
int foundRows = 0;
Iterator<ViewRow> rows = result.rows();
while(rows.hasNext()) {
ViewRow row = rows.next();
assertNull(row.value());
assertNotNull(row.id());
assertNotNull(row.key());
foundRows++;
}
assertEquals(STORED_DOCS, foundRows);
}
@Test
public void shouldQueryViewWithIterator() {
ViewResult result = ctx.bucket().query(ViewQuery.from("users", "by_name").stale(Stale.FALSE));
assertNull(result.debug());
assertNull(result.error());
assertTrue(result.success());
assertEquals(result.totalRows(), STORED_DOCS);
int foundRows = 0;
for (ViewRow row : result) {
assertNull(row.value());
assertNotNull(row.id());
assertNotNull(row.key());
foundRows++;
}
assertEquals(STORED_DOCS, foundRows);
}
@Test
public void shouldQueryReducedView() {
ViewResult result = ctx.bucket().query(ViewQuery.from("users", "by_age").stale(Stale.FALSE));
assertNull(result.debug());
assertNull(result.error());
assertTrue(result.success());
assertEquals(0, result.totalRows());
List<ViewRow> rows = result.allRows();
assertEquals(1, rows.size());
}
@Test
public void shouldManuallyDisabledReduce() {
ViewResult result = ctx.bucket().query(ViewQuery.from("users", "by_age").stale(Stale.FALSE).reduce(false), 3, TimeUnit.SECONDS);
assertNull(result.debug());
assertNull(result.error());
assertTrue(result.success());
assertEquals(result.totalRows(), STORED_DOCS);
int foundRows = 0;
Iterator<ViewRow> rows = result.rows();
while(rows.hasNext()) {
ViewRow row = rows.next();
assertNull(row.value());
assertNotNull(row.id());
assertNotNull(row.key());
foundRows++;
}
assertEquals(STORED_DOCS, foundRows);
}
@Test
public void shouldReturnNoRowsWithNonMatchingQuery() {
ViewResult result = ctx.bucket().query(ViewQuery.from("users", "by_name").key("foobar").stale(Stale.FALSE));
assertNull(result.debug());
assertNull(result.error());
assertTrue(result.success());
assertEquals(result.totalRows(), STORED_DOCS);
assertEquals(0, result.allRows().size());
}
@Test
public void shouldLoadDocumentsWithMapOnly() {
ViewResult result = ctx.bucket().query(ViewQuery.from("users", "by_name").limit(10).stale(Stale.FALSE));
assertNull(result.debug());
assertNull(result.error());
assertTrue(result.success());
assertEquals(result.totalRows(), STORED_DOCS);
int count = 0;
Iterator<ViewRow> rows = result.rows();
while(rows.hasNext()) {
count++;
ViewRow row = rows.next();
assertNotNull(row);
JsonDocument doc = row.document();
assertTrue(doc.id().startsWith("user-"));
assertTrue(doc.cas() != 0);
assertTrue(doc.expiry() == 0);
assertTrue(doc.content().getString("name").startsWith("Mr. Foo Bar"));
assertTrue(doc.content().getString("type").equals("user"));
}
assertEquals(10, count);
}
@Test
public void shouldLoadDocumentsWithIncludeDocs() {
ViewResult result = ctx.bucket().query(
ViewQuery.from("users", "by_name").limit(10).stale(Stale.FALSE).includeDocs()
);
assertNull(result.debug());
assertNull(result.error());
assertTrue(result.success());
assertEquals(result.totalRows(), STORED_DOCS);
int count = 0;
Iterator<ViewRow> rows = result.rows();
while(rows.hasNext()) {
count++;
ViewRow row = rows.next();
assertNotNull(row);
JsonDocument doc = row.document();
assertTrue(doc.id().startsWith("user-"));
assertTrue(doc.cas() != 0);
assertTrue(doc.expiry() == 0);
assertTrue(doc.content().getString("name").startsWith("Mr. Foo Bar"));
assertTrue(doc.content().getString("type").equals("user"));
}
assertEquals(10, count);
}
@Test
public void shouldIncludeDocsWithCustomTarget() {
ViewResult result = ctx.bucket().query(
ViewQuery.from("users", "by_name").limit(20).stale(Stale.FALSE).includeDocs(RawJsonDocument.class)
);
assertNull(result.debug());
assertNull(result.error());
assertTrue(result.success());
assertEquals(result.totalRows(), STORED_DOCS);
int count = 0;
Iterator<ViewRow> rows = result.rows();
while(rows.hasNext()) {
count++;
ViewRow row = rows.next();
assertNotNull(row);
RawJsonDocument doc = row.document(RawJsonDocument.class);
assertNotNull(doc.content());
assertFalse(doc.content().isEmpty());
}
assertEquals(20, count);
}
@Test(expected = ClassCastException.class)
public void shouldFailWhenWrongCustomTargetOnIncludeDocs() {
ViewResult result = ctx.bucket().query(
ViewQuery.from("users", "by_name").limit(20).stale(Stale.FALSE).includeDocs(RawJsonDocument.class)
);
assertNull(result.debug());
assertNull(result.error());
assertTrue(result.success());
assertEquals(result.totalRows(), STORED_DOCS);
Iterator<ViewRow> rows = result.rows();
while(rows.hasNext()) {
ViewRow row = rows.next();
assertNotNull(row);
row.document();
}
}
@Test
public void shouldComposeAsyncWithDocuments() {
List<JsonDocument> documents = ctx.bucket()
.async()
.query(ViewQuery.from("users", "by_name").limit(50).stale(Stale.FALSE))
.flatMap(new Func1<AsyncViewResult, Observable<AsyncViewRow>>() {
@Override
public Observable<AsyncViewRow> call(AsyncViewResult result) {
return result.error().flatMap(new Func1<JsonObject, Observable<? extends AsyncViewRow>>() {
@Override
public Observable<? extends AsyncViewRow> call(JsonObject e) {
return Observable.error(new CouchbaseException(e.toString()));
}
}).switchIfEmpty(result.rows());
}
})
.flatMap(new Func1<AsyncViewRow, Observable<JsonDocument>>() {
@Override
public Observable<JsonDocument> call(AsyncViewRow row) {
return row.document();
}
})
.toList()
.timeout(10, TimeUnit.SECONDS)
.toBlocking()
.single();
assertEquals(50, documents.size());
for (JsonDocument doc : documents) {
assertTrue(doc.id().startsWith("user-"));
assertTrue(doc.cas() != 0);
assertTrue(doc.expiry() == 0);
assertTrue(doc.content().getString("name").startsWith("Mr. Foo Bar"));
assertTrue(doc.content().getString("type").equals("user"));
}
}
@Test(expected = ViewDoesNotExistException.class)
public void shouldFailWithInvalidViewName() {
ctx.bucket().query(ViewQuery.from("users", "foobar"));
}
@Test(expected = ViewDoesNotExistException.class)
public void shouldFailWithInvalidDesignDocument() {
ctx.bucket().query(ViewQuery.from("foo", "bar"));
}
@Test(expected = UnsupportedOperationException.class)
public void shouldFailToLoadDocumentWhenReduced() {
ViewResult result = ctx.bucket().query(ViewQuery.from("users", "by_age").stale(Stale.FALSE));
List<ViewRow> rows = result.allRows();
assertEquals(1, rows.size());
ViewRow row = rows.get(0);
assertNull(row.id());
assertNull(row.key());
assertEquals(1000, row.value());
row.document();
}
@Test
public void shouldSucceedWithLargeKeysArray() throws IOException {
InputStream ras = this.getClass().getResourceAsStream("/data/view/key_many.txt");
BufferedReader reader = new BufferedReader(new InputStreamReader(ras));
String[] keys = reader.readLine().split(",");
reader.close();
JsonArray keysArray = JsonArray.from((Object[]) keys);
ViewResult result = ctx.bucket().query(
ViewQuery.from("users", "by_name")
.keys(keysArray)
);
assertTrue(result.success());
assertNull(result.error());
}
}