diff --git a/datastore/README.md b/datastore/README.md new file mode 100644 index 00000000000..a43ad13237d --- /dev/null +++ b/datastore/README.md @@ -0,0 +1,14 @@ +## Datastore Samples + +This directory contains sample code used in Google Cloud Datastore documentation. Included here is a sample command line application, `TaskList`, that interacts with Datastore to manage a to-do list. + +## Run the `TaskList` sample application. + +1. Ensure that you have: + * Created a Google Developers Console project with the Datastore API enabled. Follow [these instructions](https://cloud.google.com/docs/authentication#preparation) to get your project set up. + * Installed the Google Cloud SDK and run the following commands in command line: `gcloud auth login` and `gcloud config set project [YOUR PROJECT ID]`. + * Installed [Maven](https://maven.apache.org/) and Java 7 (or above). + +2. Compile the program by typing `mvn clean compile` in command line. + +3. Run the program by typing `mvn exec:java` in command line. In addition to listing tasks via this command line interface, you can view tasks you create in the [Google Cloud Developer's Console](https://console.cloud.google.com/). diff --git a/datastore/pom.xml b/datastore/pom.xml new file mode 100644 index 00000000000..68806fa3731 --- /dev/null +++ b/datastore/pom.xml @@ -0,0 +1,65 @@ + + + 4.0.0 + com.google.datastore.snippets + datastore-snippets + 1.0 + jar + Google Cloud Datastore Snippets + + Example snippets for Datastore concepts and getting started documentation. + + + + com.google.gcloud + gcloud-java-datastore + 0.1.3 + + + junit + junit + 4.12 + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 2.5.1 + + 1.8 + 1.8 + + + + + + org.codehaus.mojo + exec-maven-plugin + 1.4.0 + + com.google.datastore.snippets.TaskList + + + + + org.apache.maven.plugins + maven-checkstyle-plugin + 2.17 + + ../google-checks.xml + true + true + true + + + check + + + + + diff --git a/datastore/src/main/java/com/google/datastore/snippets/Concepts.java b/datastore/src/main/java/com/google/datastore/snippets/Concepts.java new file mode 100644 index 00000000000..eedd05c7bdc --- /dev/null +++ b/datastore/src/main/java/com/google/datastore/snippets/Concepts.java @@ -0,0 +1,1053 @@ +/* + * Copyright 2016 Google Inc. All Rights Reserved. + * + * 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.google.datastore.snippets; + +import static java.util.Calendar.DECEMBER; +import static java.util.Calendar.JANUARY; +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 com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterators; +import com.google.gcloud.datastore.Cursor; +import com.google.gcloud.datastore.Datastore; +import com.google.gcloud.datastore.DatastoreException; +import com.google.gcloud.datastore.DatastoreOptions; +import com.google.gcloud.datastore.DateTime; +import com.google.gcloud.datastore.Entity; +import com.google.gcloud.datastore.EntityQuery; +import com.google.gcloud.datastore.FullEntity; +import com.google.gcloud.datastore.IncompleteKey; +import com.google.gcloud.datastore.Key; +import com.google.gcloud.datastore.KeyFactory; +import com.google.gcloud.datastore.PathElement; +import com.google.gcloud.datastore.ProjectionEntity; +import com.google.gcloud.datastore.Query; +import com.google.gcloud.datastore.Query.ResultType; +import com.google.gcloud.datastore.QueryResults; +import com.google.gcloud.datastore.StringValue; +import com.google.gcloud.datastore.StructuredQuery; +import com.google.gcloud.datastore.StructuredQuery.CompositeFilter; +import com.google.gcloud.datastore.StructuredQuery.OrderBy; +import com.google.gcloud.datastore.StructuredQuery.Projection; +import com.google.gcloud.datastore.StructuredQuery.PropertyFilter; +import com.google.gcloud.datastore.Transaction; +import com.google.gcloud.datastore.testing.LocalGcdHelper; + +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.TimeZone; + +/** + * Contains Cloud Datastore snippets linked from the Concepts documentation. + * + * @see Key Datastore Concepts + */ +public class Concepts { + + private static final String PROJECT_ID = LocalGcdHelper.DEFAULT_PROJECT_ID; + private static LocalGcdHelper gcdHelper; + private static final int PORT = LocalGcdHelper.findAvailablePort(LocalGcdHelper.DEFAULT_PORT); + private static final FullEntity TEST_FULL_ENTITY = FullEntity.builder().build(); + + private Datastore datastore; + private KeyFactory keyFactory; + private Key taskKey; + private Entity testEntity; + private DateTime startDate; + private DateTime endDate; + private DateTime includedDate; + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + /** + * Starts the local Datastore emulator. + * + * @throws IOException if there are errors starting the local Datastore + * @throws InterruptedException if there are errors starting the local Datastore + */ + @BeforeClass + public static void beforeClass() throws IOException, InterruptedException { + if (!LocalGcdHelper.isActive(PROJECT_ID, PORT)) { + gcdHelper = LocalGcdHelper.start(PROJECT_ID, PORT); + } + } + + /** + * Initializes Datastore and cleans out any residual values. Also initializes global variables + * used for testing. + */ + @Before + public void setUp() { + datastore = DatastoreOptions.builder() + .projectId(PROJECT_ID) + .namespace("ghijklmnop") + .host("http://localhost:" + PORT) + .build() + .service(); + StructuredQuery query = Query.keyQueryBuilder().build(); + QueryResults result = datastore.run(query); + datastore.delete(Iterators.toArray(result, Key.class)); + keyFactory = datastore.newKeyFactory().kind("Task"); + taskKey = keyFactory.newKey("some-arbitrary-key"); + testEntity = Entity.builder(taskKey, TEST_FULL_ENTITY).build(); + Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC")); + calendar.set(1990, JANUARY, 1); + startDate = DateTime.copyFrom(calendar); + calendar.set(2000, JANUARY, 1); + endDate = DateTime.copyFrom(calendar); + calendar.set(1999, DECEMBER, 31); + includedDate = DateTime.copyFrom(calendar); + } + + /** + * Stops the local Datastore emulator. + * + * @throws IOException if there are errors stopping the local Datastore + * @throws InterruptedException if there are errors stopping the local Datastore + */ + @AfterClass + public static void afterClass() throws IOException, InterruptedException { + if (gcdHelper != null) { + gcdHelper.stop(); + } + } + + private void assertValidKey(Key taskKey) { + datastore.put(Entity.builder(taskKey, TEST_FULL_ENTITY).build()); + } + + @Test + public void testIncompleteKey() { + // [START incomplete_key] + KeyFactory keyFactory = datastore.newKeyFactory().kind("Task"); + Key taskKey = datastore.allocateId(keyFactory.newKey()); + // [END incomplete_key] + assertValidKey(taskKey); + } + + @Test + public void testNamedKey() { + // [START named_key] + Key taskKey = datastore.newKeyFactory().kind("Task").newKey("sampleTask"); + // [END named_key] + assertValidKey(taskKey); + } + + @Test + public void testKeyWithParent() { + // [START key_with_parent] + Key taskKey = datastore.newKeyFactory() + .ancestors(PathElement.of("TaskList", "default")) + .kind("Task") + .newKey("sampleTask"); + // [END key_with_parent] + assertValidKey(taskKey); + } + + @Test + public void testKeyWithMultilevelParent() { + // [START key_with_multilevel_parent] + KeyFactory keyFactory = datastore.newKeyFactory() + .ancestors(PathElement.of("User", "Alice"), PathElement.of("TaskList", "default")) + .kind("Task"); + Key taskKey = keyFactory.newKey("sampleTask"); + // [END key_with_multilevel_parent] + assertValidKey(taskKey); + } + + private void assertValidEntity(Entity original) { + datastore.put(original); + assertEquals(original, datastore.get(original.key())); + } + + @Test + public void testEntityWithParent() { + // [START entity_with_parent] + Key taskKey = datastore.newKeyFactory() + .ancestors(PathElement.of("TaskList", "default")) + .kind("Task") + .newKey("sampleTask"); + Entity task = Entity.builder(taskKey) + .set("type", "Personal") + .set("done", false) + .set("priority", 4) + .set("description", "Learn Cloud Datastore") + .build(); + // [END entity_with_parent] + assertValidEntity(task); + } + + @Test + public void testProperties() { + // [START properties] + Entity task = Entity.builder(taskKey) + .set("type", "Personal") + .set("created", DateTime.now()) + .set("done", false) + .set("priority", 4) + .set("percent_complete", 10.0) + .set("description", "Learn Cloud Datastore") + .build(); + // [END properties] + assertValidEntity(task); + } + + @Test + public void testArrayValue() { + // [START array_value] + Entity task = Entity.builder(taskKey) + .set("tags", StringValue.of("fun"), StringValue.of("programming")) + .set("collaborators", StringValue.of("alice"), StringValue.of("bob")) + .build(); + // [END array_value] + assertValidEntity(task); + } + + @Test + public void testBasicEntity() { + // [START basic_entity] + Entity task = Entity.builder(taskKey) + .set("type", "Personal") + .set("done", false) + .set("priority", 4) + .set("description", "Learn Cloud Datastore") + .build(); + // [END basic_entity] + assertValidEntity(task); + } + + @Test + public void testUpsert() { + // [START upsert] + Entity task = Entity.builder(keyFactory.newKey("sampleTask")).build(); + datastore.put(task); + // [END upsert] + assertEquals(task, datastore.get(task.key())); + } + + @Test + public void testInsert() { + // [START insert] + Key taskKey = datastore.add(FullEntity.builder(keyFactory.newKey()).build()).key(); + // [END insert] + assertEquals(FullEntity.builder(taskKey).build(), datastore.get(taskKey)); + } + + @Test + public void testLookup() { + datastore.put(testEntity); + // [START lookup] + Entity task = datastore.get(taskKey); + // [END lookup] + assertEquals(testEntity, task); + } + + @Test + public void testUpdate() { + datastore.put(testEntity); + // [START update] + Entity task = Entity.builder(datastore.get(taskKey)).set("priority", 5).build(); + datastore.update(task); + // [END update] + assertEquals(task, datastore.get(taskKey)); + } + + @Test + public void testDelete() { + datastore.put(testEntity); + // [START delete] + datastore.delete(taskKey); + // [END delete] + assertNull(datastore.get(taskKey)); + } + + private List setUpBatchTests(Key taskKey1, Key taskKey2) { + Entity task1 = Entity.builder(taskKey1) + .set("type", "Personal") + .set("done", false) + .set("priority", 4) + .set("description", "Learn Cloud Datastore") + .build(); + Entity task2 = Entity.builder(taskKey2) + .set("type", "Personal") + .set("done", false) + .set("priority", 5) + .set("description", "Integrate Cloud Datastore") + .build(); + datastore.put(task1, task2); + return ImmutableList.of(task1, task2); + } + + @Test + public void testBatchUpsert() { + // [START batch_upsert] + FullEntity task1 = FullEntity.builder(keyFactory.newKey()) + .set("type", "Personal") + .set("done", false) + .set("priority", 4) + .set("description", "Learn Cloud Datastore") + .build(); + FullEntity task2 = Entity.builder(keyFactory.newKey()) + .set("type", "Personal") + .set("done", false) + .set("priority", 5) + .set("description", "Integrate Cloud Datastore") + .build(); + List tasks = datastore.add(task1, task2); + Key taskKey1 = tasks.get(0).key(); + Key taskKey2 = tasks.get(1).key(); + // [END batch_upsert] + assertEquals(Entity.builder(taskKey1, task1).build(), datastore.get(taskKey1)); + assertEquals(Entity.builder(taskKey2, task2).build(), datastore.get(taskKey2)); + } + + @Test + public void testBatchLookup() { + Key taskKey1 = keyFactory.newKey(1); + Key taskKey2 = keyFactory.newKey(2); + List expectedTasks = setUpBatchTests(taskKey1, taskKey2); + // [START batch_lookup] + Iterator tasks = datastore.get(taskKey1, taskKey2); + // [END batch_lookup] + assertEquals(expectedTasks.get(0), tasks.next()); + assertEquals(expectedTasks.get(1), tasks.next()); + } + + @Test + public void testBatchDelete() { + Key taskKey1 = keyFactory.newKey(1); + Key taskKey2 = keyFactory.newKey(2); + setUpBatchTests(taskKey1, taskKey2); + // [START batch_delete] + datastore.delete(taskKey1, taskKey2); + // [END batch_delete] + assertNull(datastore.get(taskKey1)); + assertNull(datastore.get(taskKey2)); + } + + private void setUpQueryTests() { + Key taskKey = datastore.newKeyFactory() + .kind("Task") + .ancestors(PathElement.of("TaskList", "default")) + .newKey("someTask"); + datastore.put(Entity.builder(taskKey) + .set("type", "Personal") + .set("done", false) + .set("completed", false) + .set("priority", 4) + .set("created", includedDate) + .set("percent_complete", 10.0) + .set("description", StringValue.builder("Learn Cloud Datastore").indexed(false).build()) + .set("tag", StringValue.of("fun"), StringValue.of("l"), StringValue.of("programming")) + .build()); + } + + private V assertValidQuery(Query query) { + QueryResults results = datastore.run(query); + V result = results.next(); + assertFalse(results.hasNext()); + return result; + } + + private void assertInvalidQuery(Query query) { + thrown.expect(DatastoreException.class); + datastore.run(query); + } + + @Test + public void testBasicQuery() { + setUpQueryTests(); + // [START basic_query] + Query query = Query.entityQueryBuilder() + .kind("Task") + .filter(CompositeFilter.and( + PropertyFilter.eq("done", false), PropertyFilter.ge("priority", 4))) + .orderBy(OrderBy.desc("priority")) + .build(); + // [END basic_query] + assertValidQuery(query); + } + + @Test + public void testRunQuery() { + setUpQueryTests(); + Query query = Query.entityQueryBuilder().kind("Task").build(); + // [START run_query] + QueryResults tasks = datastore.run(query); + // [END run_query] + assertNotNull(tasks.next()); + assertFalse(tasks.hasNext()); + } + + @Test + public void testPropertyFilter() { + setUpQueryTests(); + // [START property_filter] + Query query = + Query.entityQueryBuilder().kind("Task").filter(PropertyFilter.eq("done", false)).build(); + // [END property_filter] + assertValidQuery(query); + } + + @Test + public void testCompositeFilter() { + setUpQueryTests(); + // [START composite_filter] + Query query = Query.entityQueryBuilder() + .kind("Task") + .filter( + CompositeFilter.and(PropertyFilter.eq("done", false), PropertyFilter.eq("priority", 4))) + .build(); + // [END composite_filter] + assertValidQuery(query); + } + + @Test + public void testKeyFilter() { + setUpQueryTests(); + // [START key_filter] + Query query = Query.entityQueryBuilder() + .kind("Task") + .filter(PropertyFilter.gt("__key__", keyFactory.newKey("someTask"))) + .build(); + // [END key_filter] + assertValidQuery(query); + } + + @Test + public void testAscendingSort() { + setUpQueryTests(); + // [START ascending_sort] + Query query = + Query.entityQueryBuilder().kind("Task").orderBy(OrderBy.asc("created")).build(); + // [END ascending_sort] + assertValidQuery(query); + } + + @Test + public void testDescendingSort() { + setUpQueryTests(); + // [START descending_sort] + Query query = + Query.entityQueryBuilder().kind("Task").orderBy(OrderBy.desc("created")).build(); + // [END descending_sort] + assertValidQuery(query); + } + + @Test + public void testMultiSort() { + setUpQueryTests(); + // [START multi_sort] + Query query = Query.entityQueryBuilder() + .kind("Task") + .orderBy(OrderBy.desc("priority"), OrderBy.asc("created")) + .build(); + // [END multi_sort] + assertValidQuery(query); + } + + @Test + public void testKindlessQuery() { + Key lastSeenKey = keyFactory.newKey("a"); + setUpQueryTests(); + // [START kindless_query] + Query query = + Query.entityQueryBuilder().filter(PropertyFilter.gt("__key__", lastSeenKey)).build(); + // [END kindless_query] + assertValidQuery(query); + } + + @Test + public void testAncestorQuery() { + setUpQueryTests(); + // [START ancestor_query] + Query query = Query.entityQueryBuilder() + .kind("Task") + .filter(PropertyFilter.hasAncestor( + datastore.newKeyFactory().kind("TaskList").newKey("default"))) + .build(); + // [END ancestor_query] + assertValidQuery(query); + } + + @Test + public void testProjectionQuery() { + setUpQueryTests(); + // [START projection_query] + Query query = Query.projectionEntityQueryBuilder() + .kind("Task") + .projection(Projection.property("priority"), Projection.property("percent_complete")) + .build(); + // [END projection_query] + assertValidQuery(query); + } + + @Test + public void testRunProjectionQuery() { + setUpQueryTests(); + Query query = Query.projectionEntityQueryBuilder() + .kind("Task") + .projection(Projection.property("priority"), Projection.property("percent_complete")) + .build(); + // [START run_query_projection] + List priorities = new LinkedList<>(); + List percentCompletes = new LinkedList<>(); + QueryResults tasks = datastore.run(query); + while (tasks.hasNext()) { + ProjectionEntity task = tasks.next(); + priorities.add(task.getLong("priority")); + percentCompletes.add(task.getDouble("percent_complete")); + } + // [END run_query_projection] + assertEquals(ImmutableList.of(4L), priorities); + assertEquals(ImmutableList.of(10.0), percentCompletes); + } + + @Test + public void testKeysOnlyQuery() { + setUpQueryTests(); + // [START keys_only_query] + Query query = Query.keyQueryBuilder().kind("Task").build(); + // [END keys_only_query] + assertValidQuery(query); + } + + @Test + public void testRunKeysOnlyQuery() { + setUpQueryTests(); + Query query = Query.keyQueryBuilder().kind("Task").build(); + // [START run_keys_only_query] + QueryResults taskKeys = datastore.run(query); + // [END run_keys_only_query] + assertNotNull(taskKeys.next()); + assertFalse(taskKeys.hasNext()); + } + + @Test + public void testDistinctQuery() { + setUpQueryTests(); + // [START distinct_query] + Query query = Query.projectionEntityQueryBuilder() + .kind("Task") + .projection(Projection.property("type"), Projection.property("priority")) + .groupBy("type", "priority") + .orderBy(OrderBy.asc("type"), OrderBy.asc("priority")) + .build(); + // [END distinct_query] + assertValidQuery(query); + } + + @Test + public void testDistinctOnQuery() { + setUpQueryTests(); + // [START distinct_on_query] + Query query = Query.projectionEntityQueryBuilder() + .kind("Task") + .projection(Projection.property("type"), Projection.first("priority")) + .groupBy("type") + .orderBy(OrderBy.asc("type"), OrderBy.asc("priority")) + .build(); + // [END distinct_on_query] + assertValidQuery(query); + } + + @Test + public void testArrayValueInequalityRange() { + setUpQueryTests(); + // [START array_value_inequality_range] + Query query = Query.entityQueryBuilder() + .kind("Task") + .filter(CompositeFilter.and( + PropertyFilter.gt("tag", "learn"), PropertyFilter.lt("tag", "math"))) + .build(); + // [END array_value_inequality_range] + QueryResults results = datastore.run(query); + assertFalse(results.hasNext()); + } + + @Test + public void testArrayValueEquality() { + setUpQueryTests(); + // [START array_value_equality] + Query query = Query.entityQueryBuilder() + .kind("Task") + .filter(CompositeFilter.and( + PropertyFilter.eq("tag", "fun"), PropertyFilter.eq("tag", "programming"))) + .build(); + // [END array_value_equality] + assertValidQuery(query); + } + + @Test + public void testInequalityRange() { + setUpQueryTests(); + // [START inequality_range] + Query query = Query.entityQueryBuilder() + .kind("Task") + .filter(CompositeFilter.and( + PropertyFilter.gt("created", startDate), PropertyFilter.lt("created", endDate))) + .build(); + // [END inequality_range] + assertValidQuery(query); + } + + @Test + public void testInequalityInvalid() { + // [START inequality_invalid] + Query query = Query.entityQueryBuilder() + .kind("Task") + .filter(CompositeFilter.and( + PropertyFilter.gt("created", startDate), PropertyFilter.gt("priority", 3))) + .build(); + // [END inequality_invalid] + assertInvalidQuery(query); + } + + @Test + public void testEqualAndInequalityRange() { + setUpQueryTests(); + // [START equal_and_inequality_range] + Query query = Query.entityQueryBuilder() + .kind("Task") + .filter(CompositeFilter.and(PropertyFilter.eq("priority", 4), + PropertyFilter.gt("created", startDate), PropertyFilter.lt("created", endDate))) + .build(); + // [END equal_and_inequality_range] + assertValidQuery(query); + } + + @Test + public void testInequalitySort() { + setUpQueryTests(); + // [START inequality_sort] + Query query = Query.entityQueryBuilder() + .kind("Task") + .filter(PropertyFilter.gt("priority", 3)) + .orderBy(OrderBy.asc("priority"), OrderBy.asc("created")) + .build(); + // [END inequality_sort] + assertValidQuery(query); + } + + @Test + public void testInequalitySortInvalidNotSame() { + // [START inequality_sort_invalid_not_same] + Query query = Query.entityQueryBuilder() + .kind("Task") + .filter(PropertyFilter.gt("priority", 3)) + .orderBy(OrderBy.asc("created")) + .build(); + // [END inequality_sort_invalid_not_same] + assertInvalidQuery(query); + } + + @Test + public void testInequalitySortInvalidNotFirst() { + // [START inequality_sort_invalid_not_first] + Query query = Query.entityQueryBuilder() + .kind("Task") + .filter(PropertyFilter.gt("priority", 3)) + .orderBy(OrderBy.asc("created"), OrderBy.asc("priority")) + .build(); + // [END inequality_sort_invalid_not_first] + assertInvalidQuery(query); + } + + @Test + public void testLimit() { + setUpQueryTests(); + // [START limit] + Query query = Query.entityQueryBuilder().kind("Task").limit(5).build(); + // [END limit] + assertValidQuery(query); + } + + @Test + public void testCursorPaging() { + setUpQueryTests(); + datastore.put(testEntity); + Cursor nextPageCursor = cursorPaging(1, null); + assertNotNull(nextPageCursor); + nextPageCursor = cursorPaging(1, nextPageCursor); + assertNotNull(nextPageCursor); + } + + private Cursor cursorPaging(int pageSize, Cursor pageCursor) { + // [START cursor_paging] + EntityQuery.Builder queryBuilder = Query.entityQueryBuilder().kind("Task").limit(pageSize); + if (pageCursor != null) { + queryBuilder.startCursor(pageCursor); + } + QueryResults tasks = datastore.run(queryBuilder.build()); + while (tasks.hasNext()) { + Entity task = tasks.next(); + // do something with the task + } + Cursor nextPageCursor = tasks.cursorAfter(); + // [END cursor_paging] + return nextPageCursor; + } + + @Test + public void testEventualConsistentQuery() { + // [START eventual_consistent_query] + // Read consistency cannot be specified in gcloud-java. + // [END eventual_consistent_query] + } + + @Test + public void testUnindexedPropertyQuery() { + setUpQueryTests(); + // [START unindexed_property_query] + Query query = Query.entityQueryBuilder() + .kind("Task") + .filter(PropertyFilter.eq("description", "A task description")) + .build(); + // [END unindexed_property_query] + QueryResults results = datastore.run(query); + assertFalse(results.hasNext()); + } + + @Test + public void testExplodingProperties() { + // [START exploding_properties] + Entity task = Entity.builder(taskKey) + .set("tags", StringValue.of("fun"), StringValue.of("programming"), StringValue.of("learn")) + .set("collaborators", StringValue.of("alice"), StringValue.of("bob"), + StringValue.of("charlie")) + .set("created", DateTime.now()) + .build(); + // [END exploding_properties] + assertValidEntity(task); + } + + private List setUpTransferTests() { + KeyFactory keyFactory = datastore.newKeyFactory().kind("People"); + Key from = keyFactory.newKey("from"); + Key to = keyFactory.newKey("to"); + datastore.put(Entity.builder(from).set("balance", 100).build()); + datastore.put(Entity.builder(to).set("balance", 0).build()); + return ImmutableList.of(from, to); + } + + private void assertSuccessfulTransfer(Key from, Key to) { + assertEquals(90, datastore.get(from).getLong("balance")); + assertEquals(10, datastore.get(to).getLong("balance")); + } + + @Test + public void testTransactionalUpdate() { + List keys = setUpTransferTests(); + transferFunds(keys.get(0), keys.get(1), 10); + assertSuccessfulTransfer(keys.get(0), keys.get(1)); + } + + // [START transactional_update] + void transferFunds(Key fromKey, Key toKey, long amount) { + Transaction txn = datastore.newTransaction(); + try { + List entities = txn.fetch(fromKey, toKey); + Entity from = entities.get(0); + Entity updatedFrom = + Entity.builder(from).set("balance", from.getLong("balance") - amount).build(); + Entity to = entities.get(1); + Entity updatedTo = Entity.builder(to).set("balance", to.getLong("balance") + amount).build(); + txn.put(updatedFrom, updatedTo); + txn.commit(); + } finally { + if (txn.active()) { + txn.rollback(); + } + } + } + // [END transactional_update] + + @Test + public void testTransactionalRetry() { + List keys = setUpTransferTests(); + Key fromKey = keys.get(0); + Key toKey = keys.get(1); + // [START transactional_retry] + int retries = 5; + while (true) { + try { + transferFunds(fromKey, toKey, 10); + break; + } catch (DatastoreException e) { + if (retries == 0) { + throw e; + } + --retries; + } + } + // Retry handling can also be configured and automatically applied using gcloud-java. + // [END transactional_retry] + assertSuccessfulTransfer(keys.get(0), keys.get(1)); + } + + @Test + public void testTransactionalGetOrCreate() { + // [START transactional_get_or_create] + Entity task; + Transaction txn = datastore.newTransaction(); + try { + task = txn.get(taskKey); + if (task == null) { + task = Entity.builder(taskKey).build(); + txn.put(task); + txn.commit(); + } + } finally { + if (txn.active()) { + txn.rollback(); + } + } + // [END transactional_get_or_create] + assertEquals(task, datastore.get(taskKey)); + } + + @Test + public void testTransactionalSingleEntityGroupReadOnly() { + setUpQueryTests(); + Key taskListKey = datastore.newKeyFactory().kind("TaskList").newKey("default"); + Entity taskListEntity = Entity.builder(taskListKey).build(); + datastore.put(taskListEntity); + // [START transactional_single_entity_group_read_only] + Entity taskList; + QueryResults tasks; + Transaction txn = datastore.newTransaction(); + try { + taskList = txn.get(taskListKey); + Query query = Query.entityQueryBuilder() + .kind("Task") + .filter(PropertyFilter.hasAncestor(taskListKey)) + .build(); + tasks = txn.run(query); + txn.commit(); + } finally { + if (txn.active()) { + txn.rollback(); + } + } + // [END transactional_single_entity_group_read_only] + assertEquals(taskListEntity, taskList); + assertNotNull(tasks.next()); + assertFalse(tasks.hasNext()); + } + + @Test + public void testNamespaceRunQuery() { + setUpQueryTests(); + // [START namespace_run_query] + KeyFactory keyFactory = datastore.newKeyFactory().kind("__namespace__"); + Key startNamespace = keyFactory.newKey("g"); + Key endNamespace = keyFactory.newKey("h"); + Query query = Query.keyQueryBuilder() + .kind("__namespace__") + .filter(CompositeFilter.and( + PropertyFilter.gt("__key__", startNamespace), + PropertyFilter.lt("__key__", endNamespace))) + .build(); + List namespaces = new ArrayList<>(); + QueryResults results = datastore.run(query); + while (results.hasNext()) { + namespaces.add(results.next().name()); + } + // [END namespace_run_query] + assertEquals(ImmutableList.of("ghijklmnop"), namespaces); + } + + @Test + public void testKindRunQuery() { + setUpQueryTests(); + // [START kind_run_query] + Query query = Query.keyQueryBuilder().kind("__kind__").build(); + List kinds = new ArrayList<>(); + QueryResults results = datastore.run(query); + while (results.hasNext()) { + kinds.add(results.next().name()); + } + // [END kind_run_query] + assertEquals(ImmutableList.of("Task"), kinds); + } + + @Test + public void testPropertyRunQuery() { + setUpQueryTests(); + // [START property_run_query] + Query query = Query.keyQueryBuilder().kind("__property__").build(); + QueryResults results = datastore.run(query); + Map> propertiesByKind = new HashMap<>(); + while (results.hasNext()) { + Key property = results.next(); + String kind = property.ancestors().get(property.ancestors().size() - 1).name(); + String propertyName = property.name(); + Collection properties = propertiesByKind.get(kind); + if (properties == null) { + properties = new HashSet<>(); + propertiesByKind.put(kind, properties); + } + properties.add(propertyName); + } + // [END property_run_query] + Map> expected = ImmutableMap.of("Task", ImmutableSet.of( + "done", "type", "done", "completed", "priority", "created", "percent_complete", "tag")); + assertEquals(expected, propertiesByKind); + } + + @Test + public void testPropertyByKindRunQuery() { + setUpQueryTests(); + // [START property_by_kind_run_query] + Key key = datastore.newKeyFactory().kind("__kind__").newKey("Task"); + Query query = Query.entityQueryBuilder() + .kind("__property__") + .filter(PropertyFilter.hasAncestor(key)) + .build(); + QueryResults results = datastore.run(query); + Map> representationsByProperty = new HashMap<>(); + while (results.hasNext()) { + Entity property = results.next(); + String propertyName = property.key().name(); + List representations = + (List) property.getList("property_representation"); + Collection currentRepresentations = representationsByProperty.get(propertyName); + if (currentRepresentations == null) { + currentRepresentations = new HashSet<>(); + representationsByProperty.put(propertyName, currentRepresentations); + } + for (StringValue value : representations) { + currentRepresentations.add(value.get()); + } + } + // [END property_by_kind_run_query] + Map> expected = ImmutableMap.>builder() + .put("type", Collections.singleton("STRING")) + .put("done", Collections.singleton("BOOLEAN")) + .put("completed", Collections.singleton("BOOLEAN")) + .put("priority", Collections.singleton("INT64")) + .put("created", Collections.singleton("INT64")) + .put("percent_complete", Collections.singleton("DOUBLE")) + .put("tag", Collections.singleton("STRING")) + .build(); + assertEquals(expected, representationsByProperty); + } + + @Test + public void testPropertyFilteringRunQuery() { + setUpQueryTests(); + // [START property_filtering_run_query] + Key key = + datastore.newKeyFactory() + .kind("__property__") + .ancestors(PathElement.of("__kind__", "Task")) + .newKey("priority"); + Query query = Query.keyQueryBuilder() + .kind("__property__") + .filter(PropertyFilter.ge("__key__", key)) + .build(); + Map> propertiesByKind = new HashMap<>(); + QueryResults results = datastore.run(query); + while (results.hasNext()) { + Key property = results.next(); + String kind = property.ancestors().get(property.ancestors().size() - 1).name(); + String propertyName = property.name(); + Collection properties = propertiesByKind.get(kind); + if (properties == null) { + properties = new HashSet(); + propertiesByKind.put(kind, properties); + } + properties.add(propertyName); + } + // [END property_filtering_run_query] + Map> expected = + ImmutableMap.of("Task", ImmutableSet.of("priority", "tag", "type")); + assertEquals(expected, propertiesByKind); + } + + @Test + public void testGqlRunQuery() { + setUpQueryTests(); + // [START gql_run_query] + Query query = + Query.gqlQueryBuilder(ResultType.ENTITY, "select * from Task order by created asc").build(); + // [END gql_run_query] + assertValidQuery(query); + } + + @Test + public void testGqlNamedBindingQuery() { + setUpQueryTests(); + // [START gql_named_binding_query] + Query query = + Query.gqlQueryBuilder( + ResultType.ENTITY, + "select * from Task where completed = @completed and priority = @priority") + .setBinding("completed", false) + .setBinding("priority", 4) + .build(); + // [END gql_named_binding_query] + assertValidQuery(query); + } + + @Test + public void testGqlPositionalBindingQuery() { + setUpQueryTests(); + // [START gql_positional_binding_query] + Query query = Query.gqlQueryBuilder( + ResultType.ENTITY, "select * from Task where completed = @1 and priority = @2") + .addBinding(false) + .addBinding(4) + .build(); + // [END gql_positional_binding_query] + assertValidQuery(query); + } + + @Test + public void testGqlLiteralQuery() { + setUpQueryTests(); + // [START gql_literal_query] + Query query = Query.gqlQueryBuilder( + ResultType.ENTITY, "select * from Task where completed = false and priority = 4") + .allowLiteral(true) + .build(); + // [END gql_literal_query] + assertValidQuery(query); + } +} diff --git a/datastore/src/main/java/com/google/datastore/snippets/TaskList.java b/datastore/src/main/java/com/google/datastore/snippets/TaskList.java new file mode 100644 index 00000000000..40920e291cd --- /dev/null +++ b/datastore/src/main/java/com/google/datastore/snippets/TaskList.java @@ -0,0 +1,231 @@ +/* + * Copyright 2016 Google Inc. All Rights Reserved. + * + * 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.google.datastore.snippets; + +import com.google.gcloud.datastore.Datastore; +import com.google.gcloud.datastore.DatastoreException; +import com.google.gcloud.datastore.DatastoreOptions; +import com.google.gcloud.datastore.DateTime; +import com.google.gcloud.datastore.Entity; +import com.google.gcloud.datastore.Key; +import com.google.gcloud.datastore.KeyFactory; +import com.google.gcloud.datastore.Query; +import com.google.gcloud.datastore.StringValue; +import com.google.gcloud.datastore.StructuredQuery.OrderBy; +import com.google.gcloud.datastore.Transaction; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +/** + * A simple Task List application demonstrating how to connect to Cloud Datastore, create, modify, + * delete, and query entities. + */ +public class TaskList { + + // [START build_service] + // Create an authorized Datastore service using Application Default Credentials. + private final Datastore datastore = DatastoreOptions.defaultInstance().service(); + + // Create a Key factory to construct keys associated with this project. + private final KeyFactory keyFactory = datastore.newKeyFactory().kind("Task"); + // [END build_service] + + // [START add_entity] + /** + * Adds a task entity to the Datastore. + * + * @param description The task description + * @return The {@link Key} of the entity. + */ + Key addTask(String description) { + Key key = datastore.allocateId(keyFactory.newKey()); + Entity task = Entity.builder(key) + .set("description", StringValue.builder(description).indexed(false).build()) + .set("created", DateTime.now()) + .set("done", false) + .build(); + datastore.put(task); + return key; + } + // [END add_entity] + + // [START update_entity] + /** + * Marks a task entity as done. + * + * @param id The ID of the task entity as given by {@link Key#id()} + * @throws DatastoreException if the task does not exist + */ + void markDone(long id) { + Transaction transaction = datastore.newTransaction(); + try { + Entity task = transaction.get(keyFactory.newKey(id)); + transaction.put(Entity.builder(task).set("done", true).build()); + transaction.commit(); + } finally { + if (transaction.active()) { + transaction.rollback(); + } + } + } + // [END update_entity] + + // [START retrieve_entities] + /** + * Returns a list of all task entities in ascending order of creation time. + */ + Iterator listTasks() { + Query query = + Query.entityQueryBuilder().kind("Task").orderBy(OrderBy.asc("created")).build(); + return datastore.run(query); + } + // [END retrieve_entities] + + // [START delete_entity] + /** + * Deletes a task entity. + * + * @param id The ID of the task entity as given by {@link Key#id()} + */ + void deleteTask(long id) { + datastore.delete(keyFactory.newKey(id)); + } + // [END delete_entity] + + // [START format_results] + /** + * Converts a list of task entities to a list of formatted task strings. + * + * @param tasks An iterator over task entities + * @return A list of tasks strings, one per entity + */ + static List formatTasks(Iterator tasks) { + List strings = new ArrayList<>(); + while (tasks.hasNext()) { + Entity task = tasks.next(); + if (task.getBoolean("done")) { + strings.add( + String.format("%d : %s (done)", task.key().id(), task.getString("description"))); + } else { + strings.add(String.format("%d : %s (created %s)", task.key().id(), + task.getString("description"), task.getDateTime("created"))); + } + } + return strings; + } + // [END format_results] + + /** + * Handles a single command. + * + * @param commandLine A line of input provided by the user + */ + void handleCommandLine(String commandLine) { + String[] args = commandLine.split("\\s+"); + + if (args.length < 1) { + throw new IllegalArgumentException("not enough args"); + } + + String command = args[0]; + switch (command) { + case "new": + // Everything after the first whitespace token is interpreted to be the description. + args = commandLine.split("\\s+", 2); + if (args.length != 2) { + throw new IllegalArgumentException("missing description"); + } + // Set created to now() and done to false. + addTask(args[1]); + System.out.println("task added"); + break; + case "done": + assertArgsLength(args, 2); + long id = Long.parseLong(args[1]); + try { + markDone(id); + System.out.println("task marked done"); + } catch (DatastoreException e) { + System.out.printf("did not find a Task entity with ID %d%n", id); + } + break; + case "list": + assertArgsLength(args, 1); + List tasks = formatTasks(listTasks()); + System.out.printf("found %d tasks:%n", tasks.size()); + System.out.println("task ID : description"); + System.out.println("---------------------"); + for (String taskString : tasks) { + System.out.println(taskString); + } + break; + case "delete": + assertArgsLength(args, 2); + deleteTask(Long.parseLong(args[1])); + System.out.println("task deleted"); + break; + default: + throw new IllegalArgumentException("unrecognized command: " + command); + } + } + + private void assertArgsLength(String[] args, int expectedLength) { + if (args.length != expectedLength) { + throw new IllegalArgumentException( + String.format("expected exactly %d arg(s), found %d", expectedLength, args.length)); + } + } + + /** + * Exercises the methods defined in this class. + * + *

Assumes that you are authenticated using the Google Cloud SDK (using + * {@code gcloud auth login}). + */ + public static void main(String[] args) throws Exception { + TaskList taskList = new TaskList(); + System.out.println("Cloud Datastore Task List"); + System.out.println(); + printUsage(); + while (true) { + String commandLine = System.console().readLine("> "); + if (commandLine.trim().isEmpty()) { + break; + } + try { + taskList.handleCommandLine(commandLine); + } catch (IllegalArgumentException e) { + System.out.println(e.getMessage()); + printUsage(); + } + } + System.out.println("exiting"); + System.exit(0); + } + + private static void printUsage() { + System.out.println("Usage:"); + System.out.println(); + System.out.println(" new Adds a task with a description "); + System.out.println(" done Marks a task as done"); + System.out.println(" list Lists all tasks by creation time"); + System.out.println(" delete Deletes a task"); + System.out.println(); + } +} diff --git a/pom.xml b/pom.xml index c8f6370685d..1fac8254359 100644 --- a/pom.xml +++ b/pom.xml @@ -40,6 +40,7 @@ managed_vms/async-rest managed_vms/sparkjava bigquery + datastore logging monitoring storage/json-api