diff --git a/appengine/datastore/src/test/java/com/example/appengine/MetadataEntityGroupTest.java b/appengine/datastore/src/test/java/com/example/appengine/MetadataEntityGroupTest.java new file mode 100644 index 00000000000..a39f4af36ca --- /dev/null +++ b/appengine/datastore/src/test/java/com/example/appengine/MetadataEntityGroupTest.java @@ -0,0 +1,163 @@ +/* + * 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.example.appengine; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.appengine.api.datastore.DatastoreService; +import com.google.appengine.api.datastore.DatastoreServiceFactory; +import com.google.appengine.api.datastore.Entities; +import com.google.appengine.api.datastore.Entity; +import com.google.appengine.api.datastore.EntityNotFoundException; +import com.google.appengine.api.datastore.FetchOptions; +import com.google.appengine.api.datastore.Key; +import com.google.appengine.api.datastore.PreparedQuery; +import com.google.appengine.api.datastore.Query; +import com.google.appengine.api.datastore.Transaction; +import com.google.appengine.api.memcache.MemcacheService; +import com.google.appengine.api.memcache.MemcacheServiceFactory; +import com.google.appengine.tools.development.testing.LocalDatastoreServiceTestConfig; +import com.google.appengine.tools.development.testing.LocalMemcacheServiceTestConfig; +import com.google.appengine.tools.development.testing.LocalServiceTestHelper; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.io.PrintWriter; +import java.io.Serializable; +import java.io.StringWriter; + +/** + * Unit tests to demonstrate App Engine Datastore entity group metadata. + */ +@RunWith(JUnit4.class) +public class MetadataEntityGroupTest { + + private final LocalServiceTestHelper helper = + new LocalServiceTestHelper( + // Set no eventual consistency, that way queries return all results. + // https://cloud.google.com/appengine/docs/java/tools/localunittesting#Java_Writing_High_Replication_Datastore_tests + new LocalDatastoreServiceTestConfig().setDefaultHighRepJobPolicyUnappliedJobPercentage(0), + new LocalMemcacheServiceTestConfig()); + + private DatastoreService datastore; + + @Before + public void setUp() { + helper.setUp(); + datastore = DatastoreServiceFactory.getDatastoreService(); + } + + @After + public void tearDown() { + helper.tearDown(); + } + + // [START entity_group_1] + private static long getEntityGroupVersion(DatastoreService ds, Transaction tx, Key entityKey) { + try { + return Entities.getVersionProperty(ds.get(tx, Entities.createEntityGroupKey(entityKey))); + } catch (EntityNotFoundException e) { + // No entity group information, return a value strictly smaller than any + // possible version + return 0; + } + } + + private static void printEntityGroupVersions(DatastoreService ds, PrintWriter writer) { + Entity entity1 = new Entity("Simple"); + Key key1 = ds.put(entity1); + Key entityGroupKey = Entities.createEntityGroupKey(key1); + + // Print entity1's entity group version + writer.println("version " + getEntityGroupVersion(ds, null, key1)); + + // Write to a different entity group + Entity entity2 = new Entity("Simple"); + ds.put(entity2); + + // Will print the same version, as entity1's entity group has not changed + writer.println("version " + getEntityGroupVersion(ds, null, key1)); + + // Change entity1's entity group by adding a new child entity + Entity entity3 = new Entity("Simple", entity1.getKey()); + ds.put(entity3); + + // Will print a higher version, as entity1's entity group has changed + writer.println("version " + getEntityGroupVersion(ds, null, key1)); + } + // [END entity_group_1] + + @Test + public void printEntityGroupVersions_printsVersions() throws Exception { + StringWriter responseWriter = new StringWriter(); + printEntityGroupVersions(datastore, new PrintWriter(responseWriter)); + assertThat(responseWriter.toString()).contains("version"); + } + + // [START entity_group_2] + // A simple class for tracking consistent entity group counts. + private static class EntityGroupCount implements Serializable { + long version; // Version of the entity group whose count we are tracking + int count; + + EntityGroupCount(long version, int count) { + this.version = version; + this.count = count; + } + + // Display count of entities in an entity group, with consistent caching + void showEntityGroupCount( + DatastoreService ds, MemcacheService cache, PrintWriter writer, Key entityGroupKey) { + EntityGroupCount egCount = (EntityGroupCount) cache.get(entityGroupKey); + // Reuses getEntityGroupVersion method from the previous example. + if (egCount != null && egCount.version == getEntityGroupVersion(ds, null, entityGroupKey)) { + // Cached value matched current entity group version, use that + writer.println(egCount.count + " entities (cached)"); + } else { + // Need to actually count entities. Using a transaction to get a consistent count + // and entity group version. + Transaction tx = ds.beginTransaction(); + PreparedQuery pq = ds.prepare(tx, new Query(entityGroupKey)); + int count = pq.countEntities(FetchOptions.Builder.withLimit(5000)); + cache.put( + entityGroupKey, + new EntityGroupCount(getEntityGroupVersion(ds, tx, entityGroupKey), count)); + tx.rollback(); + writer.println(count + " entities"); + } + } + } + // [END entity_group_2] + + @Test + public void entityGroupCount_printsCount() throws Exception { + StringWriter responseWriter = new StringWriter(); + MemcacheService cache = MemcacheServiceFactory.getMemcacheService(); + Entity entity1 = new Entity("Simple"); + Key key1 = datastore.put(entity1); + Key entityGroupKey = Entities.createEntityGroupKey(key1); + + EntityGroupCount groupCount = new EntityGroupCount(0, 0); + groupCount.showEntityGroupCount( + datastore, cache, new PrintWriter(responseWriter), entityGroupKey); + + assertThat(responseWriter.toString()).contains(" entities"); + } +} diff --git a/appengine/datastore/src/test/java/com/example/appengine/MetadataKindsTest.java b/appengine/datastore/src/test/java/com/example/appengine/MetadataKindsTest.java new file mode 100644 index 00000000000..998d2ab33cf --- /dev/null +++ b/appengine/datastore/src/test/java/com/example/appengine/MetadataKindsTest.java @@ -0,0 +1,121 @@ +/* + * 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.example.appengine; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.appengine.api.datastore.DatastoreService; +import com.google.appengine.api.datastore.DatastoreServiceFactory; +import com.google.appengine.api.datastore.Entities; +import com.google.appengine.api.datastore.Entity; +import com.google.appengine.api.datastore.Query; +import com.google.appengine.api.datastore.Query.CompositeFilterOperator; +import com.google.appengine.api.datastore.Query.Filter; +import com.google.appengine.api.datastore.Query.FilterOperator; +import com.google.appengine.api.datastore.Query.FilterPredicate; +import com.google.appengine.tools.development.testing.LocalDatastoreServiceTestConfig; +import com.google.appengine.tools.development.testing.LocalServiceTestHelper; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.List; + +/** + * Unit tests to demonstrate App Engine Datastore kinds metadata. + */ +@RunWith(JUnit4.class) +public class MetadataKindsTest { + + private final LocalServiceTestHelper helper = + new LocalServiceTestHelper( + // Set no eventual consistency, that way queries return all results. + // https://cloud.google.com/appengine/docs/java/tools/localunittesting#Java_Writing_High_Replication_Datastore_tests + new LocalDatastoreServiceTestConfig() + .setDefaultHighRepJobPolicyUnappliedJobPercentage(0)); + + private StringWriter responseWriter; + private DatastoreService datastore; + + @Before + public void setUp() { + helper.setUp(); + datastore = DatastoreServiceFactory.getDatastoreService(); + responseWriter = new StringWriter(); + } + + @After + public void tearDown() { + helper.tearDown(); + } + + // [START kind_query_example] + void printLowercaseKinds(DatastoreService ds, PrintWriter writer) { + + // Start with unrestricted kind query + Query q = new Query(Entities.KIND_METADATA_KIND); + + List subFils = new ArrayList(); + + // Limit to lowercase initial letters + subFils.add( + new FilterPredicate( + Entity.KEY_RESERVED_PROPERTY, + FilterOperator.GREATER_THAN_OR_EQUAL, + Entities.createKindKey("a"))); + + String endChar = Character.toString((char) ('z' + 1)); // Character after 'z' + + subFils.add( + new FilterPredicate( + Entity.KEY_RESERVED_PROPERTY, + FilterOperator.LESS_THAN, + Entities.createKindKey(endChar))); + + q.setFilter(CompositeFilterOperator.and(subFils)); + + // Print heading + writer.println("Lowercase kinds:"); + + // Print query results + for (Entity e : ds.prepare(q).asIterable()) { + writer.println(" " + e.getKey().getName()); + } + } + // [END kind_query_example] + + @Test + public void printLowercaseKinds_printsKinds() throws Exception { + datastore.put(new Entity("alpha")); + datastore.put(new Entity("beta")); + datastore.put(new Entity("NotIncluded")); + datastore.put(new Entity("zed")); + + printLowercaseKinds(datastore, new PrintWriter(responseWriter)); + + String response = responseWriter.toString(); + assertThat(response).contains("alpha"); + assertThat(response).contains("beta"); + assertThat(response).contains("zed"); + assertThat(response).doesNotContain("NotIncluded"); + } +} diff --git a/appengine/datastore/src/test/java/com/example/appengine/MetadataNamespacesTest.java b/appengine/datastore/src/test/java/com/example/appengine/MetadataNamespacesTest.java new file mode 100644 index 00000000000..e31f7a14852 --- /dev/null +++ b/appengine/datastore/src/test/java/com/example/appengine/MetadataNamespacesTest.java @@ -0,0 +1,153 @@ +/* + * 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.example.appengine; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.appengine.api.NamespaceManager; +import com.google.appengine.api.datastore.DatastoreService; +import com.google.appengine.api.datastore.DatastoreServiceFactory; +import com.google.appengine.api.datastore.Entities; +import com.google.appengine.api.datastore.Entity; +import com.google.appengine.api.datastore.Query; +import com.google.appengine.api.datastore.Query.CompositeFilterOperator; +import com.google.appengine.api.datastore.Query.Filter; +import com.google.appengine.api.datastore.Query.FilterOperator; +import com.google.appengine.api.datastore.Query.FilterPredicate; +import com.google.appengine.tools.development.testing.LocalDatastoreServiceTestConfig; +import com.google.appengine.tools.development.testing.LocalServiceTestHelper; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.List; + +/** + * Unit tests to demonstrate App Engine Datastore namespaces metadata. + */ +@RunWith(JUnit4.class) +public class MetadataNamespacesTest { + + private final LocalServiceTestHelper helper = + new LocalServiceTestHelper( + // Set no eventual consistency, that way queries return all results. + // https://cloud.google.com/appengine/docs/java/tools/localunittesting#Java_Writing_High_Replication_Datastore_tests + new LocalDatastoreServiceTestConfig() + .setDefaultHighRepJobPolicyUnappliedJobPercentage(0)); + + private StringWriter responseWriter; + private DatastoreService datastore; + + @Before + public void setUp() { + helper.setUp(); + datastore = DatastoreServiceFactory.getDatastoreService(); + responseWriter = new StringWriter(); + } + + @After + public void tearDown() { + helper.tearDown(); + } + + // [START queries_intro_example] + void printAllNamespaces(DatastoreService ds, PrintWriter writer) { + Query q = new Query(Entities.NAMESPACE_METADATA_KIND); + + for (Entity e : ds.prepare(q).asIterable()) { + // A nonzero numeric id denotes the default namespace; + // see Namespace Queries, below + if (e.getKey().getId() != 0) { + writer.println(""); + } else { + writer.println(e.getKey().getName()); + } + } + } + // [END queries_intro_example] + + @Test + public void printAllNamespaces_printsNamespaces() throws Exception { + datastore.put(new Entity("Simple")); + NamespaceManager.set("another-namespace"); + datastore.put(new Entity("Simple")); + + printAllNamespaces(datastore, new PrintWriter(responseWriter)); + + String response = responseWriter.toString(); + assertThat(response).contains(""); + assertThat(response).contains("another-namespace"); + } + + // [START namespace_query_example] + List getNamespaces(DatastoreService ds, String start, String end) { + + // Start with unrestricted namespace query + Query q = new Query(Entities.NAMESPACE_METADATA_KIND); + List subFilters = new ArrayList(); + // Limit to specified range, if any + if (start != null) { + subFilters.add( + new FilterPredicate( + Entity.KEY_RESERVED_PROPERTY, + FilterOperator.GREATER_THAN_OR_EQUAL, + Entities.createNamespaceKey(start))); + } + if (end != null) { + subFilters.add( + new FilterPredicate( + Entity.KEY_RESERVED_PROPERTY, + FilterOperator.LESS_THAN_OR_EQUAL, + Entities.createNamespaceKey(end))); + } + + q.setFilter(CompositeFilterOperator.and(subFilters)); + + // Initialize result list + List results = new ArrayList(); + + // Build list of query results + for (Entity e : ds.prepare(q).asIterable()) { + results.add(Entities.getNamespaceFromNamespaceKey(e.getKey())); + } + + // Return result list + return results; + } + // [END namespace_query_example] + + @Test + public void getNamespaces_returnsNamespaces() throws Exception { + NamespaceManager.set("alpha"); + datastore.put(new Entity("Simple")); + NamespaceManager.set("bravo"); + datastore.put(new Entity("Simple")); + NamespaceManager.set("charlie"); + datastore.put(new Entity("Simple")); + NamespaceManager.set("zed"); + datastore.put(new Entity("Simple")); + + List results = getNamespaces(datastore, "bravo", "echo"); + + assertThat(results).containsExactly("bravo", "charlie"); + } +} diff --git a/appengine/datastore/src/test/java/com/example/appengine/MetadataPropertiesTest.java b/appengine/datastore/src/test/java/com/example/appengine/MetadataPropertiesTest.java new file mode 100644 index 00000000000..88ba79008ab --- /dev/null +++ b/appengine/datastore/src/test/java/com/example/appengine/MetadataPropertiesTest.java @@ -0,0 +1,234 @@ +/* + * 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.example.appengine; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.appengine.api.datastore.DatastoreService; +import com.google.appengine.api.datastore.DatastoreServiceFactory; +import com.google.appengine.api.datastore.Entities; +import com.google.appengine.api.datastore.Entity; +import com.google.appengine.api.datastore.Query; +import com.google.appengine.api.datastore.Query.CompositeFilterOperator; +import com.google.appengine.api.datastore.Query.FilterPredicate; +import com.google.appengine.api.datastore.Query.SortDirection; +import com.google.appengine.tools.development.testing.LocalDatastoreServiceTestConfig; +import com.google.appengine.tools.development.testing.LocalServiceTestHelper; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Date; +import java.util.List; + +/** + * Unit tests to demonstrate App Engine Datastore properties metadata. + */ +@RunWith(JUnit4.class) +public class MetadataPropertiesTest { + + private final LocalServiceTestHelper helper = + new LocalServiceTestHelper( + // Set no eventual consistency, that way queries return all results. + // https://cloud.google.com/appengine/docs/java/tools/localunittesting#Java_Writing_High_Replication_Datastore_tests + new LocalDatastoreServiceTestConfig() + .setDefaultHighRepJobPolicyUnappliedJobPercentage(0)); + + private StringWriter responseWriter; + private DatastoreService datastore; + + @Before + public void setUp() { + helper.setUp(); + datastore = DatastoreServiceFactory.getDatastoreService(); + responseWriter = new StringWriter(); + } + + @After + public void tearDown() { + helper.tearDown(); + } + + // [START property_query_example] + void printProperties(DatastoreService ds, PrintWriter writer) { + + // Create unrestricted keys-only property query + Query q = new Query(Entities.PROPERTY_METADATA_KIND).setKeysOnly(); + + // Print query results + for (Entity e : ds.prepare(q).asIterable()) { + writer.println(e.getKey().getParent().getName() + ": " + e.getKey().getName()); + } + } + // [END property_query_example] + + @Test + public void printProperties_printsProperties() throws Exception { + Entity a = new Entity("Widget"); + a.setProperty("combobulators", 2); + a.setProperty("oscillatorState", "harmonzing"); + Entity b = new Entity("Ship"); + b.setProperty("sails", 2); + b.setProperty("captain", "Blackbeard"); + Entity c = new Entity("Ship"); + c.setProperty("captain", "Redbeard"); + c.setProperty("motor", "outboard"); + datastore.put(Arrays.asList(a, b, c)); + + printProperties(datastore, new PrintWriter(responseWriter)); + + String response = responseWriter.toString(); + assertThat(response).contains("Widget: combobulators"); + assertThat(response).contains("Widget: oscillatorState"); + assertThat(response).contains("Ship: sails"); + assertThat(response).contains("Ship: captain"); + assertThat(response).contains("Ship: motor"); + } + + // [START property_filtering_example] + void printPropertyRange(DatastoreService ds, PrintWriter writer) { + + // Start with unrestricted keys-only property query + Query q = new Query(Entities.PROPERTY_METADATA_KIND).setKeysOnly(); + + // Limit range + q.setFilter( + CompositeFilterOperator.and( + new FilterPredicate( + Entity.KEY_RESERVED_PROPERTY, + Query.FilterOperator.GREATER_THAN_OR_EQUAL, + Entities.createPropertyKey("Employee", "salary")), + new FilterPredicate( + Entity.KEY_RESERVED_PROPERTY, + Query.FilterOperator.LESS_THAN_OR_EQUAL, + Entities.createPropertyKey("Manager", "salary")))); + q.addSort(Entity.KEY_RESERVED_PROPERTY, SortDirection.ASCENDING); + + // Print query results + for (Entity e : ds.prepare(q).asIterable()) { + writer.println(e.getKey().getParent().getName() + ": " + e.getKey().getName()); + } + } + // [END property_filtering_example] + + @Test + public void printPropertyRange_printsProperties() throws Exception { + Entity account = new Entity("Account"); + account.setProperty("balance", "10.30"); + account.setProperty("company", "General Company"); + Entity employee = new Entity("Employee"); + employee.setProperty("name", "John Doe"); + employee.setProperty("ssn", "987-65-4321"); + Entity invoice = new Entity("Invoice"); + invoice.setProperty("date", new Date()); + invoice.setProperty("amount", "99.98"); + Entity manager = new Entity("Manager"); + manager.setProperty("name", "Jane Doe"); + manager.setProperty("title", "Technical Director"); + Entity product = new Entity("Product"); + product.setProperty("description", "Widget to re-ionize an oscillator"); + product.setProperty("price", "19.97"); + datastore.put(Arrays.asList(account, employee, invoice, manager, product)); + + printPropertyRange(datastore, new PrintWriter(responseWriter)); + + String response = responseWriter.toString(); + assertThat(response) + .isEqualTo("Employee: ssn\nInvoice: amount\nInvoice: date\nManager: name\n"); + } + + // [START property_ancestor_query_example] + List propertiesOfKind(DatastoreService ds, String kind) { + + // Start with unrestricted keys-only property query + Query q = new Query(Entities.PROPERTY_METADATA_KIND).setKeysOnly(); + + // Limit to specified kind + q.setAncestor(Entities.createKindKey(kind)); + + // Initialize result list + ArrayList results = new ArrayList(); + + //Build list of query results + for (Entity e : ds.prepare(q).asIterable()) { + results.add(e.getKey().getName()); + } + + // Return result list + return results; + } + // [END property_ancestor_query_example] + + @Test + public void propertiesOfKind_returnsProperties() throws Exception { + Entity a = new Entity("Alpha"); + a.setProperty("beta", 12); + a.setProperty("charlie", "misc."); + Entity b = new Entity("Alpha"); + b.setProperty("charlie", "assorted"); + b.setProperty("delta", new Date()); + Entity c = new Entity("Charlie"); + c.setProperty("charlie", "some"); + c.setProperty("echo", new Date()); + datastore.put(Arrays.asList(a, b, c)); + + List properties = propertiesOfKind(datastore, "Alpha"); + + assertThat(properties).containsExactly("beta", "charlie", "delta"); + } + + // [START property_representation_query_example] + Collection representationsOfProperty(DatastoreService ds, String kind, String property) { + + // Start with unrestricted non-keys-only property query + Query q = new Query(Entities.PROPERTY_METADATA_KIND); + + // Limit to specified kind and property + q.setFilter( + new FilterPredicate( + "__key__", Query.FilterOperator.EQUAL, Entities.createPropertyKey(kind, property))); + + // Get query result + Entity propInfo = ds.prepare(q).asSingleEntity(); + + // Return collection of property representations + return (Collection) propInfo.getProperty("property_representation"); + } + // [END property_representation_query_example] + + @Test + public void representationsOfProperty_returnsRepresentations() throws Exception { + Entity a = new Entity("Alpha"); + a.setProperty("beta", 12); + Entity b = new Entity("Alpha"); + b.setProperty("beta", true); + Entity c = new Entity("Alpha"); + c.setProperty("beta", new Date()); + datastore.put(Arrays.asList(a, b, c)); + + Collection results = representationsOfProperty(datastore, "Alpha", "beta"); + + assertThat(results).containsExactly("INT64", "BOOLEAN"); + } +}