Skip to content

Commit

Permalink
feat: Add support for count(*) aggregation query firestore
Browse files Browse the repository at this point in the history
  • Loading branch information
hemanshv committed Dec 8, 2022
1 parent 5966935 commit 1df2774
Show file tree
Hide file tree
Showing 12 changed files with 503 additions and 16 deletions.
Expand Up @@ -431,6 +431,24 @@ public async Task WhereArrayContainsAny()
Assert.Equal(new[] { "a", "b", "d", "e" }, ids);
}

[Fact]
public async Task Count_WithoutLimit()
{
CollectionReference collection = _fixture.HighScoreCollection;
var snapshot = await collection.Count().GetSnapshotAsync();
Assert.Equal(HighScore.Data.Length, snapshot.Count);
}

[Fact]
public async Task Count_WithLimit()
{
CollectionReference collection = _fixture.HighScoreCollection;
var snapshotWithoutLimit = await collection.Count().GetSnapshotAsync();
var snapshotWithLimit = await collection.Limit(2).Count().GetSnapshotAsync();
Assert.Equal(HighScore.Data.Length, snapshotWithoutLimit.Count);
Assert.Equal(2, snapshotWithLimit.Count);
}

public static TheoryData<string, object, string[]> ArrayContainsTheoryData = new TheoryData<string, object, string[]>
{
{ "StringArray", "x", new[] { "string-x,y", "mixed" } },
Expand Down
Expand Up @@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.

using Google.Cloud.Firestore.IntegrationTests.Models;
using Grpc.Core;
using System;
using System.Threading;
Expand Down Expand Up @@ -130,5 +131,17 @@ async Task IncrementCounter(Transaction transaction)
transaction.Set(doc, new { Count = snapshot.GetValue<int>("Count") + 1 });
}
}

[Fact]
public async Task TransactionWithCountAggregation()
{
var collection = _fixture.HighScoreCollection;
await _fixture.FirestoreDb.RunTransactionAsync(async txn =>
{
var aggQuery = collection.Count();
var snapshot = await txn.GetSnapshotAsync(aggQuery);
Assert.Equal(HighScore.Data.Length, snapshot.Count);
});
}
}
}
@@ -0,0 +1,61 @@
// Copyright 2022 Google LLC
//
// 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
//
// https://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.ClientTesting;
using Xunit;
using static Google.Cloud.Firestore.Tests.ProtoHelpers;

namespace Google.Cloud.Firestore.Tests;
public class AggregateQuerySnapshotTest
{
private static readonly FirestoreDb s_db = FirestoreDb.Create("proj", "db", new FakeFirestoreClient());

[Fact]
public void VerifyAllMembers()
{
AggregateQuery aggQuery = s_db.Collection("col").Count();
var sampleReadTime = Timestamp.FromProto(CreateProtoTimestamp(1, 2));
int sampleCount = 10;
var aggQuerySnapshot = new AggregateQuerySnapshot(aggQuery, sampleReadTime, sampleCount);
Assert.Equal(sampleCount, aggQuerySnapshot.Count);
Assert.Equal(sampleReadTime, aggQuerySnapshot.ReadTime);
Assert.Equal(aggQuery, aggQuerySnapshot.Query);
}

[Fact]
public void Equality()
{
AggregateQuery aggQuery1 = s_db.Collection("col").Count();
AggregateQuery aggQuery2 = s_db.Collection("col1").Count();
var sampleReadTime = Timestamp.FromProto(CreateProtoTimestamp(1, 2));
int sampleCount = 10;
var control = new AggregateQuerySnapshot(aggQuery1, sampleReadTime, sampleCount);

EqualityTester.AssertEqual(control,
equal: new[] {
// All the members are equal.
new AggregateQuerySnapshot(s_db.Collection("col").Count(), Timestamp.FromProto(CreateProtoTimestamp(1, 2)), 10)
},
unequal: new[] {
// Unequal aggregate query
new AggregateQuerySnapshot(aggQuery2, sampleReadTime, sampleCount),
// Null count.
new AggregateQuerySnapshot(aggQuery2, sampleReadTime, null),
// Same aggregate query and count but different read time.
new AggregateQuerySnapshot(s_db.Collection("col").Count(), Timestamp.FromProto(CreateProtoTimestamp(3, 4)), sampleCount),
// Same aggregate query but different read time and count.
new AggregateQuerySnapshot(s_db.Collection("col").Count(), Timestamp.FromProto(CreateProtoTimestamp(3, 4)), 20)
});
}
}
@@ -0,0 +1,88 @@
// Copyright 2022 Google LLC
//
// 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
//
// https://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.Api.Gax.Grpc;
using Google.Cloud.ClientTesting;
using Google.Cloud.Firestore.V1;
using Moq;
using System.Threading.Tasks;
using Xunit;
using static Google.Cloud.Firestore.Tests.ProtoHelpers;
using static Google.Cloud.Firestore.V1.StructuredAggregationQuery.Types;
using static Google.Cloud.Firestore.V1.StructuredAggregationQuery.Types.Aggregation.Types;

