package net.notdot.bdbdatastore.server;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import net.notdot.bdbdatastore.Indexing;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import com.google.appengine.base.ApiBase;
import com.google.appengine.base.ApiBase.VoidProto;
import com.google.appengine.datastore_v3.DatastoreV3;
import com.google.appengine.entity.Entity;
import com.google.protobuf.ByteString;
import com.google.protobuf.RpcCallback;
import com.google.protobuf.RpcController;
import com.google.protobuf.TextFormat;
import com.google.protobuf.TextFormat.ParseException;
import com.sleepycat.je.Cursor;
import com.sleepycat.je.DatabaseEntry;
import com.sleepycat.je.DatabaseException;
import com.sleepycat.je.OperationStatus;
import com.sleepycat.je.SecondaryCursor;
public class DatastoreServiceTest {
protected class TestRpcCallback<T> implements RpcCallback<T> {
protected T value = null;
protected boolean called = false;
public T getValue() {
return value;
}
public boolean isCalled() {
return called;
}
public void run(T arg0) {
value = arg0;
called = true;
}
}
public class TestRpcController implements RpcController {
public String errorText() {
return null;
}
public boolean failed() {
return false;
}
public boolean isCanceled() {
return false;
}
public void notifyOnCancel(RpcCallback<Object> arg0) {
}
public void reset() {
}
public void setFailed(String arg0) {
}
public void startCancel() {
}
}
// Sample key
protected static Entity.Reference testkey = Entity.Reference.newBuilder()
.setApp("testapp").setPath(
Entity.Path.newBuilder().addElement(
Entity.Path.Element.newBuilder()
.setType(ByteString.copyFromUtf8("testtype"))
.setName(ByteString.copyFromUtf8("testname")))).build();
// Sample entity
protected static Entity.EntityProto testent = Entity.EntityProto.newBuilder()
.setKey(testkey)
.setEntityGroup(testkey.getPath())
.addProperty(Entity.Property.newBuilder()
.setName(ByteString.copyFromUtf8("bar"))
.setValue(Entity.PropertyValue.newBuilder().setStringValue(ByteString.copyFromUtf8("Hello, world!"))))
.addProperty(Entity.Property.newBuilder()
.setName(ByteString.copyFromUtf8("foo"))
.setValue(Entity.PropertyValue.newBuilder().setInt64Value(1234))).build();
// Sample entity with no ID
protected static Entity.EntityProto testnewent = Entity.EntityProto.newBuilder(testent)
.setKey(Entity.Reference.newBuilder().setApp("testapp").setPath(
Entity.Path.newBuilder().addElement(
Entity.Path.Element.newBuilder().setType(ByteString.copyFromUtf8("testtype"))))).build();
// Sample composite indexes
Entity.CompositeIndex compositeIdx = Entity.CompositeIndex.newBuilder()
.setAppId("testapp")
.setId(0)
.setState(Entity.CompositeIndex.State.READ_WRITE) // Ignored
.setDefinition(Entity.Index.newBuilder()
.setEntityType(ByteString.copyFromUtf8("wtype"))
.setAncestor(false)
.addProperty(Entity.Index.Property.newBuilder()
.setName(ByteString.copyFromUtf8("tags"))
.setDirection(Entity.Index.Property.Direction.ASCENDING))
.addProperty(Entity.Index.Property.newBuilder()
.setName(ByteString.copyFromUtf8("num"))
.setDirection(Entity.Index.Property.Direction.DESCENDING))
).build();
Entity.CompositeIndex compositeMultiIdx = Entity.CompositeIndex.newBuilder()
.setAppId("testapp")
.setId(0)
.setState(Entity.CompositeIndex.State.READ_WRITE) // Ignored
.setDefinition(Entity.Index.newBuilder()
.setEntityType(ByteString.copyFromUtf8("xtype"))
.setAncestor(false)
.addProperty(Entity.Index.Property.newBuilder()
.setName(ByteString.copyFromUtf8("b"))
.setDirection(Entity.Index.Property.Direction.ASCENDING))
.addProperty(Entity.Index.Property.newBuilder()
.setName(ByteString.copyFromUtf8("a"))
.setDirection(Entity.Index.Property.Direction.ASCENDING))
.addProperty(Entity.Index.Property.newBuilder()
.setName(ByteString.copyFromUtf8("c"))
.setDirection(Entity.Index.Property.Direction.DESCENDING))
).build();
Entity.CompositeIndex compositeAncestorIdx = Entity.CompositeIndex.newBuilder(compositeIdx)
.setDefinition(Entity.Index.newBuilder(compositeIdx.getDefinition())
.setEntityType(ByteString.copyFromUtf8("vtype"))
.setAncestor(true))
.build();
Entity.CompositeIndex compositeKeyIdx = Entity.CompositeIndex.newBuilder()
.setAppId("testapp")
.setId(0)
.setState(Entity.CompositeIndex.State.READ_WRITE) // Ignored
.setDefinition(Entity.Index.newBuilder()
.setEntityType(ByteString.copyFromUtf8("wtype"))
.setAncestor(false)
.addProperty(Entity.Index.Property.newBuilder()
.setName(ByteString.copyFromUtf8("num"))
.setDirection(Entity.Index.Property.Direction.ASCENDING))
.addProperty(Entity.Index.Property.newBuilder()
.setName(ByteString.copyFromUtf8("__key__"))
.setDirection(Entity.Index.Property.Direction.DESCENDING))
).build();
protected File basedir;
protected Datastore datastore;
protected DatastoreService service;
protected Entity.Reference sample_key = null;
protected DatastoreV3.PutRequest dataset_put;
@Before
public void setUp() throws IOException {
basedir = File.createTempFile("bdbdatastore", "tmp", new File(System.getProperty("java.io.tmpdir")));
basedir.delete();
basedir.mkdir();
datastore = new Datastore(basedir.getAbsolutePath());
service = new DatastoreService(datastore);
DatastoreV3.PutRequest.Builder request = DatastoreV3.PutRequest.newBuilder();
InputStream dataSet = ClassLoader.getSystemResourceAsStream("dataset.txt");
BufferedReader reader = new BufferedReader ( new InputStreamReader ( dataSet ) );
TextFormat.merge(reader, request);
dataset_put = request.build();
}
@After
public void cleanUp() {
service.close();
datastore.close();
for(File f : basedir.listFiles())
f.delete();
basedir.delete();
}
@Test
public void testBeginTransaction() {
RpcController controller = new TestRpcController();
TestRpcCallback<DatastoreV3.Transaction> done = new TestRpcCallback<DatastoreV3.Transaction>();
service.beginTransaction(controller, VoidProto.getDefaultInstance(), done);
assertTrue(done.isCalled());
assertEquals(done.getValue().getHandle(), 0);
assertTrue(service.transactions.containsKey(done.getValue()));
assertEquals(service.transactions.get(done.getValue()), null);
controller = new TestRpcController();
done = new TestRpcCallback<DatastoreV3.Transaction>();
service.beginTransaction(controller, VoidProto.getDefaultInstance(), done);
assertTrue(done.isCalled());
assertEquals(done.getValue().getHandle(), 1);
assertTrue(service.transactions.containsKey(done.getValue()));
assertEquals(service.transactions.get(done.getValue()), null);
}
@Test
public void testCommit() {
RpcController controller = new TestRpcController();
// Create a transaction
TestRpcCallback<DatastoreV3.Transaction> tx_done = new TestRpcCallback<DatastoreV3.Transaction>();
service.beginTransaction(controller, ApiBase.VoidProto.getDefaultInstance(), tx_done);
assertTrue(tx_done.isCalled());
DatastoreV3.Transaction tx = tx_done.getValue();
// Create an entity
TestRpcCallback<DatastoreV3.PutResponse> put_done = new TestRpcCallback<DatastoreV3.PutResponse>();
DatastoreV3.PutRequest put_request = DatastoreV3.PutRequest.newBuilder().addEntity(testent).setTransaction(tx).build();
service.put(controller, put_request, put_done);
assertTrue(put_done.isCalled());
// Commit the transaction
TestRpcCallback<ApiBase.VoidProto> void_done = new TestRpcCallback<ApiBase.VoidProto>();
service.commit(controller, tx, void_done);
assertTrue(void_done.isCalled());
// Check the entity is there
controller = new TestRpcController();
TestRpcCallback<DatastoreV3.GetResponse> get_done = new TestRpcCallback<DatastoreV3.GetResponse>();
DatastoreV3.GetRequest get_request = DatastoreV3.GetRequest.newBuilder().addKey(testkey).build();
service.get(controller, get_request, get_done);
assertTrue(get_done.isCalled());
assertEquals(get_done.getValue().getEntity(0).getEntity(), testent);
// Ensure the transaction is gone
assertFalse(service.transactions.containsKey(tx));
}
@Test
public void testCommitEmpty() {
RpcController controller = new TestRpcController();
// Create a transaction
TestRpcCallback<DatastoreV3.Transaction> tx_done = new TestRpcCallback<DatastoreV3.Transaction>();
service.beginTransaction(controller, ApiBase.VoidProto.getDefaultInstance(), tx_done);
assertTrue(tx_done.isCalled());
DatastoreV3.Transaction tx = tx_done.getValue();
// Commit the transaction
TestRpcCallback<ApiBase.VoidProto> void_done = new TestRpcCallback<ApiBase.VoidProto>();
service.commit(controller, tx, void_done);
assertTrue(void_done.isCalled());
}
@Test
public void testDelete() {
RpcController controller = new TestRpcController();
// Create an entity
TestRpcCallback<DatastoreV3.PutResponse> put_done = new TestRpcCallback<DatastoreV3.PutResponse>();
DatastoreV3.PutRequest put_request = DatastoreV3.PutRequest.newBuilder().addEntity(testent).build();
service.put(controller, put_request, put_done);
assertTrue(put_done.isCalled());
// Check it's there
controller = new TestRpcController();
TestRpcCallback<DatastoreV3.GetResponse> get_done = new TestRpcCallback<DatastoreV3.GetResponse>();
DatastoreV3.GetRequest get_request = DatastoreV3.GetRequest.newBuilder().addKey(testkey).build();
service.get(controller, get_request, get_done);
assertTrue(get_done.isCalled());
assertEquals(get_done.getValue().getEntity(0).getEntity(), testent);
// Delete it
controller = new TestRpcController();
TestRpcCallback<ApiBase.VoidProto> del_done = new TestRpcCallback<ApiBase.VoidProto>();
DatastoreV3.DeleteRequest del_request = DatastoreV3.DeleteRequest.newBuilder().addKey(testkey).build();
service.delete(controller, del_request, del_done);
assertTrue(del_done.isCalled());
// Check it's not there
controller = new TestRpcController();
get_done = new TestRpcCallback<DatastoreV3.GetResponse>();
service.get(controller, get_request, get_done);
assertTrue(get_done.isCalled());
assertFalse(get_done.getValue().getEntity(0).hasEntity());
}
@Test
public void testGet() {
this.testPut();
RpcController controller = new TestRpcController();
TestRpcCallback<DatastoreV3.GetResponse> done = new TestRpcCallback<DatastoreV3.GetResponse>();
// Get the two entities we put in testPut()
DatastoreV3.GetRequest request = DatastoreV3.GetRequest.newBuilder().addKey(testkey).addKey(sample_key).build();
service.get(controller, request, done);
assertTrue(done.isCalled());
assertEquals(done.getValue().getEntityCount(), 2);
// Entity with key name was retrieved correctly
assertEquals(done.getValue().getEntity(0).getEntity(), testent);
// Entity with assigned ID was retrieved correctly
assertEquals(done.getValue().getEntity(1).getEntity().getKey(), sample_key);
assertEquals(done.getValue().getEntity(1).getEntity().getPropertyList(), testnewent.getPropertyList());
assertEquals(done.getValue().getEntity(1).getEntity().getEntityGroup(), sample_key.getPath());
}
@Test
public void testPut() {
RpcController controller = new TestRpcController();
TestRpcCallback<DatastoreV3.PutResponse> done = new TestRpcCallback<DatastoreV3.PutResponse>();
// Test putting an entity with a name, and one with neither name nor ID
DatastoreV3.PutRequest request = DatastoreV3.PutRequest.newBuilder().addEntity(testent).addEntity(testnewent).build();
service.put(controller, request, done);
assertTrue(done.isCalled());
assertEquals(done.getValue().getKeyCount(), 2);
// Entity with name was inserted correctly
assertEquals(done.getValue().getKey(0), testkey);
// Entity with no id was inserted correctly and assigned an id
sample_key = done.getValue().getKey(1);
assertEquals(sample_key.getApp(), testkey.getApp());
assertEquals(1, sample_key.getPath().getElementCount());
assertEquals(testnewent.getKey().getPath().getElement(0).getType(), sample_key.getPath().getElement(0).getType());
assertTrue(sample_key.getPath().getElement(0).hasId());
assertTrue(sample_key.getPath().getElement(0).getId() > 0);
}
@Test
public void testRollback() {
RpcController controller = new TestRpcController();
// Create a transaction
TestRpcCallback<DatastoreV3.Transaction> tx_done = new TestRpcCallback<DatastoreV3.Transaction>();
service.beginTransaction(controller, ApiBase.VoidProto.getDefaultInstance(), tx_done);
assertTrue(tx_done.isCalled());
DatastoreV3.Transaction tx = tx_done.getValue();
// Create an entity
TestRpcCallback<DatastoreV3.PutResponse> put_done = new TestRpcCallback<DatastoreV3.PutResponse>();
DatastoreV3.PutRequest put_request = DatastoreV3.PutRequest.newBuilder().addEntity(testent).setTransaction(tx).build();
service.put(controller, put_request, put_done);
assertTrue(put_done.isCalled());
// Rollback the transaction
TestRpcCallback<ApiBase.VoidProto> void_done = new TestRpcCallback<ApiBase.VoidProto>();
service.rollback(controller, tx, void_done);
assertTrue(void_done.isCalled());
// Check the entity is not there
controller = new TestRpcController();
TestRpcCallback<DatastoreV3.GetResponse> get_done = new TestRpcCallback<DatastoreV3.GetResponse>();
DatastoreV3.GetRequest get_request = DatastoreV3.GetRequest.newBuilder().addKey(testkey).build();
service.get(controller, get_request, get_done);
assertTrue(get_done.isCalled());
assertFalse(get_done.getValue().getEntity(0).hasEntity());
// Ensure the transaction is gone
assertFalse(service.transactions.containsKey(tx));
}
@Test
public void testRollbackEmpty() {
RpcController controller = new TestRpcController();
// Create a transaction
TestRpcCallback<DatastoreV3.Transaction> tx_done = new TestRpcCallback<DatastoreV3.Transaction>();
service.beginTransaction(controller, ApiBase.VoidProto.getDefaultInstance(), tx_done);
assertTrue(tx_done.isCalled());
DatastoreV3.Transaction tx = tx_done.getValue();
// Roll back the transaction
TestRpcCallback<ApiBase.VoidProto> void_done = new TestRpcCallback<ApiBase.VoidProto>();
service.rollback(controller, tx, void_done);
assertTrue(void_done.isCalled());
}
protected void loadCorpus() throws ParseException, FileNotFoundException, IOException {
// Insert the test corpus
RpcController controller = new TestRpcController();
TestRpcCallback<DatastoreV3.PutResponse> put_done = new TestRpcCallback<DatastoreV3.PutResponse>();
service.put(controller, dataset_put, put_done);
assertTrue(put_done.isCalled());
}
@Test
public void testKeyComparer() {
Indexing.EntityKey startKey = Indexing.EntityKey.newBuilder().setKind(ByteString.copyFromUtf8("testtype")).build();
assertTrue(EntityKeyComparator.instance.compare(startKey, AppDatastore.toEntityKey(dataset_put.getEntity(0).getKey())) > 0);
assertTrue(EntityKeyComparator.instance.compare(startKey, AppDatastore.toEntityKey(dataset_put.getEntity(1).getKey())) < 0);
assertTrue(EntityKeyComparator.instance.compare(startKey, AppDatastore.toEntityKey(dataset_put.getEntity(2).getKey())) < 0);
assertTrue(EntityKeyComparator.instance.compare(startKey, AppDatastore.toEntityKey(dataset_put.getEntity(3).getKey())) < 0);
}
@Test
public void testEntityOrdering() throws ParseException, FileNotFoundException, IOException, DatabaseException {
loadCorpus();
AppDatastore ds = this.service.datastore.getAppDatastore("testapp");
Cursor cur = ds.entities.openCursor(null, null);
DatabaseEntry key = new DatabaseEntry();
DatabaseEntry data = new DatabaseEntry();
cur.getFirst(key, data, null);
assertEquals(AppDatastore.toEntityKey(dataset_put.getEntity(0).getKey()), Indexing.EntityKey.parseFrom(key.getData()));
assertEquals(dataset_put.getEntity(0), Indexing.EntityData.parseFrom(data.getData()).getData());
for(int i = 1; i < 4; i++) {
cur.getNext(key, data, null);
assertEquals(AppDatastore.toEntityKey(dataset_put.getEntity(i).getKey()), Indexing.EntityKey.parseFrom(key.getData()));
assertEquals(dataset_put.getEntity(i), Indexing.EntityData.parseFrom(data.getData()).getData());
}
cur.close();
}
@Test
public void testEntityQuery() throws ParseException, FileNotFoundException, IOException {
RpcController controller = new TestRpcController();
loadCorpus();
// Construct a query for a kind
DatastoreV3.Query query = DatastoreV3.Query.newBuilder()
.setApp("testapp")
.setKind(ByteString.copyFromUtf8("testtype"))
.setOffset(1).setLimit(1).build();
TestRpcCallback<DatastoreV3.QueryResult> query_done = new TestRpcCallback<DatastoreV3.QueryResult>();
service.runQuery(controller, query, query_done);
assertTrue(query_done.isCalled());
assertTrue(service.cursors.containsKey(query_done.getValue().getCursor()));
// Get the results
controller = new TestRpcController();
DatastoreV3.NextRequest next = DatastoreV3.NextRequest.newBuilder()
.setCursor(query_done.getValue().getCursor())
.setCount(1).build();
query_done = new TestRpcCallback<DatastoreV3.QueryResult>();
service.next(controller, next, query_done);
assertTrue(query_done.isCalled());
assertEquals(1, query_done.getValue().getResultCount());
assertEquals(dataset_put.getEntity(2), query_done.getValue().getResult(0));
// Delete the cursor
controller = new TestRpcController();
TestRpcCallback<ApiBase.VoidProto> delete_done = new TestRpcCallback<ApiBase.VoidProto>();
service.deleteCursor(controller, query_done.getValue().getCursor(), delete_done);
assertTrue(delete_done.isCalled());
assertFalse(service.cursors.containsKey(query_done.getValue().getCursor()));
}
@Test
public void testAncestorQuery() throws ParseException, FileNotFoundException, IOException {
RpcController controller = new TestRpcController();
loadCorpus();
DatastoreV3.Query query = DatastoreV3.Query.newBuilder()
.setApp("testapp")
.setKind(ByteString.copyFromUtf8("vtype"))
.setAncestor(Entity.Reference.newBuilder()
.setApp("testapp")
.setPath(Entity.Path.newBuilder()
.addElement(Entity.Path.Element.newBuilder()
.setType(ByteString.copyFromUtf8("vtype"))
.setName(ByteString.copyFromUtf8("bar"))))).build();
TestRpcCallback<DatastoreV3.QueryResult> query_done = new TestRpcCallback<DatastoreV3.QueryResult>();
service.runQuery(controller, query, query_done);
assertTrue(query_done.isCalled());
assertTrue(service.cursors.containsKey(query_done.getValue().getCursor()));
// Get the results
controller = new TestRpcController();
DatastoreV3.NextRequest next = DatastoreV3.NextRequest.newBuilder()
.setCursor(query_done.getValue().getCursor())
.setCount(5).build();
query_done = new TestRpcCallback<DatastoreV3.QueryResult>();
service.next(controller, next, query_done);
assertTrue(query_done.isCalled());
assertEquals(3, query_done.getValue().getResultCount());
assertEquals(dataset_put.getEntity(4), query_done.getValue().getResult(0));
assertEquals(dataset_put.getEntity(5), query_done.getValue().getResult(1));
assertEquals(dataset_put.getEntity(7), query_done.getValue().getResult(2));
}
@Test
public void testSinglePropertyQuery() throws ParseException, FileNotFoundException, IOException {
loadCorpus();
String[] fields = new String[] {
"tags",
"num",
"num",
"num",
"num"
};
int[] operators = new int[] {
DatastoreV3.Query.Filter.Operator.EQUAL.getNumber(),
DatastoreV3.Query.Filter.Operator.GREATER_THAN.getNumber(),
DatastoreV3.Query.Filter.Operator.GREATER_THAN_OR_EQUAL.getNumber(),
DatastoreV3.Query.Filter.Operator.LESS_THAN.getNumber(),
DatastoreV3.Query.Filter.Operator.LESS_THAN_OR_EQUAL.getNumber()
};
Entity.PropertyValue[] values = new Entity.PropertyValue[] {
Entity.PropertyValue.newBuilder().setStringValue(ByteString.copyFromUtf8("foo")).build(),
Entity.PropertyValue.newBuilder().setInt64Value(3).build(),
Entity.PropertyValue.newBuilder().setInt64Value(5).build(),
Entity.PropertyValue.newBuilder().setInt64Value(10).build(),
Entity.PropertyValue.newBuilder().setInt64Value(5).build()
};
String[][] keyNames = new String[][] {
new String[] { "a", "b" },
new String[] { "a", "d" },
new String[] { "a", "d" },
new String[] { "a", "b", "c" },
new String[] { "a", "b", "c" }
};
for(int i = 0; i < operators.length; i++) {
RpcController controller = new TestRpcController();
DatastoreV3.Query query = DatastoreV3.Query.newBuilder()
.setApp("testapp")
.setKind(ByteString.copyFromUtf8("wtype"))
.addFilter(DatastoreV3.Query.Filter.newBuilder()
.setOp(operators[i])
.addProperty(Entity.Property.newBuilder()
.setName(ByteString.copyFromUtf8(fields[i]))
.setValue(values[i]))).build();
TestRpcCallback<DatastoreV3.QueryResult> done = new TestRpcCallback<DatastoreV3.QueryResult>();
service.runQuery(controller, query, done);
assertTrue(done.isCalled());
DatastoreV3.NextRequest next = DatastoreV3.NextRequest.newBuilder()
.setCursor(done.getValue().getCursor())
.setCount(10).build();
controller = new TestRpcController();
done = new TestRpcCallback<DatastoreV3.QueryResult>();
service.next(controller, next, done);
assertTrue(done.isCalled());
assertEquals(String.format("i=%d", i), keyNames[i].length, done.getValue().getResultCount());
Set<String> keySet = new HashSet<String>(Arrays.asList(keyNames[i]));
for(Entity.EntityProto entity : done.getValue().getResultList()) {
String name = entity.getKey().getPath().getElement(0).getName().toStringUtf8();
assertTrue(String.format("i=%d, key=%s", i, name), keySet.contains(name));
}
}
}
@Test
public void testSinglePropertyRange() throws ParseException, FileNotFoundException, IOException {
// Tests that a range query on a single property works.
loadCorpus();
RpcController controller = new TestRpcController();
DatastoreV3.Query query = DatastoreV3.Query.newBuilder()
.setApp("testapp")
.setKind(ByteString.copyFromUtf8("wtype"))
.addFilter(DatastoreV3.Query.Filter.newBuilder()
.setOp(DatastoreV3.Query.Filter.Operator.GREATER_THAN.getNumber())
.addProperty(Entity.Property.newBuilder()
.setName(ByteString.copyFromUtf8("num"))
.setValue(Entity.PropertyValue.newBuilder().setInt64Value(3))))
.addFilter(DatastoreV3.Query.Filter.newBuilder()
.setOp(DatastoreV3.Query.Filter.Operator.LESS_THAN.getNumber())
.addProperty(Entity.Property.newBuilder()
.setName(ByteString.copyFromUtf8("num"))
.setValue(Entity.PropertyValue.newBuilder().setInt64Value(10))))
.addOrder(DatastoreV3.Query.Order.newBuilder().setProperty(ByteString.copyFromUtf8("num")))
.build();
TestRpcCallback<DatastoreV3.QueryResult> done = new TestRpcCallback<DatastoreV3.QueryResult>();
service.runQuery(controller, query, done);
assertTrue(done.isCalled());
DatastoreV3.NextRequest next = DatastoreV3.NextRequest.newBuilder()
.setCursor(done.getValue().getCursor())
.setCount(10).build();
controller = new TestRpcController();
done = new TestRpcCallback<DatastoreV3.QueryResult>();
service.next(controller, next, done);
assertTrue(done.isCalled());
assertEquals(1, done.getValue().getResultCount());
assertEquals("a", done.getValue().getResult(0).getKey().getPath().getElement(0).getName().toStringUtf8());
}
@Test
public void testSinglePropertySort() throws ParseException, FileNotFoundException, IOException {
// Tests that a sort query on a single property works.
loadCorpus();
RpcController controller = new TestRpcController();
DatastoreV3.Query query = DatastoreV3.Query.newBuilder()
.setApp("testapp")
.setKind(ByteString.copyFromUtf8("wtype"))
.addOrder(DatastoreV3.Query.Order.newBuilder().setProperty(ByteString.copyFromUtf8("num")))
.build();
TestRpcCallback<DatastoreV3.QueryResult> done = new TestRpcCallback<DatastoreV3.QueryResult>();
service.runQuery(controller, query, done);
assertTrue(done.isCalled());
DatastoreV3.NextRequest next = DatastoreV3.NextRequest.newBuilder()
.setCursor(done.getValue().getCursor())
.setCount(10).build();
controller = new TestRpcController();
done = new TestRpcCallback<DatastoreV3.QueryResult>();
service.next(controller, next, done);
assertTrue(done.isCalled());
assertEquals(4, done.getValue().getResultCount());
assertEquals("b", done.getValue().getResult(0).getKey().getPath().getElement(0).getName().toStringUtf8());
assertEquals("c", done.getValue().getResult(1).getKey().getPath().getElement(0).getName().toStringUtf8());
assertEquals("a", done.getValue().getResult(2).getKey().getPath().getElement(0).getName().toStringUtf8());
assertEquals("d", done.getValue().getResult(3).getKey().getPath().getElement(0).getName().toStringUtf8());
}
@Test
public void testMergeJoinQuery() throws ParseException, FileNotFoundException, IOException {
// Tests that a merge join query executes correctly
loadCorpus();
RpcController controller = new TestRpcController();
DatastoreV3.Query query = DatastoreV3.Query.newBuilder()
.setApp("testapp")
.setKind(ByteString.copyFromUtf8("wtype"))
.addFilter(DatastoreV3.Query.Filter.newBuilder()
.setOp(DatastoreV3.Query.Filter.Operator.EQUAL.getNumber())
.addProperty(Entity.Property.newBuilder()
.setName(ByteString.copyFromUtf8("tags"))
.setValue(Entity.PropertyValue.newBuilder().setStringValue(ByteString.copyFromUtf8("foo")))))
.addFilter(DatastoreV3.Query.Filter.newBuilder()
.setOp(DatastoreV3.Query.Filter.Operator.EQUAL.getNumber())
.addProperty(Entity.Property.newBuilder()
.setName(ByteString.copyFromUtf8("num"))
.setValue(Entity.PropertyValue.newBuilder().setInt64Value(5))))
.build();
TestRpcCallback<DatastoreV3.QueryResult> done = new TestRpcCallback<DatastoreV3.QueryResult>();
service.runQuery(controller, query, done);
assertTrue(done.isCalled());
DatastoreV3.NextRequest next = DatastoreV3.NextRequest.newBuilder()
.setCursor(done.getValue().getCursor())
.setCount(10).build();
controller = new TestRpcController();
done = new TestRpcCallback<DatastoreV3.QueryResult>();
service.next(controller, next, done);
assertTrue(done.isCalled());
assertEquals(1, done.getValue().getResultCount());
assertEquals("a", done.getValue().getResult(0).getKey().getPath().getElement(0).getName().toStringUtf8());
}
@Test
public void testEmptyResultSet() throws ParseException, FileNotFoundException, IOException {
loadCorpus();
RpcController controller = new TestRpcController();
DatastoreV3.Query query = DatastoreV3.Query.newBuilder()
.setApp("testapp")
.setKind(ByteString.copyFromUtf8("wtype"))
.addFilter(DatastoreV3.Query.Filter.newBuilder()
.setOp(DatastoreV3.Query.Filter.Operator.EQUAL.getNumber())
.addProperty(Entity.Property.newBuilder()
.setName(ByteString.copyFromUtf8("num"))
.setValue(Entity.PropertyValue.newBuilder().setInt64Value(42))))
.build();
TestRpcCallback<DatastoreV3.QueryResult> done = new TestRpcCallback<DatastoreV3.QueryResult>();
service.runQuery(controller, query, done);
assertTrue(done.isCalled());
assertTrue(done.getValue().getMoreResults());
DatastoreV3.NextRequest next = DatastoreV3.NextRequest.newBuilder()
.setCursor(done.getValue().getCursor())
.setCount(10).build();
controller = new TestRpcController();
done = new TestRpcCallback<DatastoreV3.QueryResult>();
service.next(controller, next, done);
assertTrue(done.isCalled());
assertEquals(0, done.getValue().getResultCount());
assertFalse(done.getValue().getMoreResults());
}
@Test
public void testCount() throws ParseException, FileNotFoundException, IOException {
loadCorpus();
RpcController controller = new TestRpcController();
DatastoreV3.Query query = DatastoreV3.Query.newBuilder()
.setApp("testapp")
.setKind(ByteString.copyFromUtf8("wtype"))
.addOrder(DatastoreV3.Query.Order.newBuilder().setProperty(ByteString.copyFromUtf8("num")))
.setLimit(3)
.build();
TestRpcCallback<ApiBase.Integer64Proto> done = new TestRpcCallback<ApiBase.Integer64Proto>();
service.count(controller, query, done);
assertTrue(done.isCalled());
assertEquals(3, done.getValue().getValue());
}
@Test
public void testCompositeIndexGeneration() throws ParseException, FileNotFoundException, IOException, DatabaseException {
RpcController controller = new TestRpcController();
TestRpcCallback<ApiBase.Integer64Proto> create_done = new TestRpcCallback<ApiBase.Integer64Proto>();
service.createIndex(controller, compositeIdx, create_done);
assertTrue(create_done.isCalled());
loadCorpus();
TestRpcCallback<DatastoreV3.CompositeIndices> list_done = new TestRpcCallback<DatastoreV3.CompositeIndices>();
controller = new TestRpcController();
service.getIndices(controller, ApiBase.StringProto.newBuilder().setValue("testapp").build(), list_done);
assertTrue(list_done.isCalled());
assertEquals(1, list_done.getValue().getIndexCount());
assertEquals(1, list_done.getValue().getIndex(0).getId());
assertEquals(Entity.CompositeIndex.State.READ_WRITE, list_done.getValue().getIndex(0).getState());
assertEquals(compositeIdx.getDefinition(), list_done.getValue().getIndex(0).getDefinition());
Object[] keyNames = new Object[] { "a", "b", "a", "b" };
AppDatastore ds = this.service.datastore.getAppDatastore("testapp");
SecondaryCursor cur = ds.indexes.get(compositeIdx.getDefinition()).openSecondaryCursor(null, null);
List<String> resultNames = new ArrayList<String>();
DatabaseEntry key = new DatabaseEntry();
DatabaseEntry data = new DatabaseEntry();
while(cur.getNext(key, data, null) == OperationStatus.SUCCESS) {
Entity.EntityProto entity = Indexing.EntityData.parseFrom(data.getData()).getData();
resultNames.add(entity.getKey().getPath().getElement(0).getName().toStringUtf8());
}
assertArrayEquals(keyNames, resultNames.toArray());
cur.close();
TestRpcCallback<ApiBase.VoidProto> delete_done = new TestRpcCallback<ApiBase.VoidProto>();
controller = new TestRpcController();
service.deleteIndex(controller, list_done.getValue().getIndex(0), delete_done);
assertTrue(delete_done.isCalled());
}
@Test
public void testCompositeIndexQueries() throws ParseException, FileNotFoundException, IOException {
RpcController controller = new TestRpcController();
TestRpcCallback<ApiBase.Integer64Proto> done = new TestRpcCallback<ApiBase.Integer64Proto>();
service.createIndex(controller, compositeIdx, done);
controller = new TestRpcController();
done = new TestRpcCallback<ApiBase.Integer64Proto>();
service.createIndex(controller, compositeMultiIdx, done);
controller = new TestRpcController();
done = new TestRpcCallback<ApiBase.Integer64Proto>();
service.createIndex(controller, compositeAncestorIdx, done);
controller = new TestRpcController();
done = new TestRpcCallback<ApiBase.Integer64Proto>();
service.createIndex(controller, compositeKeyIdx, done);
loadCorpus();
// Perform a basic composite index query
List<DatastoreV3.Query> queries = new ArrayList<DatastoreV3.Query>();
List<String[]> resultSets = new ArrayList<String[]>();
queries.add(DatastoreV3.Query.newBuilder()
.setApp("testapp")
.setKind(ByteString.copyFromUtf8("wtype"))
.addFilter(DatastoreV3.Query.Filter.newBuilder()
.setOp(DatastoreV3.Query.Filter.Operator.EQUAL.getNumber())
.addProperty(Entity.Property.newBuilder()
.setName(ByteString.copyFromUtf8("tags"))
.setValue(Entity.PropertyValue.newBuilder()
.setStringValue(ByteString.copyFromUtf8("foo")))))
.addFilter(DatastoreV3.Query.Filter.newBuilder()
.setOp(DatastoreV3.Query.Filter.Operator.GREATER_THAN.getNumber())
.addProperty(Entity.Property.newBuilder()
.setName(ByteString.copyFromUtf8("num"))
.setValue(Entity.PropertyValue.newBuilder()
.setInt64Value(3))))
.addOrder(DatastoreV3.Query.Order.newBuilder()
.setProperty(ByteString.copyFromUtf8("num"))
.setDirection(DatastoreV3.Query.Order.Direction.DESCENDING.getNumber()))
.build());
resultSets.add(new String[] { "a" });
queries.add(DatastoreV3.Query.newBuilder(queries.get(0)).clearFilter().addFilter(queries.get(0).getFilter(0)).build());
resultSets.add(new String[] { "a", "b" });
queries.add(DatastoreV3.Query.newBuilder()
.setApp("testapp")
.setKind(ByteString.copyFromUtf8("wtype"))
.addOrder(DatastoreV3.Query.Order.newBuilder()
.setProperty(ByteString.copyFromUtf8("tags"))
.setDirection(DatastoreV3.Query.Order.Direction.ASCENDING.getNumber()))
.addOrder(DatastoreV3.Query.Order.newBuilder()
.setProperty(ByteString.copyFromUtf8("num"))
.setDirection(DatastoreV3.Query.Order.Direction.DESCENDING.getNumber()))
.build());
resultSets.add(new String[] { "a", "b" });
queries.add(DatastoreV3.Query.newBuilder()
.setApp("testapp")
.setKind(ByteString.copyFromUtf8("vtype"))
.setAncestor(Entity.Reference.newBuilder()
.setApp("testapp")
.setPath(Entity.Path.newBuilder()
.addElement(Entity.Path.Element.newBuilder()
.setType(ByteString.copyFromUtf8("vtype"))
.setName(ByteString.copyFromUtf8("bar")))))
.addOrder(DatastoreV3.Query.Order.newBuilder()
.setProperty(ByteString.copyFromUtf8("tags"))
.setDirection(DatastoreV3.Query.Order.Direction.ASCENDING.getNumber()))
.addOrder(DatastoreV3.Query.Order.newBuilder()
.setProperty(ByteString.copyFromUtf8("num"))
.setDirection(DatastoreV3.Query.Order.Direction.DESCENDING.getNumber()))
.build());
resultSets.add(new String[] { "foo", "baz" });
queries.add(DatastoreV3.Query.newBuilder()
.setApp("testapp")
.setKind(ByteString.copyFromUtf8("wtype"))
.addFilter(DatastoreV3.Query.Filter.newBuilder()
.setOp(DatastoreV3.Query.Filter.Operator.EQUAL.getNumber())
.addProperty(Entity.Property.newBuilder()
.setName(ByteString.copyFromUtf8("num"))
.setValue(Entity.PropertyValue.newBuilder()
.setInt64Value(3))))
.addOrder(DatastoreV3.Query.Order.newBuilder()
.setProperty(ByteString.copyFromUtf8("__key__"))
.setDirection(DatastoreV3.Query.Order.Direction.DESCENDING.getNumber()))
.build());
resultSets.add(new String[] { "c", "b" });
queries.add(DatastoreV3.Query.newBuilder()
.setApp("testapp")
.setKind(ByteString.copyFromUtf8("xtype"))
.addFilter(DatastoreV3.Query.Filter.newBuilder()
.setOp(DatastoreV3.Query.Filter.Operator.EQUAL.getNumber())
.addProperty(Entity.Property.newBuilder()
.setName(ByteString.copyFromUtf8("b"))
.setValue(Entity.PropertyValue.newBuilder()
.setInt64Value(42))))
.addFilter(DatastoreV3.Query.Filter.newBuilder()
.setOp(DatastoreV3.Query.Filter.Operator.EQUAL.getNumber())
.addProperty(Entity.Property.newBuilder()
.setName(ByteString.copyFromUtf8("a"))
.setValue(Entity.PropertyValue.newBuilder()
.setInt64Value(10))))
.addOrder(DatastoreV3.Query.Order.newBuilder()
.setProperty(ByteString.copyFromUtf8("c"))
.setDirection(DatastoreV3.Query.Order.Direction.DESCENDING.getNumber()))
.build());
resultSets.add(new String[] { "b", "a" });
for(int i = 0; i < queries.size(); i++) {
DatastoreV3.Query query = queries.get(i);
String[] results = resultSets.get(i);
TestRpcCallback<DatastoreV3.QueryResult> query_done = new TestRpcCallback<DatastoreV3.QueryResult>();
service.runQuery(controller, query, query_done);
assertTrue(query_done.isCalled());
assertTrue(service.cursors.containsKey(query_done.getValue().getCursor()));
// Get the results
controller = new TestRpcController();
DatastoreV3.NextRequest next = DatastoreV3.NextRequest.newBuilder()
.setCursor(query_done.getValue().getCursor())
.setCount(5).build();
query_done = new TestRpcCallback<DatastoreV3.QueryResult>();
service.next(controller, next, query_done);
assertTrue(query_done.isCalled());
assertEquals(String.format("i=%d", i), results.length, query_done.getValue().getResultCount());
for(int j = 0; j < results.length; j++) {
Entity.Path path = query_done.getValue().getResult(j).getKey().getPath();
assertEquals(String.format("i=%d, j=%d", i, j), results[j], path.getElement(path.getElementCount() - 1).getName().toStringUtf8());
}
}
}
@Test
public void testCompositeIndexOrdering() {
Indexing.CompositeIndexKey keyA = Indexing.CompositeIndexKey.newBuilder()
.addValue(Entity.PropertyValue.newBuilder()
.setStringValue(ByteString.copyFromUtf8("aaa")))
.addValue(Entity.PropertyValue.newBuilder()
.setInt64Value(2))
.build();
Indexing.CompositeIndexKey key3 = Indexing.CompositeIndexKey.newBuilder()
.addValue(Entity.PropertyValue.newBuilder()
.setStringValue(ByteString.copyFromUtf8("foo")))
.addValue(Entity.PropertyValue.newBuilder()
.setInt64Value(3))
.build();
Indexing.CompositeIndexKey key5 = Indexing.CompositeIndexKey.newBuilder()
.addValue(Entity.PropertyValue.newBuilder()
.setStringValue(ByteString.copyFromUtf8("foo")))
.addValue(Entity.PropertyValue.newBuilder()
.setInt64Value(5))
.build();
Indexing.CompositeIndexKey keyPrefix = Indexing.CompositeIndexKey.newBuilder()
.addValue(Entity.PropertyValue.newBuilder()
.setStringValue(ByteString.copyFromUtf8("foo")))
.build();
Indexing.CompositeIndexKey keyLast = Indexing.CompositeIndexKey.newBuilder()
.addValue(Entity.PropertyValue.newBuilder()
.setStringValue(ByteString.copyFromUtf8("foo")))
.addValue(Entity.PropertyValue.getDefaultInstance())
.build();
Indexing.CompositeIndexKey keyZ = Indexing.CompositeIndexKey.newBuilder()
.addValue(Entity.PropertyValue.newBuilder()
.setStringValue(ByteString.copyFromUtf8("zzz")))
.addValue(Entity.PropertyValue.newBuilder()
.setInt64Value(10))
.build();
CompositeIndexKeyComparator comparator = new CompositeIndexKeyComparator(new int[] { 1, 1 }, false);
assertTrue(comparator.compare(keyA, key3) < 0);
assertTrue(comparator.compare(key3, key3) == 0);
assertTrue(comparator.compare(key3, key5) < 0);
assertTrue(comparator.compare(keyPrefix, key3) < 0);
assertTrue(comparator.compare(key5, keyLast) < 0);
assertTrue(comparator.compare(keyZ, keyLast) > 0);
comparator = new CompositeIndexKeyComparator(new int[] { 1, -1 }, false);
assertTrue(comparator.compare(keyA, key3) < 0);
assertTrue(comparator.compare(key3, key3) == 0);
assertTrue(comparator.compare(key3, key5) > 0);
assertTrue(comparator.compare(keyPrefix, key3) > 0);
assertTrue(comparator.compare(key5, keyLast) > 0);
assertTrue(comparator.compare(keyZ, keyLast) > 0);
}
@Test
public void testGetSchema() throws ParseException, FileNotFoundException, IOException {
RpcController controller = new TestRpcController();
loadCorpus();
String[] kinds = new String[] { "sss", "testtype", "uuu", "vtype", "wtype", "xtype" };
TestRpcCallback<DatastoreV3.Schema> done = new TestRpcCallback<DatastoreV3.Schema>();
service.getSchema(controller, ApiBase.StringProto.newBuilder().setValue("testapp").build(), done);
assertTrue(done.isCalled());
assertEquals(kinds.length, done.getValue().getKindCount());
for(int i = 0; i < kinds.length; i++) {
Entity.Path path = done.getValue().getKind(i).getKey().getPath();
assertEquals(kinds[i], path.getElement(0).getType().toStringUtf8());
}
Entity.EntityProto entity = done.getValue().getKind(1);
assertEquals("bar", entity.getProperty(0).getName().toStringUtf8());
assertEquals("baz", entity.getProperty(1).getName().toStringUtf8());
assertEquals("foo", entity.getProperty(2).getName().toStringUtf8());
}
@Test
public void testEntityKeyQuery() throws ParseException, FileNotFoundException, IOException {
RpcController controller = new TestRpcController();
loadCorpus();
Entity.PropertyValue minKey = Entity.PropertyValue.newBuilder()
.setReferenceValue(Entity.PropertyValue.ReferenceValue.newBuilder()
.setApp("testapp")
.addPathElement(Entity.PropertyValue.ReferenceValue.PathElement.newBuilder()
.setType(ByteString.copyFromUtf8("testtype"))
.setName(ByteString.copyFromUtf8("bar")))).build();
Entity.PropertyValue maxKey = Entity.PropertyValue.newBuilder()
.setReferenceValue(Entity.PropertyValue.ReferenceValue.newBuilder()
.setApp("testapp")
.addPathElement(Entity.PropertyValue.ReferenceValue.PathElement.newBuilder()
.setType(ByteString.copyFromUtf8("testtype"))
.setName(ByteString.copyFromUtf8("zzz")))).build();
// Construct a query for a kind with __key__ filter
DatastoreV3.Query query = DatastoreV3.Query.newBuilder()
.setApp("testapp")
.setKind(ByteString.copyFromUtf8("testtype"))
.addFilter(DatastoreV3.Query.Filter.newBuilder()
.setOp(DatastoreV3.Query.Filter.Operator.GREATER_THAN.getNumber())
.addProperty(Entity.Property.newBuilder()
.setName(QuerySpec.KEY_PROPERTY)
.setValue(minKey))).build();
TestRpcCallback<DatastoreV3.QueryResult> query_done = new TestRpcCallback<DatastoreV3.QueryResult>();
service.runQuery(controller, query, query_done);
assertTrue(query_done.isCalled());
// Get the results
controller = new TestRpcController();
DatastoreV3.NextRequest next = DatastoreV3.NextRequest.newBuilder()
.setCursor(query_done.getValue().getCursor())
.setCount(5).build();
query_done = new TestRpcCallback<DatastoreV3.QueryResult>();
service.next(controller, next, query_done);
assertTrue(query_done.isCalled());
assertEquals(1, query_done.getValue().getResultCount());
assertEquals("testname", query_done.getValue().getResult(0).getKey().getPath().getElement(0).getName().toStringUtf8());
// Try again with range filter
query = DatastoreV3.Query.newBuilder(query).addFilter(DatastoreV3.Query.Filter.newBuilder()
.setOp(DatastoreV3.Query.Filter.Operator.LESS_THAN.getNumber())
.addProperty(Entity.Property.newBuilder()
.setName(QuerySpec.KEY_PROPERTY)
.setValue(maxKey))).build();
query_done = new TestRpcCallback<DatastoreV3.QueryResult>();
service.runQuery(controller, query, query_done);
// Get the results
controller = new TestRpcController();
next = DatastoreV3.NextRequest.newBuilder()
.setCursor(query_done.getValue().getCursor())
.setCount(5).build();
query_done = new TestRpcCallback<DatastoreV3.QueryResult>();
service.next(controller, next, query_done);
assertTrue(query_done.isCalled());
assertEquals(1, query_done.getValue().getResultCount());
assertEquals("testname", query_done.getValue().getResult(0).getKey().getPath().getElement(0).getName().toStringUtf8());
}
@Test
public void testAncestorKeyQuery() throws ParseException, FileNotFoundException, IOException {
RpcController controller = new TestRpcController();
Entity.PropertyValue minKey = Entity.PropertyValue.newBuilder()
.setReferenceValue(Entity.PropertyValue.ReferenceValue.newBuilder()
.setApp("testapp")
.addPathElement(Entity.PropertyValue.ReferenceValue.PathElement.newBuilder()
.setType(ByteString.copyFromUtf8("vtype"))
.setName(ByteString.copyFromUtf8("bar")))).build();
Entity.PropertyValue maxKey = Entity.PropertyValue.newBuilder()
.setReferenceValue(Entity.PropertyValue.ReferenceValue.newBuilder(minKey.getReferenceValue())
.addPathElement(Entity.PropertyValue.ReferenceValue.PathElement.newBuilder()
.setType(ByteString.copyFromUtf8("vtype"))
.setName(ByteString.copyFromUtf8("baz")))).build();
loadCorpus();
DatastoreV3.Query query = DatastoreV3.Query.newBuilder()
.setApp("testapp")
.setKind(ByteString.copyFromUtf8("vtype"))
.setAncestor(Entity.Reference.newBuilder()
.setApp("testapp")
.setPath(Entity.Path.newBuilder()
.addElement(Entity.Path.Element.newBuilder()
.setType(ByteString.copyFromUtf8("vtype"))
.setName(ByteString.copyFromUtf8("bar")))))
.addFilter(DatastoreV3.Query.Filter.newBuilder()
.setOp(DatastoreV3.Query.Filter.Operator.GREATER_THAN.getNumber())
.addProperty(Entity.Property.newBuilder()
.setName(QuerySpec.KEY_PROPERTY)
.setValue(minKey)))
.addFilter(DatastoreV3.Query.Filter.newBuilder()
.setOp(DatastoreV3.Query.Filter.Operator.LESS_THAN.getNumber())
.addProperty(Entity.Property.newBuilder()
.setName(QuerySpec.KEY_PROPERTY)
.setValue(maxKey)))
.build();
TestRpcCallback<DatastoreV3.QueryResult> query_done = new TestRpcCallback<DatastoreV3.QueryResult>();
service.runQuery(controller, query, query_done);
assertTrue(query_done.isCalled());
assertTrue(service.cursors.containsKey(query_done.getValue().getCursor()));
// Get the results
controller = new TestRpcController();
DatastoreV3.NextRequest next = DatastoreV3.NextRequest.newBuilder()
.setCursor(query_done.getValue().getCursor())
.setCount(5).build();
query_done = new TestRpcCallback<DatastoreV3.QueryResult>();
service.next(controller, next, query_done);
assertTrue(query_done.isCalled());
assertEquals(1, query_done.getValue().getResultCount());
assertEquals(dataset_put.getEntity(5), query_done.getValue().getResult(0));
}
@Test
public void testDeleteQueryResults() throws ParseException, FileNotFoundException, IOException {
RpcController controller = new TestRpcController();
loadCorpus();
// Construct a query for a kind
DatastoreV3.Query query = DatastoreV3.Query.newBuilder()
.setApp("testapp")
.setKind(ByteString.copyFromUtf8("testtype")).build();
TestRpcCallback<DatastoreV3.QueryResult> query_done = new TestRpcCallback<DatastoreV3.QueryResult>();
service.runQuery(controller, query, query_done);
assertTrue(query_done.isCalled());
assertTrue(service.cursors.containsKey(query_done.getValue().getCursor()));
// Get the results
controller = new TestRpcController();
DatastoreV3.NextRequest next = DatastoreV3.NextRequest.newBuilder()
.setCursor(query_done.getValue().getCursor())
.setCount(2).build();
query_done = new TestRpcCallback<DatastoreV3.QueryResult>();
service.next(controller, next, query_done);
assertTrue(query_done.isCalled());
for(Entity.EntityProto result : query_done.getValue().getResultList()) {
controller = new TestRpcController();
TestRpcCallback<ApiBase.VoidProto> delete_done = new TestRpcCallback<ApiBase.VoidProto>();
service.delete(controller, DatastoreV3.DeleteRequest.newBuilder().addKey(result.getKey()).build(), delete_done);
assertTrue(delete_done.isCalled());
}
// Delete the cursor
controller = new TestRpcController();
TestRpcCallback<ApiBase.VoidProto> deletecursor_done = new TestRpcCallback<ApiBase.VoidProto>();
service.deleteCursor(controller, query_done.getValue().getCursor(), deletecursor_done);
assertTrue(deletecursor_done.isCalled());
assertFalse(service.cursors.containsKey(query_done.getValue().getCursor()));
}
}