datastore/api/TaskListTest/DatastoreTest.cs (914 lines of code) (raw):
// 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.
using Google.Cloud.Datastore.V1;
using Google.Protobuf;
using System;
using System.Linq;
using System.Collections.Generic;
using Xunit;
using System.Threading;
namespace GoogleCloudSamples
{
public class DatastoreTest : IDisposable
{
private readonly string _projectId;
private readonly DatastoreDb _db;
private readonly Entity _sampleTask;
private readonly KeyFactory _keyFactory;
private readonly DateTime _includedDate =
new DateTime(1999, 12, 31, 0, 0, 0, DateTimeKind.Utc);
private readonly DateTime _startDate =
new DateTime(1998, 4, 18, 0, 0, 0, DateTimeKind.Utc);
private readonly DateTime _endDate =
new DateTime(2013, 4, 18, 0, 0, 0, DateTimeKind.Utc);
private readonly int _retryCount = 3;
private readonly int _retryDelayMs = 500;
private readonly RetryRobot _retryRobot = new RetryRobot()
{
RetryWhenExceptions = new[] { typeof(Xunit.Sdk.XunitException) },
// Eventually consistency can take a long time.
MaxTryCount = 20
};
public DatastoreTest()
{
_projectId = Environment.GetEnvironmentVariable("GOOGLE_PROJECT_ID");
_db = DatastoreDb.Create(_projectId, TestUtil.RandomName());
_keyFactory = _db.CreateKeyFactory("Task");
_sampleTask = new Entity()
{
Key = _keyFactory.CreateKey("sampleTask"),
};
}
public void Dispose()
{
ClearTasks();
}
/// <summary>
/// Retry action.
/// Datastore guarantees only eventual consistency. Many tests write
/// an entity and then query it afterward, but may not find it immediately.
/// </summary>
/// <param name="action"></param>
private void Eventually(Action action) => _retryRobot.Eventually(action);
private bool IsValidKey(Key key)
{
foreach (var element in key.Path)
{
if (element.Id == 0 && string.IsNullOrEmpty(element.Name))
return false;
if (string.IsNullOrEmpty(element.Kind))
return false;
}
return true;
}
[Fact]
public void TestIncompleteKey()
{
// [START datastore_incomplete_key]
Key incompleteKey = _db.CreateKeyFactory("Task").CreateIncompleteKey();
Key key = _db.AllocateId(incompleteKey);
// [END datastore_incomplete_key]
Assert.True(IsValidKey(key));
}
[Fact]
public void TestNamedKey()
{
// [START datastore_named_key]
Key key = _db.CreateKeyFactory("Task").CreateKey("sampleTask");
// [END datastore_named_key]
Assert.True(IsValidKey(key));
}
[Fact]
public void TestKeyWithParent()
{
// [START datastore_key_with_parent]
Key rootKey = _db.CreateKeyFactory("TaskList").CreateKey("default");
Key key = new KeyFactory(rootKey, "Task").CreateKey("sampleTask");
// [END datastore_key_with_parent]
Assert.True(IsValidKey(key));
}
[Fact]
public void TestKeyWithMultilevelParent()
{
// [START datastore_key_with_multilevel_parent]
Key rootKey = _db.CreateKeyFactory("User").CreateKey("Alice");
Key taskListKey = new KeyFactory(rootKey, "TaskList").CreateKey("default");
Key key = new KeyFactory(taskListKey, "Task").CreateKey("sampleTask");
// [END datastore_key_with_multilevel_parent]
Assert.True(IsValidKey(key));
}
private void AssertValidEntity(Entity original)
{
_db.Upsert(original);
Assert.Equal(original, _db.Lookup(original.Key));
}
[Fact]
public void TestEntityWithParent()
{
// [START datastore_entity_with_parent]
Key taskListKey = _db.CreateKeyFactory("TaskList").CreateKey(TestUtil.RandomName());
Key taskKey = new KeyFactory(taskListKey, "Task").CreateKey("sampleTask");
Entity task = new Entity()
{
Key = taskKey,
["category"] = "Personal",
["done"] = false,
["priority"] = 4,
["description"] = "Learn Cloud Datastore"
};
// [END datastore_entity_with_parent]
AssertValidEntity(task);
}
[Fact]
public void TestProperties()
{
// [START datastore_properties]
Entity task = new Entity()
{
Key = _db.CreateKeyFactory("Task").CreateKey("sampleTask"),
["category"] = "Personal",
["created"] = new DateTime(1999, 01, 01, 0, 0, 0, DateTimeKind.Utc),
["done"] = false,
["priority"] = 4,
["percent_complete"] = 10.0,
["description"] = new Value()
{
StringValue = "Learn Cloud Datastore",
ExcludeFromIndexes = true
},
};
// [END datastore_properties]
AssertValidEntity(task);
}
[Fact]
public void TestArrayValue()
{
// [START datastore_array_value]
Entity task = new Entity()
{
Key = _db.CreateKeyFactory("Task").CreateKey("sampleTask"),
["collaborators"] = new ArrayValue() { Values = { "alice", "bob" } },
["tags"] = new ArrayValue() { Values = { "fun", "programming" } }
};
// [END datastore_array_value]
AssertValidEntity(task);
}
[Fact]
public void TestBasicEntity()
{
// [START datastore_basic_entity]
Entity task = new Entity()
{
Key = _db.CreateKeyFactory("Task").CreateKey("sampleTask"),
["category"] = "Personal",
["done"] = false,
["priority"] = 4,
["description"] = "Learn Cloud Datastore"
};
// [END datastore_basic_entity]
AssertValidEntity(task);
}
[Fact]
public void TestUpsert()
{
// [START datastore_upsert]
_db.Upsert(_sampleTask);
// [END datastore_upsert]
Assert.Equal(_sampleTask, _db.Lookup(_sampleTask.Key));
// Make sure a second upsert doesn't throw an exception.
_db.Upsert(_sampleTask);
}
[Fact]
public void TestInsert()
{
// [START datastore_insert]
Entity task = new Entity()
{
Key = _keyFactory.CreateIncompleteKey()
};
task.Key = _db.Insert(task);
// [END datastore_insert]
Assert.Equal(task, _db.Lookup(task.Key));
// Make sure a second insert throws an exception.
Grpc.Core.RpcException e = Assert.Throws<Grpc.Core.RpcException>(() =>
_db.Insert(task));
}
[Fact]
public void TestInsertCompleteKey()
{
Entity task = new Entity()
{
Key = _keyFactory.CreateKey(new Random().Next())
};
task.Key = _db.Insert(task);
// This assertion should fail!
Assert.Null(task.Key);
// Instead, this assertion should pass:
// Assert.Equal(task, _db.Lookup(task.Key));
}
[Fact]
public void TestLookup()
{
_db.Upsert(_sampleTask);
// [START datastore_lookup]
Entity task = _db.Lookup(_sampleTask.Key);
// [END datastore_lookup]
Assert.Equal(_sampleTask, task);
}
[Fact]
public void TestUpdate()
{
_db.Upsert(_sampleTask);
// [START datastore_update]
_sampleTask["priority"] = 5;
_db.Update(_sampleTask);
// [END datastore_update]
Assert.Equal(_sampleTask, _db.Lookup(_sampleTask.Key));
}
[Fact]
public void TestDelete()
{
_db.Upsert(_sampleTask);
// [START datastore_delete]
_db.Delete(_sampleTask.Key);
// [END datastore_delete]
Assert.Null(_db.Lookup(_sampleTask.Key));
}
private Entity[] UpsertBatch(Key taskKey1, Key taskKey2)
{
var taskList = new[]
{
new Entity()
{
Key = taskKey1,
["category"] = "Personal",
["done"] = false,
["priority"] = 4,
["description"] = "Learn Cloud Datastore"
},
new Entity()
{
Key = taskKey2,
["category"] = "Personal",
["done"] = "false",
["priority"] = 5,
["description"] = "Integrate Cloud Datastore"
}
};
_db.Upsert(taskList);
return taskList;
}
[Fact]
public void TestBatchUpsert()
{
// [START datastore_batch_upsert]
var taskList = new[]
{
new Entity()
{
Key = _keyFactory.CreateIncompleteKey(),
["category"] = "Personal",
["done"] = false,
["priority"] = 4,
["description"] = "Learn Cloud Datastore"
},
new Entity()
{
Key = _keyFactory.CreateIncompleteKey(),
["category"] = "Personal",
["done"] = "false",
["priority"] = 5,
["description"] = "Integrate Cloud Datastore"
}
};
var keyList = _db.Upsert(taskList[0], taskList[1]);
// [END datastore_batch_upsert]
taskList[0].Key = keyList[0];
taskList[1].Key = keyList[1];
Assert.Equal(taskList[0], _db.Lookup(keyList[0]));
Assert.Equal(taskList[1], _db.Lookup(keyList[1]));
}
[Fact]
public void TestBatchLookup()
{
// [START datastore_batch_lookup]
var keys = new Key[] { _keyFactory.CreateKey(1), _keyFactory.CreateKey(2) };
// [END datastore_batch_lookup]
var expectedTasks = UpsertBatch(keys[0], keys[1]);
// [START datastore_batch_lookup]
var tasks = _db.Lookup(keys[0], keys[1]);
// [END datastore_batch_lookup]
Assert.Equal(expectedTasks[0], tasks[0]);
Assert.Equal(expectedTasks[1], tasks[1]);
}
[Fact]
public void TestBatchDelete()
{
// [START datastore_batch_delete]
var keys = new Key[] { _keyFactory.CreateKey(1), _keyFactory.CreateKey(2) };
// [END datastore_batch_delete]
UpsertBatch(keys[0], keys[1]);
var lookups = _db.Lookup(keys);
Assert.NotNull(lookups[0]);
Assert.NotNull(lookups[1]);
// [START datastore_batch_delete]
_db.Delete(keys);
// [END datastore_batch_delete]
lookups = _db.Lookup(keys[0], keys[1]);
Assert.Null(lookups[0]);
Assert.Null(lookups[1]);
}
private void ClearTasks()
{
var deadEntities = _db.RunQuery(new Query("Task"));
_db.Delete(deadEntities.Entities);
}
private string UpsertTaskList()
{
string taskListKeyName = TestUtil.RandomName();
Key taskListKey = _db.CreateKeyFactory("TaskList").CreateKey(taskListKeyName);
Key taskKey = new KeyFactory(taskListKey, "Task").CreateKey("someTask");
Entity task = new Entity()
{
Key = taskKey,
["category"] = "Personal",
["done"] = false,
["completed"] = false,
["priority"] = 4,
["created"] = _includedDate,
["percent_complete"] = 10.0,
["description"] = new Value()
{
StringValue = "Learn Cloud Datastore",
ExcludeFromIndexes = true
},
["tag"] = new ArrayValue() { Values = { "fun", "l", "programming" } }
};
_db.Upsert(task);
return taskListKeyName;
}
private static bool IsEmpty(DatastoreQueryResults results) =>
results.Entities.Count == 0;
[Fact(Skip = "https://github.com/GoogleCloudPlatform/google-cloud-dotnet/issues/304")]
public void TestBasicQuery()
{
UpsertTaskList();
// [START datastore_basic_query]
Query query = new Query("Task")
{
Filter = Filter.And(Filter.Equal("done", false),
Filter.GreaterThanOrEqual("priority", 4)),
Order = { { "priority", PropertyOrder.Types.Direction.Descending } }
};
// [END datastore_basic_query]
Eventually(() => Assert.False(IsEmpty(_db.RunQuery(query))));
}
[Fact]
public void TestRunQuery()
{
UpsertTaskList();
Eventually(() =>
{
// [START datastore_run_query]
Query query = new Query("Task");
DatastoreQueryResults tasks = _db.RunQuery(query);
// [END datastore_run_query]
Assert.False(IsEmpty(tasks));
});
}
[Fact]
public void TestPropertyFilter()
{
UpsertTaskList();
Eventually(() =>
{
// [START datastore_property_filter]
Query query = new Query("Task")
{
Filter = Filter.Equal("done", false)
};
// [END datastore_property_filter]
var tasks = _db.RunQuery(query);
Assert.False(IsEmpty(tasks));
});
}
[Fact]
public void TestCompositeFilter()
{
UpsertTaskList();
// [START datastore_composite_filter]
Query query = new Query("Task")
{
Filter = Filter.And(Filter.Equal("done", false),
Filter.Equal("priority", 4)),
};
// [END datastore_composite_filter]
Eventually(() => Assert.False(IsEmpty(_db.RunQuery(query))));
}
[Fact]
public void TestKeyFilter()
{
UpsertTaskList();
// [START datastore_key_filter]
Query query = new Query("Task")
{
Filter = Filter.GreaterThan("__key__", _keyFactory.CreateKey("aTask"))
};
// [END datastore_key_filter]
Eventually(() => Assert.False(IsEmpty(_db.RunQuery(query))));
}
[Fact]
public void TestAscendingSort()
{
UpsertTaskList();
// [START datastore_ascending_sort]
Query query = new Query("Task")
{
Order = { { "created", PropertyOrder.Types.Direction.Ascending } }
};
// [END datastore_ascending_sort]
Eventually(() => Assert.False(IsEmpty(_db.RunQuery(query))));
}
[Fact]
public void TestDescendingSort()
{
UpsertTaskList();
// [START datastore_descending_sort]
Query query = new Query("Task")
{
Order = { { "created", PropertyOrder.Types.Direction.Descending } }
};
// [END datastore_descending_sort]
Eventually(() => Assert.False(IsEmpty(_db.RunQuery(query))));
}
[Fact(Skip = "https://github.com/GoogleCloudPlatform/google-cloud-dotnet/issues/304")]
public void TestMultiSort()
{
UpsertTaskList();
// [START datastore_multi_sort]
Query query = new Query("Task")
{
Order = { { "priority", PropertyOrder.Types.Direction.Descending },
{ "created", PropertyOrder.Types.Direction.Ascending } }
};
// [END datastore_multi_sort]
Eventually(() => Assert.False(IsEmpty(_db.RunQuery(query))));
}
[Fact]
public void TestKindlessQuery()
{
UpsertTaskList();
// [START datastore_kindless_query]
Query query = new Query()
{
Filter = Filter.GreaterThan("__key__",
_keyFactory.CreateKey("aTask"))
};
// [END datastore_kindless_query]
Eventually(() => Assert.False(IsEmpty(_db.RunQuery(query))));
}
[Fact]
public void TestAncestorQuery()
{
string keyName = UpsertTaskList();
// [START datastore_ancestor_query]
Query query = new Query("Task")
{
Filter = Filter.HasAncestor(_db.CreateKeyFactory("TaskList")
.CreateKey(keyName))
};
// [END datastore_ancestor_query]
Eventually(() => Assert.False(IsEmpty(_db.RunQuery(query))));
}
[Fact(Skip = "https://github.com/GoogleCloudPlatform/google-cloud-dotnet/issues/304")]
public void TestProjectionQuery()
{
UpsertTaskList();
// [START datastore_projection_query]
Query query = new Query("Task")
{
Projection = { "priority", "percent_complete" }
};
// [END datastore_projection_query]
Eventually(() => Assert.False(IsEmpty(_db.RunQuery(query))));
}
[Fact(Skip = "https://github.com/GoogleCloudPlatform/google-cloud-dotnet/issues/304")]
public void TestRunProjectionQuery()
{
UpsertTaskList();
// [START datastore_run_query_projection]
Query query = new Query("Task")
{
Projection = { "priority", "percent_complete" }
};
List<long> priorities = new List<long>();
List<double> percentCompletes = new List<double>();
foreach (var entity in _db.RunQuery(query).Entities)
{
priorities.Add((long)entity["priority"]);
percentCompletes.Add((double)entity["percent_complete"]);
}
// [END datastore_run_query_projection]
Eventually(() =>
{
Assert.Equal(new long[] { 4L }, priorities.ToArray());
Assert.Equal(new double[] { 10.0 }, percentCompletes.ToArray());
});
}
[Fact]
public void TestKeysOnlyQuery()
{
UpsertTaskList();
// [START datastore_keys_only_query]
Query query = new Query("Task")
{
Projection = { "__key__" }
};
// [END datastore_keys_only_query]
Eventually(() =>
{
foreach (Entity task in _db.RunQuery(query).Entities)
{
Assert.False(string.IsNullOrEmpty(task.Key.Path[0].Name));
Assert.Empty(task.Properties);
break;
}
});
}
[Fact]
public void TestNamespaceRunQuery()
{
UpsertTaskList();
Eventually(() =>
{
// [START datastore_namespace_run_query]
KeyFactory keyFactory = _db.CreateKeyFactory("__namespace__");
// List all the namespaces between a lower and upper bound.
string lowerBound = _db.NamespaceId.Substring(0,
_db.NamespaceId.Length - 1);
string upperBound = _db.NamespaceId + "z";
Key startNamespace = keyFactory.CreateKey(lowerBound);
Key endNamespace = keyFactory.CreateKey(upperBound);
Query query = new Query("__namespace__")
{
Filter = Filter.And(
Filter.GreaterThan("__key__", startNamespace),
Filter.LessThan("__key__", endNamespace))
};
var namespaces = new List<string>();
foreach (Entity entity in _db.RunQuery(query).Entities)
{
namespaces.Add(entity.Key.Path[0].Name);
};
// [END datastore_namespace_run_query]
Assert.Contains(_db.NamespaceId, namespaces.ToArray());
});
}
[Fact]
public void TestKindRunQuery()
{
UpsertTaskList();
Eventually(() =>
{
// [START datastore_kind_run_query]
Query query = new Query("__kind__");
var kinds = new List<string>();
foreach (Entity entity in _db.RunQuery(query).Entities)
{
kinds.Add(entity.Key.Path[0].Name);
};
// [END datastore_kind_run_query]
Assert.Contains("Task", kinds);
});
}
[Fact]
public void TestPropertyRunQuery()
{
UpsertTaskList();
Eventually(() =>
{
// [START datastore_property_run_query]
Query query = new Query("__property__");
var properties = new List<string>();
foreach (Entity entity in _db.RunQuery(query).Entities)
{
string kind = entity.Key.Path[0].Name;
string property = entity.Key.Path[1].Name;
if (kind == "Task")
properties.Add(property);
};
// [END datastore_property_run_query]
properties.Sort();
Assert.Equal(new[] { "category", "completed", "created",
"done", "percent_complete", "priority", "tag" },
properties.ToArray());
});
}
[Fact]
public void TestPropertyByKindRunQuery()
{
ClearTasks();
UpsertTaskList();
Eventually(() =>
{
// [START datastore_property_by_kind_run_query]
Key key = _db.CreateKeyFactory("__kind__").CreateKey("Task");
Query query = new Query("__property__")
{
Filter = Filter.HasAncestor(key)
};
var properties = new List<string>();
foreach (Entity entity in _db.RunQuery(query).Entities)
{
string kind = entity.Key.Path[0].Name;
string property = entity.Key.Path[1].Name;
var representations = entity["property_representation"]
.ArrayValue.Values.Select(x => x.StringValue)
.OrderBy(x => x);
properties.Add($"{property}:" +
string.Join(",", representations));
};
// [END datastore_property_by_kind_run_query]
properties.Sort();
Assert.Equal(new[] {
"category:STRING",
"completed:BOOLEAN",
"created:INT64",
"done:BOOLEAN",
"percent_complete:DOUBLE",
"priority:INT64",
"tag:STRING" },
properties.ToArray());
});
}
[Fact]
public void TestPropertyFilteringRunQuery()
{
UpsertTaskList();
Eventually(() =>
{
// [START datastore_property_filtering_run_query]
Key key = _db.CreateKeyFactory("__kind__").CreateKey("Task");
Key startKey = new KeyFactory(key, "__property__")
.CreateKey("priority");
Query query = new Query("__property__")
{
Filter = Filter.GreaterThanOrEqual("__key__", startKey)
};
var properties = new List<string>();
foreach (Entity entity in _db.RunQuery(query).Entities)
{
string kind = entity.Key.Path[0].Name;
string property = entity.Key.Path[1].Name;
properties.Add($"{kind}.{property}");
};
// [END datastore_property_filtering_run_query]
properties.Sort();
Assert.NotEmpty(properties);
});
}
[Fact(Skip = "https://github.com/GoogleCloudPlatform/google-cloud-dotnet/issues/346")]
public void TestDistinctOnQuery()
{
UpsertTaskList();
Eventually(() =>
{
// [START datastore_distinct_on_query]
Query query = new Query("Task")
{
Projection = { "category", "priority" },
DistinctOn = { "category" },
Order = {
{ "category", PropertyOrder.Types.Direction.Ascending},
{"priority", PropertyOrder.Types.Direction.Ascending }
}
};
// [END datastore_distinct_on_query]
Assert.False(IsEmpty(_db.RunQuery(query)));
});
}
[Fact]
public void TestArrayValueInequalityRange()
{
UpsertTaskList();
// [START datastore_array_value_inequality_range]
Query query = new Query("Task")
{
Filter = Filter.And(Filter.GreaterThan("tag", "learn"),
Filter.LessThan("tag", "math"))
};
// [END datastore_array_value_inequality_range]
Eventually(() => Assert.True(IsEmpty(_db.RunQuery(query))));
}
[Fact]
public void TestArrayValueEquality()
{
UpsertTaskList();
// [START datastore_array_value_equality]
Query query = new Query("Task")
{
Filter = Filter.And(Filter.Equal("tag", "fun"),
Filter.Equal("tag", "programming"))
};
// [END datastore_array_value_equality]
Eventually(() => Assert.False(IsEmpty(_db.RunQuery(query))));
}
[Fact]
public void TestInequalityRange()
{
UpsertTaskList();
// [START datastore_inequality_range]
Query query = new Query("Task")
{
Filter = Filter.And(Filter.GreaterThan("created", _startDate),
Filter.LessThan("created", _endDate))
};
// [END datastore_inequality_range]
Eventually(() => Assert.False(IsEmpty(_db.RunQuery(query))));
}
[Fact]
public void TestInequalityInvalid()
{
UpsertTaskList();
// [START datastore_inequality_invalid]
Query query = new Query("Task")
{
Filter = Filter.And(Filter.GreaterThan("created", _startDate),
Filter.GreaterThan("priority", 3))
};
// [END datastore_inequality_invalid]
Exception e = Assert.Throws<Grpc.Core.RpcException>(() =>
IsEmpty(_db.RunQuery(query)));
}
[Fact(Skip = "https://github.com/GoogleCloudPlatform/google-cloud-dotnet/issues/304")]
public void TestEqualAndInequalityRange()
{
UpsertTaskList();
// [START datastore_equal_and_inequality_range]
Query query = new Query("Task")
{
Filter = Filter.And(Filter.Equal("priority", 4),
Filter.GreaterThan("created", _startDate),
Filter.LessThan("created", _endDate))
};
// [END datastore_equal_and_inequality_range]
Eventually(() => Assert.False(IsEmpty(_db.RunQuery(query))));
}
[Fact(Skip = "https://github.com/GoogleCloudPlatform/google-cloud-dotnet/issues/304")]
public void TestInequalitySort()
{
UpsertTaskList();
// [START datastore_inequality_sort]
Query query = new Query("Task")
{
Filter = Filter.GreaterThan("priority", 3),
Order = { { "priority", PropertyOrder.Types.Direction.Ascending},
{"created", PropertyOrder.Types.Direction.Ascending } }
};
// [END datastore_inequality_sort]
Eventually(() => Assert.False(IsEmpty(_db.RunQuery(query))));
}
[Fact]
public void TestInequalitySortInvalidNotSame()
{
UpsertTaskList();
// [START datastore_inequality_sort_invalid_not_same]
Query query = new Query("Task")
{
Filter = Filter.GreaterThan("priority", 3),
Order = { { "created", PropertyOrder.Types.Direction.Ascending } }
};
// [END datastore_inequality_sort_invalid_not_same]
Exception e = Assert.Throws<Grpc.Core.RpcException>(() =>
IsEmpty(_db.RunQuery(query)));
}
[Fact]
public void TestInequalitySortInvalidNotFirst()
{
UpsertTaskList();
// [START datastore_inequality_sort_invalid_not_first]
Query query = new Query("Task")
{
Filter = Filter.GreaterThan("priority", 3),
Order = { {"created", PropertyOrder.Types.Direction.Ascending },
{ "priority", PropertyOrder.Types.Direction.Ascending} }
};
// [END datastore_inequality_sort_invalid_not_first]
Exception e = Assert.Throws<Grpc.Core.RpcException>(() =>
IsEmpty(_db.RunQuery(query)));
}
[Fact]
public void TestLimit()
{
UpsertTaskList();
// [START datastore_limit]
Query query = new Query("Task")
{
Limit = 5,
};
// [END datastore_limit]
Eventually(() => Assert.InRange(_db.RunQuery(query).Entities.Count(), 1, 5));
}
[Fact]
public void TestCursorPaging()
{
UpsertTaskList();
_db.Upsert(_sampleTask);
Eventually(() =>
{
var pageOneCursor = CursorPaging(1, null);
Assert.NotNull(pageOneCursor);
var pageTwoCursor = CursorPaging(1, pageOneCursor);
Assert.NotNull(pageTwoCursor);
Assert.NotEqual(pageOneCursor, pageTwoCursor);
});
}
private string CursorPaging(int pageSize, string pageCursor)
{
// [START datastore_cursor_paging]
Query query = new Query("Task")
{
Limit = pageSize,
};
if (!string.IsNullOrEmpty(pageCursor))
query.StartCursor = ByteString.FromBase64(pageCursor);
return _db.RunQuery(query).EndCursor?.ToBase64();
// [END datastore_cursor_paging]
}
private IReadOnlyList<Key> UpsertBalances()
{
KeyFactory keyFactory = _db.CreateKeyFactory("People");
Entity from = new Entity()
{
Key = keyFactory.CreateKey("from"),
["balance"] = 100
};
Entity to = new Entity()
{
Key = keyFactory.CreateKey("to"),
["balance"] = 0
};
var keys = _db.Upsert(from, to);
// TODO: return keys; when following bug is fixed:
// https://github.com/GoogleCloudPlatform/google-cloud-dotnet/issues/308
return new[] { from.Key, to.Key };
}
// [START datastore_transactional_update]
private void TransferFunds(Key fromKey, Key toKey, long amount)
{
using (var transaction = _db.BeginTransaction())
{
var entities = transaction.Lookup(fromKey, toKey);
entities[0]["balance"].IntegerValue -= amount;
entities[1]["balance"].IntegerValue += amount;
transaction.Update(entities);
transaction.Commit();
}
}
// [END datastore_transactional_update]
private void TransferFunds(Key fromKey, Key toKey, long amount,
DatastoreTransaction transaction)
{
var entities = transaction.Lookup(fromKey, toKey);
entities[0]["balance"].IntegerValue -= amount;
entities[1]["balance"].IntegerValue += amount;
transaction.Update(entities);
}
[Fact]
public void TestTransactionalUpdate()
{
var keys = UpsertBalances();
using (var transaction = _db.BeginTransaction())
{
TransferFunds(keys[0], keys[1], 10, transaction);
transaction.Commit();
}
var entities = _db.Lookup(keys);
Assert.Equal(90, entities[0]["balance"]);
Assert.Equal(10, entities[1]["balance"]);
}
[Fact]
public void TestConflictingTransactionalUpdate()
{
var keys = UpsertBalances();
using (var transaction = _db.BeginTransaction())
{
TransferFunds(keys[0], keys[1], 10, transaction);
TransferFunds(keys[1], keys[0], 5);
Exception e = Assert.Throws<Grpc.Core.RpcException>(() =>
transaction.Commit());
}
}
// [START datastore_transactional_retry]
/// <summary>
/// Retry the action when a Grpc.Core.RpcException is thrown.
/// </summary>
private T RetryRpc<T>(Func<T> action)
{
List<Grpc.Core.RpcException> exceptions = null;
var delayMs = _retryDelayMs;
for (int tryCount = 0; tryCount < _retryCount; ++tryCount)
{
try
{
return action();
}
catch (Grpc.Core.RpcException e)
{
if (exceptions == null)
exceptions = new List<Grpc.Core.RpcException>();
exceptions.Add(e);
}
System.Threading.Thread.Sleep(delayMs);
delayMs *= 2; // Exponential back-off.
}
throw new AggregateException(exceptions);
}
private void RetryRpc(Action action)
{
RetryRpc(() => { action(); return 0; });
}
[Fact]
public void TestTransactionalRetry()
{
int tryCount = 0;
var keys = UpsertBalances();
RetryRpc(() =>
{
using (var transaction = _db.BeginTransaction())
{
TransferFunds(keys[0], keys[1], 10, transaction);
// Insert a conflicting transaction on the first try.
if (tryCount++ == 0)
TransferFunds(keys[1], keys[0], 5);
transaction.Commit();
}
});
Assert.Equal(2, tryCount);
}
// [END datastore_transactional_retry]
[Fact]
public void TestTransactionalGetOrCreate()
{
// [START datastore_transactional_get_or_create]
Entity task;
using (var transaction = _db.BeginTransaction())
{
task = transaction.Lookup(_sampleTask.Key);
if (task == null)
{
transaction.Insert(_sampleTask);
transaction.Commit();
}
}
// [END datastore_transactional_get_or_create]
Assert.Equal(_sampleTask, _db.Lookup(_sampleTask.Key));
}
[Fact]
public void TestTransactionalSingleEntityGroupReadOnly()
{
string keyName = UpsertTaskList();
Key taskListKey = _db.CreateKeyFactory("TaskList")
.CreateKey(keyName);
Entity taskListEntity = new Entity() { Key = taskListKey };
_db.Upsert(taskListEntity);
// [START datastore_transactional_single_entity_group_read_only]
Entity taskList;
IReadOnlyList<Entity> tasks;
using (var transaction = _db.BeginTransaction(TransactionOptions.CreateReadOnly()))
{
taskList = transaction.Lookup(taskListKey);
var query = new Query("Task")
{
Filter = Filter.HasAncestor(taskListKey)
};
tasks = transaction.RunQuery(query).Entities;
transaction.Commit();
}
// [END datastore_transactional_single_entity_group_read_only]
Assert.Equal(taskListEntity, taskList);
Assert.Collection(tasks, task => { });
}
[Fact]
public void TestEventualConsistentQuery()
{
string keyName = UpsertTaskList();
Eventually(() =>
{
// [START datastore_eventual_consistent_query]
Query query = new Query("Task")
{
Filter = Filter.HasAncestor(_db.CreateKeyFactory("TaskList")
.CreateKey(keyName))
};
var results = _db.RunQuery(query,
ReadOptions.Types.ReadConsistency.Eventual);
// [END datastore_eventual_consistent_query]
Assert.False(IsEmpty(results));
});
}
[Fact]
public void TestUnindexedPropertyQuery()
{
UpsertTaskList();
// [START datastore_unindexed_property_query]
Query query = new Query("Task")
{
Filter = Filter.Equal("description", "Learn Cloud Datastore")
};
// [END datastore_unindexed_property_query]
Eventually(() =>
{
var tasks = _db.RunQuery(query).Entities;
Assert.True(IsEmpty(_db.RunQuery(query)));
});
}
[Fact]
public void TestExplodingProperties()
{
// [START datastore_exploding_properties]
Entity task = new Entity()
{
Key = _db.CreateKeyFactory("Task").CreateKey("sampleTask"),
["tags"] = new ArrayValue() { Values = { "fun", "programming", "learn" } },
["collaborators"] = new ArrayValue() { Values = { "alice", "bob", "charlie" } },
["created"] = DateTime.UtcNow
};
// [END datastore_exploding_properties]
// Avoid test failure due to float rounding differences.
task["created"] = new DateTime(2016, 8, 12, 9, 0, 0, DateTimeKind.Utc);
AssertValidEntity(task);
}
}
}