diff --git a/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/BaseEntity.java b/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/BaseEntity.java index d674a5e242ad..20c0b13e5001 100644 --- a/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/BaseEntity.java +++ b/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/BaseEntity.java @@ -33,6 +33,7 @@ import com.google.protobuf.InvalidProtocolBufferException; import java.util.HashMap; +import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Objects; @@ -128,61 +129,286 @@ public B remove(String name) { return self(); } + /** + * Sets a property. + * + * @param name name of the property + * @param value value associated with the property + */ public B set(String name, Value value) { properties.put(name, value); return self(); } + /** + * Sets a property of type {@link StringValue}. + * + * @param name name of the property + * @param value value associated with the property + */ public B set(String name, String value) { properties.put(name, of(value)); return self(); } + /** + * Sets a list property containing elements of type {@link StringValue}. + * + * @param name name of the property + * @param first the first string in the list + * @param second the second string in the list + * @param others other strings in the list + */ + public B set(String name, String first, String second, String... others) { + List values = new LinkedList<>(); + values.add(of(first)); + values.add(of(second)); + for (String other : others) { + values.add(of(other)); + } + properties.put(name, of(values)); + return self(); + } + + /** + * Sets a property of type {@link LongValue}. + * + * @param name name of the property + * @param value value associated with the property + */ public B set(String name, long value) { properties.put(name, of(value)); return self(); } + /** + * Sets a list property containing elements of type {@link LongValue}. + * + * @param name name of the property + * @param first the first long in the list + * @param second the second long in the list + * @param others other longs in the list + */ + public B set(String name, long first, long second, long... others) { + List values = new LinkedList<>(); + values.add(of(first)); + values.add(of(second)); + for (long other : others) { + values.add(of(other)); + } + properties.put(name, of(values)); + return self(); + } + + /** + * Sets a property of type {@link DoubleValue}. + * + * @param name name of the property + * @param value value associated with the property + */ public B set(String name, double value) { properties.put(name, of(value)); return self(); } + /** + * Sets a list property containing elements of type {@link DoubleValue}. + * + * @param name name of the property + * @param first the first double in the list + * @param second the second double in the list + * @param others other doubles in the list + */ + public B set(String name, double first, double second, double... others) { + List values = new LinkedList<>(); + values.add(of(first)); + values.add(of(second)); + for (double other : others) { + values.add(of(other)); + } + properties.put(name, of(values)); + return self(); + } + + /** + * Sets a property of type {@link BooleanValue}. + * + * @param name name of the property + * @param value value associated with the property + */ public B set(String name, boolean value) { properties.put(name, of(value)); return self(); } + /** + * Sets a list property containing elements of type {@link BooleanValue}. + * + * @param name name of the property + * @param first the first boolean in the list + * @param second the second boolean in the list + * @param others other booleans in the list + */ + public B set(String name, boolean first, boolean second, boolean... others) { + List values = new LinkedList<>(); + values.add(of(first)); + values.add(of(second)); + for (boolean other : others) { + values.add(of(other)); + } + properties.put(name, of(values)); + return self(); + } + + /** + * Sets a property of type {@link DateTimeValue}. + * + * @param name name of the property + * @param value value associated with the property + */ public B set(String name, DateTime value) { properties.put(name, of(value)); return self(); } + /** + * Sets a list property containing elements of type {@link DateTimeValue}. + * + * @param name name of the property + * @param first the first {@link DateTime} in the list + * @param second the second {@link DateTime} in the list + * @param others other {@link DateTime}s in the list + */ + public B set(String name, DateTime first, DateTime second, DateTime... others) { + List values = new LinkedList<>(); + values.add(of(first)); + values.add(of(second)); + for (DateTime other : others) { + values.add(of(other)); + } + properties.put(name, of(values)); + return self(); + } + + /** + * Sets a property of type {@link KeyValue}. + * + * @param name name of the property + * @param value value associated with the property + */ public B set(String name, Key value) { properties.put(name, of(value)); return self(); } + /** + * Sets a list property containing elements of type {@link KeyValue}. + * + * @param name name of the property + * @param first the first {@link Key} in the list + * @param second the second {@link Key} in the list + * @param others other {@link Key}s in the list + */ + public B set(String name, Key first, Key second, Key... others) { + List values = new LinkedList<>(); + values.add(of(first)); + values.add(of(second)); + for (Key other : others) { + values.add(of(other)); + } + properties.put(name, of(values)); + return self(); + } + + /** + * Sets a property of type {@link EntityValue}. + * + * @param name name of the property + * @param value value associated with the property + */ public B set(String name, FullEntity value) { properties.put(name, of(value)); return self(); } + /** + * Sets a list property containing elements of type {@link EntityValue}. + * + * @param name name of the property + * @param first the first {@link FullEntity} in the list + * @param second the second {@link FullEntity} in the list + * @param others other entities in the list + */ + public B set(String name, FullEntity first, FullEntity second, FullEntity... others) { + List values = new LinkedList<>(); + values.add(of(first)); + values.add(of(second)); + for (FullEntity other : others) { + values.add(of(other)); + } + properties.put(name, of(values)); + return self(); + } + + /** + * Sets a property of type {@link ListValue}. + * + * @param name name of the property + * @param values list of values associated with the property + */ public B set(String name, List> values) { properties.put(name, of(values)); return self(); } - public B set(String name, Value value, Value... other) { - properties.put(name, of(value, other)); + /** + * Sets a property of type {@link ListValue}. + * + * @param name name of the property + * @param first the first value in the list + * @param second the second value in the list + * @param others other values in the list + */ + public B set(String name, Value first, Value second, Value... others) { + properties.put(name, ListValue.builder().addValue(first).addValue(second, others).build()); return self(); } + /** + * Sets a property of type {@link BlobValue}. + * + * @param name name of the property + * @param value value associated with the property + */ public B set(String name, Blob value) { properties.put(name, of(value)); return self(); } + /** + * Sets a list property containing elements of type {@link BlobValue}. + * + * @param name name of the property + * @param first the first {@link Blob} in the list + * @param second the second {@link Blob} in the list + * @param others other {@link Blob}s in the list + */ + public B set(String name, Blob first, Blob second, Blob... others) { + List values = new LinkedList<>(); + values.add(of(first)); + values.add(of(second)); + for (Blob other : others) { + values.add(of(other)); + } + properties.put(name, of(values)); + return self(); + } + + /** + * Sets a property of type {@code NullValue}. + * + * @param name name of the property + */ public B setNull(String name) { properties.put(name, of()); return self(); @@ -348,8 +574,8 @@ public FullEntity getEntity(String name) { * @throws ClassCastException if value is not a list of values */ @SuppressWarnings("unchecked") - public List> getList(String name) { - return ((Value>>) getValue(name)).get(); + public > List getList(String name) { + return (List) getValue(name).get(); } /** diff --git a/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/BaseKey.java b/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/BaseKey.java index a8ad7d4e7734..4ab6f51b6767 100644 --- a/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/BaseKey.java +++ b/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/BaseKey.java @@ -157,6 +157,8 @@ public String kind() { return leaf().kind(); } + abstract BaseKey parent(); + @Override public int hashCode() { return Objects.hash(projectId(), namespace(), path()); diff --git a/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/IncompleteKey.java b/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/IncompleteKey.java index 2ccd59e725a8..2192384ef70b 100644 --- a/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/IncompleteKey.java +++ b/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/IncompleteKey.java @@ -84,6 +84,29 @@ static IncompleteKey fromPb(DatastoreV1.Key keyPb) { return new IncompleteKey(projectId, namespace, path); } + /** + * Returns the key's parent. + */ + @Override + public Key parent() { + List ancestors = ancestors(); + if (ancestors.isEmpty()) { + return null; + } + PathElement parent = ancestors.get(ancestors.size() - 1); + Key.Builder keyBuilder; + if (parent.hasName()) { + keyBuilder = Key.builder(projectId(), parent.kind(), parent.name()); + } else { + keyBuilder = Key.builder(projectId(), parent.kind(), parent.id()); + } + String namespace = namespace(); + if (namespace != null) { + keyBuilder.namespace(namespace); + } + return keyBuilder.ancestors(ancestors.subList(0, ancestors.size() - 1)).build(); + } + public static Builder builder(String projectId, String kind) { return new Builder(projectId, kind); } diff --git a/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/ListValue.java b/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/ListValue.java index 41c7e82788b5..06282a2c79d1 100644 --- a/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/ListValue.java +++ b/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/ListValue.java @@ -70,17 +70,16 @@ private Builder() { super(ValueType.LIST); } - public Builder addValue(Value value) { + private void addValueHelper(Value value) { // see datastore_v1.proto definition for list_value Preconditions.checkArgument(value.type() != ValueType.LIST, "Cannot contain another list"); listBuilder.add(value); - return this; } public Builder addValue(Value first, Value... other) { - addValue(first); + addValueHelper(first); for (Value value : other) { - addValue(value); + addValueHelper(value); } return this; } diff --git a/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/testing/LocalGcdHelper.java b/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/testing/LocalGcdHelper.java index 7c60da50b0b0..9d4ea65af634 100644 --- a/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/testing/LocalGcdHelper.java +++ b/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/testing/LocalGcdHelper.java @@ -17,6 +17,7 @@ package com.google.gcloud.datastore.testing; import static com.google.common.base.MoreObjects.firstNonNull; +import static com.google.common.base.Preconditions.checkArgument; import static java.nio.charset.StandardCharsets.UTF_8; import com.google.common.base.Strings; @@ -85,6 +86,7 @@ public class LocalGcdHelper { private static final String GCLOUD = "gcloud"; private static final Path INSTALLED_GCD_PATH; private static final String GCD_VERSION_PREFIX = "gcd-emulator "; + private static final double DEFAULT_CONSISTENCY = 0.9; static { INSTALLED_GCD_PATH = installedGcdPath(); @@ -398,9 +400,15 @@ public LocalGcdHelper(String projectId, int port) { * All content is written to a temporary directory that will be deleted when * {@link #stop()} is called or when the program terminates) to make sure that no left-over * data from prior runs is used. + * + * @param consistency the fraction of job application attempts that will succeed, with 0.0 + * resulting in no attempts succeeding, and 1.0 resulting in all attempts succeeding. Defaults + * to 0.9. Note that setting this to 1.0 may mask incorrect assumptions about the consistency + * of non-ancestor queries; non-ancestor queries are eventually consistent. */ - public void start() throws IOException, InterruptedException { + public void start(double consistency) throws IOException, InterruptedException { // send a quick request in case we have a hanging process from a previous run + checkArgument(consistency >= 0.0 && consistency <= 1.0, "Consistency must be between 0 and 1"); sendQuitRequest(port); // Each run is associated with its own folder that is deleted once test completes. gcdPath = Files.createTempDirectory("gcd"); @@ -415,7 +423,7 @@ public void start() throws IOException, InterruptedException { } else { gcdExecutablePath = INSTALLED_GCD_PATH; } - startGcd(gcdExecutablePath); + startGcd(gcdExecutablePath, consistency); } private void downloadGcd() throws IOException { @@ -453,7 +461,8 @@ private void downloadGcd() throws IOException { } } - private void startGcd(Path executablePath) throws IOException, InterruptedException { + private void startGcd(Path executablePath, double consistency) + throws IOException, InterruptedException { // cleanup any possible data for the same project File datasetFolder = new File(gcdPath.toFile(), projectId); deleteRecurse(datasetFolder.toPath()); @@ -486,7 +495,8 @@ private void startGcd(Path executablePath) throws IOException, InterruptedExcept startProcess = CommandWrapper.create() .command(gcdAbsolutePath.toString(), "start", "--testing", "--allow_remote_shutdown", - "--port=" + Integer.toString(port), projectId) + "--port=" + Integer.toString(port), "--consistency=" + Double.toString(consistency), + projectId) .directory(gcdPath) .start(); processReader = ProcessStreamReader.start(startProcess.getInputStream()); @@ -578,10 +588,10 @@ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IO }); } - public static LocalGcdHelper start(String projectId, int port) + public static LocalGcdHelper start(String projectId, int port, double consistency) throws IOException, InterruptedException { LocalGcdHelper helper = new LocalGcdHelper(projectId, port); - helper.start(); + helper.start(consistency); return helper; } @@ -593,7 +603,9 @@ public static void main(String... args) throws IOException, InterruptedException switch (action) { case "START": if (!isActive(DEFAULT_PROJECT_ID, port)) { - LocalGcdHelper helper = start(DEFAULT_PROJECT_ID, port); + double consistency = parsedArgs.get("consistency") == null + ? DEFAULT_CONSISTENCY : Double.parseDouble(parsedArgs.get("consistency")); + LocalGcdHelper helper = start(DEFAULT_PROJECT_ID, port, consistency); try (FileWriter writer = new FileWriter(".local_gcd_helper")) { writer.write(helper.gcdPath.toAbsolutePath().toString() + System.lineSeparator()); writer.write(Integer.toString(port)); diff --git a/gcloud-java-datastore/src/test/java/com/google/gcloud/datastore/BaseEntityTest.java b/gcloud-java-datastore/src/test/java/com/google/gcloud/datastore/BaseEntityTest.java index 5ece01508d3a..a69ea5e20e3b 100644 --- a/gcloud-java-datastore/src/test/java/com/google/gcloud/datastore/BaseEntityTest.java +++ b/gcloud-java-datastore/src/test/java/com/google/gcloud/datastore/BaseEntityTest.java @@ -67,6 +67,16 @@ public void setUp() { builder.set("list1", NullValue.of(), StringValue.of("foo")); builder.set("list2", ImmutableList.of(LongValue.of(10), DoubleValue.of(2))); builder.set("list3", Collections.singletonList(BooleanValue.of(true))); + builder.set( + "blobList", BLOB, Blob.copyFrom(new byte[] {3, 4}), Blob.copyFrom(new byte[] {5, 6})); + builder.set("booleanList", true, false, true); + builder.set("dateTimeList", DateTime.now(), DateTime.now(), DateTime.now()); + builder.set("doubleList", 12.3, 4.56, .789); + builder.set("keyList", KEY, Key.builder("ds2", "k2", "n2").build(), + Key.builder("ds3", "k3", "n3").build()); + builder.set("entityList", ENTITY, PARTIAL_ENTITY); + builder.set("stringList", "s1", "s2", "s3"); + builder.set("longList", 1, 23, 456); } @Test @@ -183,6 +193,17 @@ public void testGetList() throws Exception { assertEquals(Boolean.TRUE, list.get(0).get()); entity = builder.set("list1", ListValue.of(list)).build(); assertEquals(list, entity.getList("list1")); + List> stringList = entity.getList("stringList"); + assertEquals( + ImmutableList.of(StringValue.of("s1"), StringValue.of("s2"), StringValue.of("s3")), + stringList); + List> doubleList = entity.getList("doubleList"); + assertEquals( + ImmutableList.of(DoubleValue.of(12.3), DoubleValue.of(4.56), DoubleValue.of(.789)), + doubleList); + List entityList = entity.getList("entityList"); + assertEquals( + ImmutableList.of(EntityValue.of(ENTITY), EntityValue.of(PARTIAL_ENTITY)), entityList); } @Test @@ -198,7 +219,9 @@ public void testGetBlob() throws Exception { public void testNames() throws Exception { Set names = ImmutableSet.builder() .add("string", "stringValue", "boolean", "double", "long", "list1", "list2", "list3") - .add("entity", "partialEntity", "null", "dateTime", "blob", "key") + .add("entity", "partialEntity", "null", "dateTime", "blob", "key", "blobList") + .add("booleanList", "dateTimeList", "doubleList", "keyList", "entityList", "stringList") + .add("longList") .build(); BaseEntity entity = builder.build(); assertEquals(names, entity.names()); diff --git a/gcloud-java-datastore/src/test/java/com/google/gcloud/datastore/BaseKeyTest.java b/gcloud-java-datastore/src/test/java/com/google/gcloud/datastore/BaseKeyTest.java index 8615ee025bd1..43db4695b191 100644 --- a/gcloud-java-datastore/src/test/java/com/google/gcloud/datastore/BaseKeyTest.java +++ b/gcloud-java-datastore/src/test/java/com/google/gcloud/datastore/BaseKeyTest.java @@ -50,6 +50,11 @@ protected BaseKey build() { protected Object fromPb(byte[] bytesPb) throws InvalidProtocolBufferException { return null; } + + @Override + protected BaseKey parent() { + return null; + } }; } } diff --git a/gcloud-java-datastore/src/test/java/com/google/gcloud/datastore/DatastoreTest.java b/gcloud-java-datastore/src/test/java/com/google/gcloud/datastore/DatastoreTest.java index e9eed027e8e0..92f232c18a56 100644 --- a/gcloud-java-datastore/src/test/java/com/google/gcloud/datastore/DatastoreTest.java +++ b/gcloud-java-datastore/src/test/java/com/google/gcloud/datastore/DatastoreTest.java @@ -118,7 +118,7 @@ public class DatastoreTest { @BeforeClass public static void beforeClass() throws IOException, InterruptedException { if (!LocalGcdHelper.isActive(PROJECT_ID, PORT)) { - gcdHelper = LocalGcdHelper.start(PROJECT_ID, PORT); + gcdHelper = LocalGcdHelper.start(PROJECT_ID, PORT, 1.0); } } diff --git a/gcloud-java-datastore/src/test/java/com/google/gcloud/datastore/IncompleteKeyTest.java b/gcloud-java-datastore/src/test/java/com/google/gcloud/datastore/IncompleteKeyTest.java index 7edbf133d330..acd1dfd3c9e3 100644 --- a/gcloud-java-datastore/src/test/java/com/google/gcloud/datastore/IncompleteKeyTest.java +++ b/gcloud-java-datastore/src/test/java/com/google/gcloud/datastore/IncompleteKeyTest.java @@ -17,29 +17,47 @@ package com.google.gcloud.datastore; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; +import org.junit.Before; import org.junit.Test; public class IncompleteKeyTest { + private static IncompleteKey pk1, pk2; + private static Key parent1; + + @Before + public void setUp() { + pk1 = IncompleteKey.builder("ds", "kind1").build(); + parent1 = Key.builder("ds", "kind2", 10).namespace("ns").build(); + pk2 = IncompleteKey.builder(parent1, "kind3").build(); + } + @Test public void testBuilders() throws Exception { - IncompleteKey pk1 = IncompleteKey.builder("ds", "kind1").build(); assertEquals("ds", pk1.projectId()); assertEquals("kind1", pk1.kind()); assertTrue(pk1.ancestors().isEmpty()); - Key parent = Key.builder("ds", "kind2", 10).build(); - IncompleteKey pk2 = IncompleteKey.builder(parent, "kind3").build(); assertEquals("ds", pk2.projectId()); assertEquals("kind3", pk2.kind()); - assertEquals(parent.path(), pk2.ancestors()); + assertEquals(parent1.path(), pk2.ancestors()); assertEquals(pk2, IncompleteKey.builder(pk2).build()); IncompleteKey pk3 = IncompleteKey.builder(pk2).kind("kind4").build(); assertEquals("ds", pk3.projectId()); assertEquals("kind4", pk3.kind()); - assertEquals(parent.path(), pk3.ancestors()); + assertEquals(parent1.path(), pk3.ancestors()); + } + + @Test + public void testParent() { + assertNull(pk1.parent()); + assertEquals(parent1, pk2.parent()); + Key parent2 = Key.builder("ds", "kind3", "name").namespace("ns").build(); + IncompleteKey pk3 = IncompleteKey.builder(parent2, "kind3").build(); + assertEquals(parent2, pk3.parent()); } } diff --git a/gcloud-java-datastore/src/test/java/com/google/gcloud/datastore/LocalGcdHelperTest.java b/gcloud-java-datastore/src/test/java/com/google/gcloud/datastore/LocalGcdHelperTest.java index 40ea62c5a7e0..5d761a713506 100644 --- a/gcloud-java-datastore/src/test/java/com/google/gcloud/datastore/LocalGcdHelperTest.java +++ b/gcloud-java-datastore/src/test/java/com/google/gcloud/datastore/LocalGcdHelperTest.java @@ -49,7 +49,7 @@ public void testFindAvailablePort() { @Test public void testSendQuitRequest() throws IOException, InterruptedException { - LocalGcdHelper gcdHelper = LocalGcdHelper.start(PROJECT_ID, PORT); + LocalGcdHelper gcdHelper = LocalGcdHelper.start(PROJECT_ID, PORT, 0.75); assertTrue(LocalGcdHelper.sendQuitRequest(PORT)); long timeoutMillis = 30000; long startTime = System.currentTimeMillis(); @@ -64,7 +64,7 @@ public void testSendQuitRequest() throws IOException, InterruptedException { @Test public void testStartStop() throws IOException, InterruptedException { - LocalGcdHelper gcdHelper = LocalGcdHelper.start(PROJECT_ID, PORT); + LocalGcdHelper gcdHelper = LocalGcdHelper.start(PROJECT_ID, PORT, 0.75); assertFalse(LocalGcdHelper.isActive("wrong-project-id", PORT)); assertTrue(LocalGcdHelper.isActive(PROJECT_ID, PORT)); gcdHelper.stop();