namespace Google.Cloud.Firestore.Tests;
public class AggregateQueryTest
{
private static readonly FirestoreDb s_db = FirestoreDb.Create("proj", "db", new FakeFirestoreClient());
private static readonly Query s_query = s_db.Collection("col");

[Fact]
public void TestToStructuredAggregationQuery()
{
var expectedStructuredAggregationQuery = new StructuredAggregationQuery
{
StructuredQuery = s_query.ToStructuredQuery(),
Aggregations = { new Aggregation { Alias = "Count", Count = new Count() } }
};
Assert.Equal(expectedStructuredAggregationQuery, s_query.Count().ToStructuredAggregationQuery());
}

[Fact]
public async Task GetSnapshotAsync_VerifySnapshotMembers()
{
Mock<FirestoreClient> mock = new() { CallBase = true };
var db = FirestoreDb.Create("proj", "db", mock.Object);
var query = db.Collection("col").Select("Name");
var sampleReadTime = CreateProtoTimestamp(1, 3);
var request = new RunAggregationQueryRequest
{
Parent = query.ParentPath,
StructuredAggregationQuery = new StructuredAggregationQuery
{
StructuredQuery = query.ToStructuredQuery(),
Aggregations = { new Aggregation { Alias = "Count", Count = new Count() } }
}
};
var response = new FakeAggregationQueryStream(new[]
{
new RunAggregationQueryResponse { ReadTime = sampleReadTime, Result = new AggregationResult() }
});
mock.Setup(c => c.RunAggregationQuery(request, It.IsAny<CallSettings>())).Returns(response);
var aggregateQuery = query.Count();
var snapshot = await aggregateQuery.GetSnapshotAsync();
Assert.Equal(aggregateQuery, snapshot.Query);
Assert.Equal(Timestamp.FromProto(sampleReadTime), snapshot.ReadTime);
}

[Fact]
public void Equality()
{
var control = s_query.Count();
EqualityTester.AssertEqual(control,
equal: new[]
{
new AggregateQuery(s_db.Collection("col")).WithAggregation(Aggregates.CreateCountAggregate()),
// Aggregate query returned by count.
s_db.Collection("col").Count()
},
unequal: new[]
{
// same query but without any aggregation.
new AggregateQuery(s_db.Collection("col")),
// un-equal query.
new AggregateQuery(s_db.Collection("col1"))
});
}
}
@@ -0,0 +1,37 @@
// Copyright 2022 Google LLC
//
// 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
//
// https://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.Api.Gax.Grpc.Testing;
using Google.Cloud.Firestore.V1;
using Grpc.Core;
using System.Collections.Generic;
using System.Linq;
using static Google.Cloud.Firestore.V1.FirestoreClient;

namespace Google.Cloud.Firestore.Tests;

/// <summary>
/// An aggregation query stream that simply returns the responses provided at construction time.
/// </summary>
internal class FakeAggregationQueryStream : RunAggregationQueryStream
{
internal FakeAggregationQueryStream(IEnumerable<RunAggregationQueryResponse> responses)
{
var adapter = new AsyncStreamAdapter<RunAggregationQueryResponse>(responses.ToAsyncEnumerable().GetAsyncEnumerator(default));
GrpcCall = new AsyncServerStreamingCall<RunAggregationQueryResponse>(adapter, null, null, null, () => { });
}

public override AsyncServerStreamingCall<RunAggregationQueryResponse> GrpcCall { get; }
}

Expand Up @@ -86,6 +86,28 @@ public async Task GetSnaphotAsync_FailsAfterWrite()
await Assert.ThrowsAsync<InvalidOperationException>(() => transaction.GetSnapshotAsync(doc.Parent));
}

[Fact]
public async Task GetSnaphsotAsync_AggregationQuery()
{
var db = CreateFirestoreDbExpectingNoCommits();
var transaction = await Transaction.BeginAsync(db, null, default);
var query = db.Collection("col");
var aggQuery = query.Count();
var snapshot = await transaction.GetSnapshotAsync(aggQuery);
Assert.Equal(aggQuery, snapshot.Query);
Assert.NotNull(snapshot);
}

[Fact]
public async Task GetSnaphotAsync_FailAfterWriteForAggregationQuery()
{
var db = CreateFirestoreDbExpectingNoCommits();
var transaction = await Transaction.BeginAsync(db, null, default);
var doc = db.Document("col/doc");
transaction.Delete(doc);
await Assert.ThrowsAsync<InvalidOperationException>(() => transaction.GetSnapshotAsync(doc.Parent));
}

[Fact]
public async Task CommitAsync()
{
Expand Down
Expand Up @@ -86,6 +86,16 @@ public override RunQueryStream RunQuery(RunQueryRequest request, CallSettings ca
return new FakeQueryStream(new[] { response });
}

public override RunAggregationQueryStream RunAggregationQuery(RunAggregationQueryRequest request, CallSettings callSettings = null)
{
var response = new RunAggregationQueryResponse { ReadTime = GetNextTimestamp(), Result = new AggregationResult() };
if (request.ConsistencySelectorCase == RunAggregationQueryRequest.ConsistencySelectorOneofCase.Transaction)
{
response.Transaction = request.Transaction;
}
return new FakeAggregationQueryStream(new[] { response });
}

public override BatchGetDocumentsStream BatchGetDocuments(BatchGetDocumentsRequest request, CallSettings callSettings = null)
{
string transaction = request.ConsistencySelectorCase == BatchGetDocumentsRequest.ConsistencySelectorOneofCase.Transaction ?
Expand Down

0 comments on commit 1df2774

Please sign in to comment.