/* * Copyright 2015 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 org.couchbase.mock.client; import org.couchbase.mock.memcached.Item; import org.couchbase.mock.memcached.client.*; import org.couchbase.mock.memcached.protocol.*; import org.couchbase.mock.memcached.client.CommandBuilder.MultiMutationSpec; import org.couchbase.mock.memcached.client.CommandBuilder.MultiLookupSpec; import java.util.List; public class ClientSubdocTest extends ClientBaseTest { private MemcachedClient client; private short vbId; private final static String docId = "someKey"; private final static String multiDocId = "multiKey"; private final static String singleValue = "{}"; private final static String multiValue = "{\"key1\":\"value1\",\"key2\":\"value2\",\"key3\":\"value3\"}"; @Override protected void setUp() throws Exception { super.setUp(); client = getBinClient(0); vbId = findValidVbucket(0); // Store the item ClientResponse resp = client.sendRequest(CommandBuilder.buildStore(docId, vbId, singleValue)); assertTrue(resp.success()); resp = client.sendRequest(CommandBuilder.buildStore(multiDocId, vbId, multiValue)); assertTrue(resp.success()); } public void testSubdocBasic() throws Exception { ClientResponse resp; CommandBuilder cb; resp = client.sendRequest(CommandBuilder.buildSubdocGet(docId, vbId, "path")); assertEquals(ErrorCode.SUBDOC_PATH_ENOENT, resp.getStatus()); // Do an upsert cb = new CommandBuilder(CommandCode.SUBDOC_DICT_ADD) .key(docId, vbId) .subdoc("hello".getBytes(), "\"world\"".getBytes()); resp = client.sendRequest(cb); assertTrue(resp.success()); long cas = resp.getCas(); assertFalse(cas == 0); // Try it again (should fail with PATH_EEXISTS) resp = client.sendRequest(cb); assertEquals(ErrorCode.SUBDOC_PATH_EEXISTS, resp.getStatus()); // Get it back resp = client.sendRequest(CommandBuilder.buildSubdocGet(docId, vbId, "hello")); assertTrue(resp.success()); assertEquals("\"world\"", resp.getValue()); assertEquals(resp.getCas(), cas); // Remove it byte[] req = new CommandBuilder(CommandCode.SUBDOC_DELETE) .key(docId, vbId).subdoc("hello".getBytes()).build(); resp = client.sendRequest(req); assertTrue(resp.success()); assertFalse(cas == resp.getCas()); assertFalse(resp.getCas() == 0); // Get it again resp = client.sendRequest(CommandBuilder.buildSubdocGet(docId, vbId, "hello")); assertEquals(ErrorCode.SUBDOC_PATH_ENOENT, resp.getStatus()); } public void testCas() throws Exception { ClientResponse resp = client.sendRequest( new CommandBuilder(CommandCode.GET).key(docId, vbId)); assertTrue(resp.success()); long cas = resp.getCas(); CommandBuilder cb = new CommandBuilder(CommandCode.SUBDOC_DICT_ADD) .key(docId, vbId) .subdoc("foo".getBytes(), "123".getBytes()) .cas(cas + 1); resp = client.sendRequest(cb); assertEquals(ErrorCode.KEY_EEXISTS, resp.getStatus()); cb.cas(cas); resp = client.sendRequest(cb); assertTrue(resp.success()); } public void testExpiry() throws Exception { Item existing = getItem(docId, vbId); assertEquals(0, existing.getExpiryTime()); CommandBuilder cb = new CommandBuilder(CommandCode.SUBDOC_DICT_UPSERT) .key(docId, vbId) .subdoc("foo".getBytes(), "123".getBytes(), 0, 0, 30); ClientResponse resp = client.sendRequest(cb); assertTrue(resp.success()); // Get the item again existing = getItem(docId, vbId); assertTrue(existing.getExpiryTime() != 0); // Reset expiration time cb.subdoc("foo".getBytes(), "123".getBytes()); resp = client.sendRequest(cb); assertTrue(resp.success()); existing = getItem(docId, vbId); assertEquals(0, existing.getExpiryTime()); } public void testInvalid() throws Exception { CommandBuilder cb = new CommandBuilder(CommandCode.SUBDOC_DICT_UPSERT) .key(docId, vbId) .subdoc("bad..path[]".getBytes(), "123".getBytes()); ClientResponse resp = client.sendRequest(cb); assertEquals(ErrorCode.SUBDOC_PATH_EINVAL, resp.getStatus()); cb.subdoc("path".getBytes(), "non-json-value".getBytes()); resp = client.sendRequest(cb); assertEquals(ErrorCode.SUBDOC_VALUE_CANTINSERT, resp.getStatus()); cb.subdoc("[0]".getBytes(), "123".getBytes()); resp = client.sendRequest(cb); assertEquals(ErrorCode.SUBDOC_PATH_MISMATCH, resp.getStatus()); } public void testEmptyPath() throws Exception { byte[] req = CommandBuilder.buildSubdocGet(docId, vbId, ""); ClientResponse resp = client.sendRequest(req); assertTrue(resp.success()); assertEquals("{}", resp.getValue()); } public void testCounter() throws Exception { CommandBuilder cb = new CommandBuilder(CommandCode.SUBDOC_COUNTER) .key(docId, vbId) .subdoc("counter", "42"); ClientResponse resp = client.sendRequest(cb); assertTrue(resp.success()); assertEquals("42", resp.getValue()); // Try it again resp = client.sendRequest(cb); assertTrue(resp.success()); assertEquals("84", resp.getValue()); // Try with a large value CommandBuilder storeCb = new CommandBuilder(CommandCode.SUBDOC_DICT_UPSERT) .key(docId, vbId) .subdoc("counter", "9999999999999999999999999999999999999999999999"); resp = client.sendRequest(storeCb); assertTrue(resp.success()); resp = client.sendRequest(cb); assertEquals(ErrorCode.SUBDOC_NUM_ERANGE, resp.getStatus()); // Test with an invalid number storeCb = new CommandBuilder(CommandCode.SUBDOC_COUNTER) .key(docId, vbId) .subdoc("counter", "bad number"); resp = client.sendRequest(storeCb); assertEquals(ErrorCode.SUBDOC_DELTA_ERANGE, resp.getStatus()); } public void testGetCount() throws Exception { CommandBuilder cb = new CommandBuilder(CommandCode.SUBDOC_GET_COUNT) .key(multiDocId, vbId) .subdoc("".getBytes()); ClientResponse resp = client.sendRequest(cb); assertTrue(resp.getStatus().toString(), resp.success()); assertEquals("3", resp.getValue()); } public void testMultiLookups() throws Exception { CommandBuilder cb = new CommandBuilder(CommandCode.SUBDOC_MULTI_LOOKUP) .key(multiDocId, vbId) .subdocMultiLookup( MultiLookupSpec.get("key1"), MultiLookupSpec.get("key2"), MultiLookupSpec.get("key3")) ; ClientResponse resp = client.sendRequest(cb); assertTrue(resp.success()); List<MultiLookupResult> results = MultiLookupResult.parse(resp.getRawValue()); assertEquals(3, results.size()); assertEquals("\"value1\"", results.get(0).getValue()); assertEquals("\"value2\"", results.get(1).getValue()); assertEquals("\"value3\"", results.get(2).getValue()); for (MultiLookupResult res : results) { assertTrue(res.success()); } // Do the same thing, but with exists.. cb = new CommandBuilder(CommandCode.SUBDOC_MULTI_LOOKUP) .key(multiDocId, vbId) .subdocMultiLookup( MultiLookupSpec.exists("key1"), MultiLookupSpec.exists("key2"), MultiLookupSpec.exists("key3") ); resp = client.sendRequest(cb); assertTrue(resp.success()); results = MultiLookupResult.parse(resp.getRawValue()); assertEquals(3, results.size()); for (MultiLookupResult res : results) { assertTrue(res.getValue().isEmpty()); assertTrue(res.success()); } // Test mixed mode cb = new CommandBuilder(CommandCode.SUBDOC_MULTI_LOOKUP) .key(multiDocId, vbId) .subdocMultiLookup( MultiLookupSpec.get("key1"), MultiLookupSpec.get("non-exist"), MultiLookupSpec.get("key2") ); resp = client.sendRequest(cb); assertEquals(ErrorCode.SUBDOC_MULTI_FAILURE, resp.getStatus()); results = MultiLookupResult.parse(resp.getRawValue()); // First one is OK assertTrue(results.get(0).success()); assertEquals("\"value1\"", results.get(0).getValue()); // Second one is bad assertEquals(ErrorCode.SUBDOC_PATH_ENOENT, results.get(1).getStatus()); assertTrue(results.get(1).getValue().isEmpty()); // Third is also OK assertTrue(results.get(2).success()); assertEquals("\"value2\"", results.get(2).getValue()); cb.key("nonExist", vbId); resp = client.sendRequest(cb); assertEquals(ErrorCode.KEY_ENOENT, resp.getStatus()); results = MultiLookupResult.parse(resp.getRawValue()); assertTrue(results.isEmpty()); } public void testMultiMutations() throws Exception { CommandBuilder cb = new CommandBuilder(CommandCode.SUBDOC_MULTI_MUTATION) .key(multiDocId, vbId) .subdocMultiMutation( new MultiMutationSpec(CommandCode.SUBDOC_DICT_ADD, "new1", "\"v1\""), new MultiMutationSpec(CommandCode.SUBDOC_DICT_UPSERT, "new2", "\"v2\""), new MultiMutationSpec(CommandCode.SUBDOC_DICT_ADD, "new3", "\"v3\"") ); ClientResponse resp = client.sendRequest(cb); assertTrue(resp.success()); cb.subdocMultiMutation( new MultiMutationSpec(CommandCode.SUBDOC_DICT_UPSERT, "new4", "\"v4\""), new MultiMutationSpec(CommandCode.SUBDOC_DICT_ADD, "new1", "\"badv1\""), new MultiMutationSpec(CommandCode.SUBDOC_COUNTER, "counterVal", "42", true) ); // Try with an error. Sending it again should be enough resp = client.sendRequest(cb); assertEquals(ErrorCode.SUBDOC_MULTI_FAILURE, resp.getStatus()); List<MultiMutationResult> res = MultiMutationResult.parse(resp.getRawValue()); assertEquals(1, res.size()); assertEquals(ErrorCode.SUBDOC_PATH_EEXISTS, res.get(0).getStatus()); assertEquals(1, res.get(0).getIndex()); // Test with counters.. cb.subdocMultiMutation( new MultiMutationSpec(CommandCode.SUBDOC_COUNTER, "counter1", "50"), new MultiMutationSpec(CommandCode.SUBDOC_DICT_UPSERT, "newField", "\"newValue\""), new MultiMutationSpec(CommandCode.SUBDOC_ARRAY_PUSH_FIRST, "newArray", "123", true), new MultiMutationSpec(CommandCode.SUBDOC_COUNTER, "counter2", "100") ); resp = client.sendRequest(cb); assertEquals(ErrorCode.SUCCESS, resp.getStatus()); res = MultiMutationResult.parse(resp.getRawValue()); assertEquals(2, res.size()); assertEquals(0, res.get(0).getIndex()); assertEquals("50", res.get(0).getValue()); assertEquals(ErrorCode.SUCCESS, res.get(0).getStatus()); assertEquals(3, res.get(1).getIndex()); assertEquals("100", res.get(1).getValue()); assertEquals(ErrorCode.SUCCESS, res.get(1).getStatus()); } public void testInvalidMultiCombos() throws Exception { CommandBuilder cb = new CommandBuilder(CommandCode.SUBDOC_MULTI_MUTATION) .key(multiDocId, vbId); cb.subdocMultiMutation( new MultiMutationSpec(CommandCode.SUBDOC_GET, "blah"), new MultiMutationSpec(CommandCode.SUBDOC_DICT_ADD, "new1") ); ClientResponse resp = client.sendRequest(cb); assertEquals(ErrorCode.SUBDOC_INVALID_COMBO, resp.getStatus()); // Test with invalid lookup specs cb = new CommandBuilder(CommandCode.SUBDOC_MULTI_LOOKUP) .key(multiDocId, vbId) .subdocMultiLookup( new MultiLookupSpec(CommandCode.SUBDOC_ARRAY_ADD_UNIQUE, "blah"), new MultiLookupSpec(CommandCode.SUBDOC_DELETE, "argh") ); resp = client.sendRequest(cb); assertEquals(ErrorCode.SUBDOC_INVALID_COMBO, resp.getStatus()); } public void testMakeDocFlagSingle() throws Exception { removeItem(multiDocId, vbId); removeItem(docId, vbId); // Test with a simple store operation CommandBuilder cb = new CommandBuilder(CommandCode.SUBDOC_DICT_UPSERT) .key(docId, vbId) .subdoc("hello".getBytes(), "true".getBytes(), 0, BinarySubdocCommand.DOCFLAG_MKDOC, 0); ClientResponse resp = client.sendRequest(cb); assertTrue(resp.getStatus().toString(), resp.success()); // Get the item Item item = getItem(docId, vbId); assertNotNull(item); assertEquals("{\"hello\":true}", new String(item.getValue())); // Try it again cb = new CommandBuilder(CommandCode.SUBDOC_DICT_UPSERT) .key(docId, vbId) .subdoc("world".getBytes(), "false".getBytes(), BinarySubdocCommand.DOCFLAG_MKDOC); resp = client.sendRequest(cb); assertTrue(resp.success()); cb = new CommandBuilder(CommandCode.SUBDOC_DICT_UPSERT) .key(docId, vbId) .subdoc("deep.path".getBytes(), "123".getBytes(), BinarySubdocCommand.DOCFLAG_MKDOC); resp = client.sendRequest(cb); assertTrue(resp.success()); // Get back the old paths cb = new CommandBuilder(CommandCode.SUBDOC_MULTI_LOOKUP) .key(docId, vbId) .subdocMultiLookup( MultiLookupSpec.get("hello"), MultiLookupSpec.get("world"), MultiLookupSpec.get("deep.path") ); resp = client.sendRequest(cb); assertTrue(resp.success()); List<MultiLookupResult> mRes = MultiLookupResult.parse(resp.getRawValue()); assertEquals("true", mRes.get(0).getValue()); assertEquals("false", mRes.get(1).getValue()); assertEquals("123", mRes.get(2).getValue()); } public void testMakeDocFlagMulti() throws Exception { removeItem(multiDocId, vbId); CommandBuilder cb = new CommandBuilder(CommandCode.SUBDOC_MULTI_MUTATION) .key(multiDocId, vbId) .subdocMultiMutation(-1, BinarySubdocCommand.DOCFLAG_MKDOC, new MultiMutationSpec(CommandCode.SUBDOC_ARRAY_PUSH_FIRST, "arr", "true"), new MultiMutationSpec(CommandCode.SUBDOC_DICT_UPSERT, "pth.nest", "false") ); ClientResponse resp = client.sendRequest(cb); assertTrue(resp.getStatus().toString(), resp.success()); // Get the items back! cb = new CommandBuilder(CommandCode.SUBDOC_MULTI_LOOKUP) .key(multiDocId, vbId) .subdocMultiLookup(MultiLookupSpec.get("arr[0]"), MultiLookupSpec.get("pth.nest")); resp = client.sendRequest(cb); assertTrue(resp.success()); List<MultiLookupResult> mRes = MultiLookupResult.parse(resp.getRawValue()); assertEquals("true", mRes.get(0).getValue()); assertEquals("false", mRes.get(1).getValue()); } public void testGetAttrsSingle() throws Exception { storeItem(docId, vbId, "{}"); CommandBuilder cb = new CommandBuilder(CommandCode.SUBDOC_DICT_UPSERT) .key(docId, vbId) .subdoc("user.myAttr", "123", BinarySubdocCommand.PATHFLAG_XATTR |BinarySubdocCommand.PATHFLAG_MKDIR_P); ClientResponse resp = client.sendRequest(cb); assertTrue(resp.getStatus().toString(), resp.success()); Item itm = getItem(docId, vbId); assertNotNull(itm.getXattr()); assertEquals("{\"user\":{\"myAttr\":123}}", new String(itm.getXattr())); assertEquals("{}", new String(itm.getValue())); // Execute fullDoc operation cb = new CommandBuilder(CommandCode.SUBDOC_COUNTER) .key(docId, vbId) .subdoc("counter", "1"); resp = client.sendRequest(cb); assertTrue(resp.success()); itm = getItem(docId, vbId); assertEquals("{\"user\":{\"myAttr\":123}}", new String(itm.getXattr())); assertEquals("{\"counter\":1}", new String(itm.getValue())); } public void testGetAttrsMulti() throws Exception { storeItem(multiDocId, vbId, "{}"); CommandBuilder cb = new CommandBuilder(CommandCode.SUBDOC_MULTI_MUTATION) .key(multiDocId, vbId) .subdocMultiMutation( new MultiMutationSpec(CommandCode.SUBDOC_DICT_UPSERT, "bodyPath", "123"), new MultiMutationSpec(CommandCode.SUBDOC_DICT_UPSERT, "attrPath", "123", BinarySubdocCommand.PATHFLAG_MKDIR_P |BinarySubdocCommand.PATHFLAG_XATTR)); ClientResponse resp = client.sendRequest(cb); assertTrue(resp.success()); Item item = getItem(multiDocId, vbId); assertEquals("{\"bodyPath\":123}", new String(item.getValue())); assertEquals("{\"attrPath\":123}", new String(item.getXattr())); } }