/*
* Copyright 2016 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.appengine;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.fail;
import com.google.appengine.api.datastore.DatastoreService;
import com.google.appengine.api.datastore.DatastoreServiceFactory;
import com.google.appengine.api.datastore.Entity;
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.PreparedQuery.TooManyResultsException;
import com.google.appengine.api.datastore.Query;
import com.google.appengine.api.datastore.Query.CompositeFilter;
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.api.datastore.Query.SortDirection;
import com.google.appengine.tools.development.testing.LocalDatastoreServiceTestConfig;
import com.google.appengine.tools.development.testing.LocalServiceTestHelper;
import com.google.common.collect.ImmutableList;
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.Arrays;
import java.util.List;
/**
* Unit tests to demonstrate App Engine Datastore queries.
*/
@RunWith(JUnit4.class)
public class QueriesTest {
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 DatastoreService datastore;
@Before
public void setUp() {
helper.setUp();
datastore = DatastoreServiceFactory.getDatastoreService();
}
@After
public void tearDown() {
helper.tearDown();
}
@Test
public void propertyFilterExample_returnsMatchingEntities() throws Exception {
// Arrange
Entity p1 = new Entity("Person");
p1.setProperty("height", 120);
Entity p2 = new Entity("Person");
p2.setProperty("height", 180);
Entity p3 = new Entity("Person");
p3.setProperty("height", 160);
datastore.put(ImmutableList.<Entity>of(p1, p2, p3));
// Act
long minHeight = 160;
// [START property_filter_example]
Filter propertyFilter =
new FilterPredicate("height", FilterOperator.GREATER_THAN_OR_EQUAL, minHeight);
Query q = new Query("Person").setFilter(propertyFilter);
// [END property_filter_example]
// Assert
List<Entity> results =
datastore.prepare(q.setKeysOnly()).asList(FetchOptions.Builder.withDefaults());
assertThat(results).named("query results").containsExactly(p2, p3);
}
@Test
public void keyFilterExample_returnsMatchingEntities() throws Exception {
// Arrange
Entity a = new Entity("Person", "a");
Entity b = new Entity("Person", "b");
Entity c = new Entity("Person", "c");
Entity aa = new Entity("Person", "aa", b.getKey());
Entity bb = new Entity("Person", "bb", b.getKey());
Entity aaa = new Entity("Person", "aaa", bb.getKey());
Entity bbb = new Entity("Person", "bbb", bb.getKey());
datastore.put(ImmutableList.<Entity>of(a, b, c, aa, bb, aaa, bbb));
// Act
Key lastSeenKey = bb.getKey();
// [START key_filter_example]
Filter keyFilter =
new FilterPredicate(Entity.KEY_RESERVED_PROPERTY, FilterOperator.GREATER_THAN, lastSeenKey);
Query q = new Query("Person").setFilter(keyFilter);
// [END key_filter_example]
// Assert
List<Entity> results =
datastore.prepare(q.setKeysOnly()).asList(FetchOptions.Builder.withDefaults());
assertThat(results)
.named("query results")
.containsExactly(
aaa, // Ancestor path "b/bb/aaa" is greater than "b/bb".
bbb, // Ancestor path "b/bb/bbb" is greater than "b/bb".
c); // Key name identifier "c" is greater than b.
}
@Test
public void keyFilterExample_kindless_returnsMatchingEntities() throws Exception {
// Arrange
Entity a = new Entity("Child", "a");
Entity b = new Entity("Child", "b");
Entity c = new Entity("Child", "c");
Entity aa = new Entity("Child", "aa", b.getKey());
Entity bb = new Entity("Child", "bb", b.getKey());
Entity aaa = new Entity("Child", "aaa", bb.getKey());
Entity bbb = new Entity("Child", "bbb", bb.getKey());
Entity adult = new Entity("Adult", "a");
Entity zooAnimal = new Entity("ZooAnimal", "a");
datastore.put(ImmutableList.<Entity>of(a, b, c, aa, bb, aaa, bbb, adult, zooAnimal));
// Act
Key lastSeenKey = bb.getKey();
// [START kindless_query_example]
Filter keyFilter =
new FilterPredicate(Entity.KEY_RESERVED_PROPERTY, FilterOperator.GREATER_THAN, lastSeenKey);
Query q = new Query().setFilter(keyFilter);
// [END kindless_query_example]
// Assert
List<Entity> results =
datastore.prepare(q.setKeysOnly()).asList(FetchOptions.Builder.withDefaults());
assertThat(results)
.named("query results")
.containsExactly(
aaa, // Ancestor path "b/bb/aaa" is greater than "b/bb".
bbb, // Ancestor path "b/bb/bbb" is greater than "b/bb".
zooAnimal, // Kind "ZooAnimal" is greater than "Child"
c); // Key name identifier "c" is greater than b.
}
@Test
public void ancestorFilterExample_returnsMatchingEntities() throws Exception {
Entity a = new Entity("Person", "a");
Entity b = new Entity("Person", "b");
Entity aa = new Entity("Person", "aa", a.getKey());
Entity ab = new Entity("Person", "ab", a.getKey());
Entity bb = new Entity("Person", "bb", b.getKey());
datastore.put(ImmutableList.<Entity>of(a, b, aa, ab, bb));
Key ancestorKey = a.getKey();
// [START ancestor_filter_example]
Query q = new Query("Person").setAncestor(ancestorKey);
// [END ancestor_filter_example]
// Assert
List<Entity> results =
datastore.prepare(q.setKeysOnly()).asList(FetchOptions.Builder.withDefaults());
assertThat(results).named("query results").containsExactly(a, aa, ab);
}
@Test
public void ancestorQueryExample_returnsMatchingEntities() throws Exception {
// [START ancestor_query_example]
DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
Entity tom = new Entity("Person", "Tom");
Key tomKey = tom.getKey();
datastore.put(tom);
Entity weddingPhoto = new Entity("Photo", tomKey);
weddingPhoto.setProperty("imageURL", "http://domain.com/some/path/to/wedding_photo.jpg");
Entity babyPhoto = new Entity("Photo", tomKey);
babyPhoto.setProperty("imageURL", "http://domain.com/some/path/to/baby_photo.jpg");
Entity dancePhoto = new Entity("Photo", tomKey);
dancePhoto.setProperty("imageURL", "http://domain.com/some/path/to/dance_photo.jpg");
Entity campingPhoto = new Entity("Photo");
campingPhoto.setProperty("imageURL", "http://domain.com/some/path/to/camping_photo.jpg");
List<Entity> photoList = Arrays.asList(weddingPhoto, babyPhoto, dancePhoto, campingPhoto);
datastore.put(photoList);
Query photoQuery = new Query("Photo").setAncestor(tomKey);
// This returns weddingPhoto, babyPhoto, and dancePhoto,
// but not campingPhoto, because tom is not an ancestor
List<Entity> results =
datastore.prepare(photoQuery).asList(FetchOptions.Builder.withDefaults());
// [END ancestor_query_example]
assertThat(results).named("query results").containsExactly(weddingPhoto, babyPhoto, dancePhoto);
}
@Test
public void ancestorQueryExample_kindlessKeyFilter_returnsMatchingEntities() throws Exception {
// Arrange
Entity a = new Entity("Grandparent", "a");
Entity b = new Entity("Grandparent", "b");
Entity c = new Entity("Grandparent", "c");
Entity aa = new Entity("Parent", "aa", a.getKey());
Entity ba = new Entity("Parent", "ba", b.getKey());
Entity bb = new Entity("Parent", "bb", b.getKey());
Entity bc = new Entity("Parent", "bc", b.getKey());
Entity cc = new Entity("Parent", "cc", c.getKey());
Entity aaa = new Entity("Child", "aaa", aa.getKey());
Entity bbb = new Entity("Child", "bbb", bb.getKey());
datastore.put(ImmutableList.<Entity>of(a, b, c, aa, ba, bb, bc, cc, aaa, bbb));
// Act
Key ancestorKey = b.getKey();
Key lastSeenKey = bb.getKey();
// [START kindless_ancestor_key_query_example]
Filter keyFilter =
new FilterPredicate(Entity.KEY_RESERVED_PROPERTY, FilterOperator.GREATER_THAN, lastSeenKey);
Query q = new Query().setAncestor(ancestorKey).setFilter(keyFilter);
// [END kindless_ancestor_key_query_example]
// Assert
List<Entity> results =
datastore.prepare(q.setKeysOnly()).asList(FetchOptions.Builder.withDefaults());
assertThat(results).named("query results").containsExactly(bc, bbb);
}
@Test
public void ancestorQueryExample_kindlessKeyFilterFull_returnsMatchingEntities()
throws Exception {
// [START kindless_ancestor_query_example]
DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
Entity tom = new Entity("Person", "Tom");
Key tomKey = tom.getKey();
datastore.put(tom);
Entity weddingPhoto = new Entity("Photo", tomKey);
weddingPhoto.setProperty("imageURL", "http://domain.com/some/path/to/wedding_photo.jpg");
Entity weddingVideo = new Entity("Video", tomKey);
weddingVideo.setProperty("videoURL", "http://domain.com/some/path/to/wedding_video.avi");
List<Entity> mediaList = Arrays.asList(weddingPhoto, weddingVideo);
datastore.put(mediaList);
// By default, ancestor queries include the specified ancestor itself.
// The following filter excludes the ancestor from the query results.
Filter keyFilter =
new FilterPredicate(Entity.KEY_RESERVED_PROPERTY, FilterOperator.GREATER_THAN, tomKey);
Query mediaQuery = new Query().setAncestor(tomKey).setFilter(keyFilter);
// Returns both weddingPhoto and weddingVideo,
// even though they are of different entity kinds
List<Entity> results =
datastore.prepare(mediaQuery).asList(FetchOptions.Builder.withDefaults());
// [END kindless_ancestor_query_example]
assertThat(results).named("query result keys").containsExactly(weddingPhoto, weddingVideo);
}
@Test
public void keysOnlyExample_returnsMatchingEntities() throws Exception {
// Arrange
Entity a = new Entity("Person", "a");
Entity b = new Entity("Building", "b");
Entity c = new Entity("Person", "c");
datastore.put(ImmutableList.<Entity>of(a, b, c));
// [START keys_only_example]
Query q = new Query("Person").setKeysOnly();
// [END keys_only_example]
// Assert
List<Entity> results = datastore.prepare(q).asList(FetchOptions.Builder.withDefaults());
assertThat(results).named("query results").containsExactly(a, c);
}
@Test
public void sortOrderExample_returnsSortedEntities() throws Exception {
// Arrange
Entity a = new Entity("Person", "a");
a.setProperty("lastName", "Alpha");
a.setProperty("height", 100);
Entity b = new Entity("Person", "b");
b.setProperty("lastName", "Bravo");
b.setProperty("height", 200);
Entity c = new Entity("Person", "c");
c.setProperty("lastName", "Charlie");
c.setProperty("height", 300);
datastore.put(ImmutableList.<Entity>of(a, b, c));
// Act
// [START sort_order_example]
// Order alphabetically by last name:
Query q1 = new Query("Person").addSort("lastName", SortDirection.ASCENDING);
// Order by height, tallest to shortest:
Query q2 = new Query("Person").addSort("height", SortDirection.DESCENDING);
// [END sort_order_example]
// Assert
List<Entity> lastNameResults =
datastore.prepare(q1.setKeysOnly()).asList(FetchOptions.Builder.withDefaults());
assertThat(lastNameResults).named("last name query results").containsExactly(a, b, c).inOrder();
List<Entity> heightResults =
datastore.prepare(q2.setKeysOnly()).asList(FetchOptions.Builder.withDefaults());
assertThat(heightResults).named("height query results").containsExactly(c, b, a).inOrder();
}
@Test
public void sortOrderExample_multipleSortOrders_returnsSortedEntities() throws Exception {
// Arrange
Entity a = new Entity("Person", "a");
a.setProperty("lastName", "Alpha");
a.setProperty("height", 100);
Entity b1 = new Entity("Person", "b1");
b1.setProperty("lastName", "Bravo");
b1.setProperty("height", 150);
Entity b2 = new Entity("Person", "b2");
b2.setProperty("lastName", "Bravo");
b2.setProperty("height", 200);
Entity c = new Entity("Person", "c");
c.setProperty("lastName", "Charlie");
c.setProperty("height", 300);
datastore.put(ImmutableList.<Entity>of(a, b1, b2, c));
// Act
// [START multiple_sort_orders_example]
Query q =
new Query("Person")
.addSort("lastName", SortDirection.ASCENDING)
.addSort("height", SortDirection.DESCENDING);
// [END multiple_sort_orders_example]
// Assert
List<Entity> results =
datastore.prepare(q.setKeysOnly()).asList(FetchOptions.Builder.withDefaults());
assertThat(results).named("query results").containsExactly(a, b2, b1, c).inOrder();
}
@Test
public void queryInterface_multipleFilters_printsMatchedEntities() throws Exception {
// Arrange
Entity a = new Entity("Person", "a");
a.setProperty("firstName", "Alph");
a.setProperty("lastName", "Alpha");
a.setProperty("height", 60);
Entity b = new Entity("Person", "b");
b.setProperty("firstName", "Bee");
b.setProperty("lastName", "Bravo");
b.setProperty("height", 70);
Entity c = new Entity("Person", "c");
c.setProperty("firstName", "Charles");
c.setProperty("lastName", "Charlie");
c.setProperty("height", 100);
datastore.put(ImmutableList.<Entity>of(a, b, c));
StringWriter buf = new StringWriter();
PrintWriter out = new PrintWriter(buf);
long minHeight = 60;
long maxHeight = 72;
// Act
// [START interface_1]
DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
Filter heightMinFilter =
new FilterPredicate("height", FilterOperator.GREATER_THAN_OR_EQUAL, minHeight);
Filter heightMaxFilter =
new FilterPredicate("height", FilterOperator.LESS_THAN_OR_EQUAL, maxHeight);
// Use CompositeFilter to combine multiple filters
CompositeFilter heightRangeFilter =
CompositeFilterOperator.and(heightMinFilter, heightMaxFilter);
// Use class Query to assemble a query
Query q = new Query("Person").setFilter(heightRangeFilter);
// Use PreparedQuery interface to retrieve results
PreparedQuery pq = datastore.prepare(q);
for (Entity result : pq.asIterable()) {
String firstName = (String) result.getProperty("firstName");
String lastName = (String) result.getProperty("lastName");
Long height = (Long) result.getProperty("height");
out.println(firstName + " " + lastName + ", " + height + " inches tall");
}
// [END interface_1]
// Assert
assertThat(buf.toString()).contains("Alph Alpha, 60 inches tall");
assertThat(buf.toString()).contains("Bee Bravo, 70 inches tall");
assertThat(buf.toString()).doesNotContain("Charlie");
}
@Test
public void queryInterface_singleFilter_returnsMatchedEntities() throws Exception {
// Arrange
Entity a = new Entity("Person", "a");
a.setProperty("height", 100);
Entity b = new Entity("Person", "b");
b.setProperty("height", 150);
Entity c = new Entity("Person", "c");
c.setProperty("height", 300);
datastore.put(ImmutableList.<Entity>of(a, b, c));
// Act
long minHeight = 150;
// [START interface_2]
Filter heightMinFilter =
new FilterPredicate("height", FilterOperator.GREATER_THAN_OR_EQUAL, minHeight);
Query q = new Query("Person").setFilter(heightMinFilter);
// [END interface_2]
// Assert
List<Entity> results =
datastore.prepare(q.setKeysOnly()).asList(FetchOptions.Builder.withDefaults());
assertThat(results).named("query results").containsExactly(b, c);
}
@Test
public void queryInterface_orFilter_printsMatchedEntities() throws Exception {
// Arrange
Entity a = new Entity("Person", "a");
a.setProperty("height", 100);
Entity b = new Entity("Person", "b");
b.setProperty("height", 150);
Entity c = new Entity("Person", "c");
c.setProperty("height", 200);
datastore.put(ImmutableList.<Entity>of(a, b, c));
StringWriter buf = new StringWriter();
PrintWriter out = new PrintWriter(buf);
long minHeight = 125;
long maxHeight = 175;
// Act
// [START interface_3]
Filter tooShortFilter = new FilterPredicate("height", FilterOperator.LESS_THAN, minHeight);
Filter tooTallFilter = new FilterPredicate("height", FilterOperator.GREATER_THAN, maxHeight);
Filter heightOutOfRangeFilter = CompositeFilterOperator.or(tooShortFilter, tooTallFilter);
Query q = new Query("Person").setFilter(heightOutOfRangeFilter);
// [END interface_3]
// Assert
List<Entity> results =
datastore.prepare(q.setKeysOnly()).asList(FetchOptions.Builder.withDefaults());
assertThat(results).named("query results").containsExactly(a, c);
}
@Test
public void queryRestrictions_compositeFilter_returnsMatchedEntities() throws Exception {
// Arrange
Entity a = new Entity("Person", "a");
a.setProperty("birthYear", 1930);
Entity b = new Entity("Person", "b");
b.setProperty("birthYear", 1960);
Entity c = new Entity("Person", "c");
c.setProperty("birthYear", 1990);
datastore.put(ImmutableList.<Entity>of(a, b, c));
// Act
long minBirthYear = 1940;
long maxBirthYear = 1980;
// [START inequality_filters_one_property_valid_example_1]
Filter birthYearMinFilter =
new FilterPredicate("birthYear", FilterOperator.GREATER_THAN_OR_EQUAL, minBirthYear);
Filter birthYearMaxFilter =
new FilterPredicate("birthYear", FilterOperator.LESS_THAN_OR_EQUAL, maxBirthYear);
Filter birthYearRangeFilter =
CompositeFilterOperator.and(birthYearMinFilter, birthYearMaxFilter);
Query q = new Query("Person").setFilter(birthYearRangeFilter);
// [END inequality_filters_one_property_valid_example_1]
// Assert
List<Entity> results =
datastore.prepare(q.setKeysOnly()).asList(FetchOptions.Builder.withDefaults());
assertThat(results).named("query results").containsExactly(b);
}
@Test
public void queryRestrictions_compositeFilter_isInvalid() throws Exception {
long minBirthYear = 1940;
long maxHeight = 200;
// [START inequality_filters_one_property_invalid_example]
Filter birthYearMinFilter =
new FilterPredicate("birthYear", FilterOperator.GREATER_THAN_OR_EQUAL, minBirthYear);
Filter heightMaxFilter =
new FilterPredicate("height", FilterOperator.LESS_THAN_OR_EQUAL, maxHeight);
Filter invalidFilter = CompositeFilterOperator.and(birthYearMinFilter, heightMaxFilter);
Query q = new Query("Person").setFilter(invalidFilter);
// [END inequality_filters_one_property_invalid_example]
// Note: The local devserver behavior is different than the production
// version of Cloud Datastore, so there aren't any assertions we can make
// in this test. The query appears to work with the local test runner,
// but will fail in production.
}
@Test
public void queryRestrictions_compositeEqualFilter_returnsMatchedEntities() throws Exception {
// Arrange
Entity a = new Entity("Person", "a");
a.setProperty("birthYear", 1930);
a.setProperty("city", "Somewhere");
a.setProperty("lastName", "Someone");
Entity b = new Entity("Person", "b");
b.setProperty("birthYear", 1960);
b.setProperty("city", "Somewhere");
b.setProperty("lastName", "Someone");
Entity c = new Entity("Person", "c");
c.setProperty("birthYear", 1990);
c.setProperty("city", "Somewhere");
c.setProperty("lastName", "Someone");
Entity d = new Entity("Person", "d");
d.setProperty("birthYear", 1960);
d.setProperty("city", "Nowhere");
d.setProperty("lastName", "Someone");
Entity e = new Entity("Person", "e");
e.setProperty("birthYear", 1960);
e.setProperty("city", "Somewhere");
e.setProperty("lastName", "Noone");
datastore.put(ImmutableList.<Entity>of(a, b, c, d, e));
long minBirthYear = 1940;
long maxBirthYear = 1980;
String targetCity = "Somewhere";
String targetLastName = "Someone";
// [START inequality_filters_one_property_valid_example_2]
Filter lastNameFilter = new FilterPredicate("lastName", FilterOperator.EQUAL, targetLastName);
Filter cityFilter = new FilterPredicate("city", FilterOperator.EQUAL, targetCity);
Filter birthYearMinFilter =
new FilterPredicate("birthYear", FilterOperator.GREATER_THAN_OR_EQUAL, minBirthYear);
Filter birthYearMaxFilter =
new FilterPredicate("birthYear", FilterOperator.LESS_THAN_OR_EQUAL, maxBirthYear);
Filter validFilter =
CompositeFilterOperator.and(
lastNameFilter, cityFilter, birthYearMinFilter, birthYearMaxFilter);
Query q = new Query("Person").setFilter(validFilter);
// [END inequality_filters_one_property_valid_example_2]
// Assert
List<Entity> results =
datastore.prepare(q.setKeysOnly()).asList(FetchOptions.Builder.withDefaults());
assertThat(results).named("query results").containsExactly(b);
}
@Test
public void queryRestrictions_inequalitySortedFirst_returnsMatchedEntities() throws Exception {
// Arrange
Entity a = new Entity("Person", "a");
a.setProperty("birthYear", 1930);
a.setProperty("lastName", "Someone");
Entity b = new Entity("Person", "b");
b.setProperty("birthYear", 1990);
b.setProperty("lastName", "Bravo");
Entity c = new Entity("Person", "c");
c.setProperty("birthYear", 1960);
c.setProperty("lastName", "Charlie");
Entity d = new Entity("Person", "d");
d.setProperty("birthYear", 1960);
d.setProperty("lastName", "Delta");
datastore.put(ImmutableList.<Entity>of(a, b, c, d));
long minBirthYear = 1940;
// [START inequality_filters_sort_orders_valid_example]
Filter birthYearMinFilter =
new FilterPredicate("birthYear", FilterOperator.GREATER_THAN_OR_EQUAL, minBirthYear);
Query q =
new Query("Person")
.setFilter(birthYearMinFilter)
.addSort("birthYear", SortDirection.ASCENDING)
.addSort("lastName", SortDirection.ASCENDING);
// [END inequality_filters_sort_orders_valid_example]
// Assert
List<Entity> results =
datastore.prepare(q.setKeysOnly()).asList(FetchOptions.Builder.withDefaults());
assertThat(results).named("query results").containsExactly(c, d, b).inOrder();
}
@Test
public void queryRestrictions_missingSortOnInequality_isInvalid() throws Exception {
long minBirthYear = 1940;
// [START inequality_filters_sort_orders_invalid_example_1]
Filter birthYearMinFilter =
new FilterPredicate("birthYear", FilterOperator.GREATER_THAN_OR_EQUAL, minBirthYear);
// Not valid. Missing sort on birthYear.
Query q =
new Query("Person")
.setFilter(birthYearMinFilter)
.addSort("lastName", SortDirection.ASCENDING);
// [END inequality_filters_sort_orders_invalid_example_1]
// Note: The local devserver behavior is different than the production
// version of Cloud Datastore, so there aren't any assertions we can make
// in this test. The query appears to work with the local test runner,
// but will fail in production.
}
@Test
public void queryRestrictions_sortWrongOrderOnInequality_isInvalid() throws Exception {
long minBirthYear = 1940;
// [START inequality_filters_sort_orders_invalid_example_2]
Filter birthYearMinFilter =
new FilterPredicate("birthYear", FilterOperator.GREATER_THAN_OR_EQUAL, minBirthYear);
// Not valid. Sort on birthYear needs to be first.
Query q =
new Query("Person")
.setFilter(birthYearMinFilter)
.addSort("lastName", SortDirection.ASCENDING)
.addSort("birthYear", SortDirection.ASCENDING);
// [END inequality_filters_sort_orders_invalid_example_2]
// Note: The local devserver behavior is different than the production
// version of Cloud Datastore, so there aren't any assertions we can make
// in this test. The query appears to work with the local test runner,
// but will fail in production.
}
@Test
public void queryRestrictions_surprisingMultipleValuesAllMustMatch_returnsNoEntities()
throws Exception {
Entity a = new Entity("Widget", "a");
List<Long> xs = Arrays.asList(1L, 2L);
a.setProperty("x", xs);
datastore.put(a);
// [START surprising_behavior_example_1]
Query q =
new Query("Widget")
.setFilter(
CompositeFilterOperator.and(
new FilterPredicate("x", FilterOperator.GREATER_THAN, 1),
new FilterPredicate("x", FilterOperator.LESS_THAN, 2)));
// [END surprising_behavior_example_1]
// Entity "a" will not match because no individual value matches all filters.
// See the documentation for more details:
// https://cloud.google.com/appengine/docs/java/datastore/query-restrictions#properties_with_multiple_values_can_behave_in_surprising_ways
List<Entity> results =
datastore.prepare(q.setKeysOnly()).asList(FetchOptions.Builder.withDefaults());
assertThat(results).named("query results").isEmpty();
}
@Test
public void queryRestrictions_surprisingMultipleValuesEquals_returnsMatchedEntities()
throws Exception {
Entity a = new Entity("Widget", "a");
a.setProperty("x", ImmutableList.<Long>of(1L, 2L));
Entity b = new Entity("Widget", "b");
b.setProperty("x", ImmutableList.<Long>of(1L, 3L));
Entity c = new Entity("Widget", "c");
c.setProperty("x", ImmutableList.<Long>of(-6L, 2L));
Entity d = new Entity("Widget", "d");
d.setProperty("x", ImmutableList.<Long>of(-6L, 4L));
Entity e = new Entity("Widget", "e");
e.setProperty("x", ImmutableList.<Long>of(1L, 2L, 3L));
datastore.put(ImmutableList.<Entity>of(a, b, c, d, e));
// [START surprising_behavior_example_2]
Query q =
new Query("Widget")
.setFilter(
CompositeFilterOperator.and(
new FilterPredicate("x", FilterOperator.EQUAL, 1),
new FilterPredicate("x", FilterOperator.EQUAL, 2)));
// [END surprising_behavior_example_2]
// Only "a" and "e" have both 1 and 2 in the "x" array-valued property.
// See the documentation for more details:
// https://cloud.google.com/appengine/docs/java/datastore/query-restrictions#properties_with_multiple_values_can_behave_in_surprising_ways
List<Entity> results =
datastore.prepare(q.setKeysOnly()).asList(FetchOptions.Builder.withDefaults());
assertThat(results).named("query results").containsExactly(a, e);
}
@Test
public void queryRestrictions_surprisingMultipleValuesNotEquals_returnsMatchedEntities()
throws Exception {
Entity a = new Entity("Widget", "a");
a.setProperty("x", ImmutableList.<Long>of(1L, 2L));
Entity b = new Entity("Widget", "b");
b.setProperty("x", ImmutableList.<Long>of(1L, 3L));
Entity c = new Entity("Widget", "c");
c.setProperty("x", ImmutableList.<Long>of(-6L, 2L));
Entity d = new Entity("Widget", "d");
d.setProperty("x", ImmutableList.<Long>of(-6L, 4L));
Entity e = new Entity("Widget", "e");
e.setProperty("x", ImmutableList.<Long>of(1L));
datastore.put(ImmutableList.<Entity>of(a, b, c, d, e));
// [START surprising_behavior_example_3]
Query q = new Query("Widget").setFilter(new FilterPredicate("x", FilterOperator.NOT_EQUAL, 1));
// [END surprising_behavior_example_3]
// The query matches any entity that has a some value other than 1. Only
// entity "e" is not matched. See the documentation for more details:
// https://cloud.google.com/appengine/docs/java/datastore/query-restrictions#properties_with_multiple_values_can_behave_in_surprising_ways
List<Entity> results =
datastore.prepare(q.setKeysOnly()).asList(FetchOptions.Builder.withDefaults());
assertThat(results).named("query results").containsExactly(a, b, c, d);
}
@Test
public void queryRestrictions_surprisingMultipleValuesTwoNotEquals_returnsMatchedEntities()
throws Exception {
Entity a = new Entity("Widget", "a");
a.setProperty("x", ImmutableList.<Long>of(1L, 2L));
Entity b = new Entity("Widget", "b");
b.setProperty("x", ImmutableList.<Long>of(1L, 2L, 3L));
datastore.put(ImmutableList.<Entity>of(a, b));
// [START surprising_behavior_example_4]
Query q =
new Query("Widget")
.setFilter(
CompositeFilterOperator.and(
new FilterPredicate("x", FilterOperator.NOT_EQUAL, 1),
new FilterPredicate("x", FilterOperator.NOT_EQUAL, 2)));
// [END surprising_behavior_example_4]
// The two NOT_EQUAL filters in the query become like the combination of queries:
// x < 1 OR (x > 1 AND x < 2) OR x > 2
//
// Only "b" has some value which matches the "x > 2" portion of this query.
//
// See the documentation for more details:
// https://cloud.google.com/appengine/docs/java/datastore/query-restrictions#properties_with_multiple_values_can_behave_in_surprising_ways
List<Entity> results =
datastore.prepare(q.setKeysOnly()).asList(FetchOptions.Builder.withDefaults());
assertThat(results).named("query results").containsExactly(b);
}
private Entity retrievePersonWithLastName(String targetLastName) {
// [START single_retrieval_example]
Query q =
new Query("Person")
.setFilter(new FilterPredicate("lastName", FilterOperator.EQUAL, targetLastName));
PreparedQuery pq = datastore.prepare(q);
Entity result = pq.asSingleEntity();
// [END single_retrieval_example]
return result;
}
@Test
public void singleRetrievalExample_singleEntity_returnsEntity() throws Exception {
Entity a = new Entity("Person", "a");
a.setProperty("lastName", "Johnson");
Entity b = new Entity("Person", "b");
b.setProperty("lastName", "Smith");
datastore.put(ImmutableList.<Entity>of(a, b));
Entity result = retrievePersonWithLastName("Johnson");
assertThat(result).named("result").isEqualTo(a); // Note: Entity.equals() only checks the Key.
}
@Test
public void singleRetrievalExample_multitpleEntities_throwsException() throws Exception {
Entity a = new Entity("Person", "a");
a.setProperty("lastName", "Johnson");
Entity b = new Entity("Person", "b");
b.setProperty("lastName", "Johnson");
datastore.put(ImmutableList.<Entity>of(a, b));
try {
Entity result = retrievePersonWithLastName("Johnson");
fail("Expected TooManyResultsException");
} catch (TooManyResultsException expected) {
// TooManyResultsException does not provide addition details.
}
}
// [START query_limit_example]
private List<Entity> getTallestPeople() {
DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
Query q = new Query("Person").addSort("height", SortDirection.DESCENDING);
PreparedQuery pq = datastore.prepare(q);
return pq.asList(FetchOptions.Builder.withLimit(5));
}
// [END query_limit_example]
@Test
public void queryLimitExample_returnsLimitedEntities() throws Exception {
Entity a = new Entity("Person", "a");
a.setProperty("height", 200);
Entity b = new Entity("Person", "b");
b.setProperty("height", 199);
Entity c = new Entity("Person", "c");
c.setProperty("height", 201);
Entity d = new Entity("Person", "d");
d.setProperty("height", 198);
Entity e = new Entity("Person", "e");
e.setProperty("height", 202);
Entity f = new Entity("Person", "f");
f.setProperty("height", 197);
Entity g = new Entity("Person", "g");
g.setProperty("height", 203);
Entity h = new Entity("Person", "h");
h.setProperty("height", 196);
datastore.put(ImmutableList.<Entity>of(a, b, c, d, e, f, g, h));
List<Entity> results = getTallestPeople();
assertThat(results).named("results").containsExactly(g, e, c, a, b).inOrder();
}
}