Skip to content

Commit

Permalink
(CommunityToolkit#245) MongoDB Driver, complete.
Browse files Browse the repository at this point in the history
  • Loading branch information
adrianhall committed Feb 6, 2025
1 parent 6ed9c47 commit 7af4b16
Show file tree
Hide file tree
Showing 5 changed files with 199 additions and 88 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -24,86 +24,13 @@ param tags object = {}

/*********************************************************************************/

var compositeIndices = [
[
{ path: '/BestPictureWinner', order: 'ascending' }
{ path: '/id', order: 'ascending' }
]
[
{ path: '/BestPictureWinner', order: 'descending' }
{ path: '/id', order: 'ascending' }
]
[
{ path: '/Duration', order: 'ascending' }
{ path: '/id', order: 'ascending' }
]
[
{ path: '/Duration', order: 'descending' }
{ path: '/id', order: 'ascending' }
]
[
{ path: '/Rating', order: 'ascending' }
{ path: '/id', order: 'ascending' }
]
[
{ path: '/Rating', order: 'descending' }
{ path: '/id', order: 'ascending' }
]
[
{ path: '/ReleaseDate', order: 'ascending' }
{ path: '/id', order: 'ascending' }
]
[
{ path: '/ReleaseDate', order: 'descending' }
{ path: '/id', order: 'ascending' }
]
[
{ path: '/Title', order: 'ascending' }
{ path: '/id', order: 'ascending' }
]
[
{ path: '/Title', order: 'descending' }
{ path: '/id', order: 'ascending' }
]
[
{ path: '/UpdatedAt', order: 'ascending' }
{ path: '/id', order: 'ascending' }
]
[
{ path: '/UpdatedAt', order: 'descending' }
{ path: '/id', order: 'ascending' }
]
[
{ path: '/Year', order: 'ascending' }
{ path: '/id', order: 'ascending' }
]
[
{ path: '/Year', order: 'descending' }
{ path: '/id', order: 'ascending' }
]
[
{ path: '/Year', order: 'ascending' }
{ path: '/Title', order: 'ascending' }
{ path: '/id', order: 'ascending' }
]
[
{ path: '/Year', order: 'descending' }
{ path: '/Title', order: 'ascending' }
{ path: '/id', order: 'ascending' }
]
[
{ path: '/Year', order: 'ascending' }
{ path: '/Title', order: 'descending' }
{ path: '/id', order: 'ascending' }
]
[
{ path: '/Year', order: 'descending' }
{ path: '/Title', order: 'descending' }
{ path: '/id', order: 'ascending' }
]
]

/*********************************************************************************/
/*
** Note that this implements the serverless (RU) model. If you use the dedicated (vCore)
** deployment model, it acts much more like MongoDB Community Edition.
**
** The use of the serverless model means you have to manually add composite keys for just
** about everything. See TestCommon MongoDBContext for how to do this.
*/

resource account 'Microsoft.DocumentDB/databaseAccounts@2022-05-15' = {
name: toLower(serverName)
Expand Down Expand Up @@ -155,7 +82,7 @@ resource collection 'Microsoft.DocumentDb/databaseAccounts/mongodbDatabases/coll
resource: {
id: collectionName
shardKey: {
_id: 'Hash'
rating: 'Hash'
}
indexes: [
{
Expand All @@ -176,6 +103,7 @@ resource collection 'Microsoft.DocumentDb/databaseAccounts/mongodbDatabases/coll
}
}
}

/*********************************************************************************/

#disable-next-line outputs-should-not-contain-secrets
Expand Down
75 changes: 75 additions & 0 deletions infra/modules/cosmos-mongodb-vcore.bicep
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
targetScope = 'resourceGroup'

@secure()
@description('The password for the administrator')
param administratorPassword string

@description('The username for the administrator')
param administratorUsername string = 'tester'

@description('The list of firewall rules to install')
param firewallRules FirewallRule[] = [
{ startIpAddress: '0.0.0.0', endIpAddress: '0.0.0.0' }
]

@minLength(1)
@description('Primary location for all resources')
param location string = resourceGroup().location

@description('The name of the Mongo Server to create.')
param serverName string

@description('The list of tags to apply to all resources.')
param tags object = {}

@description('The tier to use for compute')
@allowed([ 'Free', 'M10', 'M20', 'M25', 'M30', 'M40', 'M50', 'M60', 'M80', 'M200', 'M200-Autoscale'])
param tier string = 'M10'

/*********************************************************************************/

resource cluster 'Microsoft.DocumentDB/mongoClusters@2024-07-01' = {
name: toLower(serverName)
location: location
tags: tags
properties: {
administrator: {
userName: administratorUsername
password: administratorPassword
}
compute: { tier: tier }
highAvailability: {
targetMode: 'Disabled'
}
publicNetworkAccess: 'Enabled'
serverVersion: '7.0'
sharding: {
shardCount: 1
}
storage: { sizeGb: 32 }
}
}

resource mongoFirewallRule 'Microsoft.DocumentDB/mongoClusters/firewallRules@2024-07-01' = [
for (fwRule, index) in firewallRules: {
name: fwRule.?name ?? 'rule-${index}'
parent: cluster
properties: {
startIpAddress: fwRule.startIpAddress
endIpAddress: fwRule.endIpAddress
}
}
]

/*********************************************************************************/

#disable-next-line outputs-should-not-contain-secrets
output MONGO_CONNECTIONSTRING string = replace(replace(cluster.listConnectionStrings().connectionStrings[0].connectionString, '<user>', administratorUsername), '<password>', administratorPassword)

/*********************************************************************************/

type FirewallRule = {
name: string?
startIpAddress: string
endIpAddress: string
}
8 changes: 4 additions & 4 deletions infra/resources.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -105,14 +105,14 @@ module cosmos './modules/cosmos.bicep' = {
}
}

module mongodb './modules/cosmos-mongodb.bicep' = {
module mongodb './modules/cosmos-mongodb-vcore.bicep' = {
name: 'mongo-deployment-${resourceToken}'
params: {
location: location
tags: tags
databaseName: testDatabaseName
containerName: cosmosContainerName
serverName: mongoServerName
administratorPassword: sqlAdminPassword
administratorUsername: sqlAdminUsername
}
}

Expand Down Expand Up @@ -148,7 +148,7 @@ module app_service './modules/appservice.bicep' = {

output AZSQL_CONNECTIONSTRING string = azuresql.outputs.AZSQL_CONNECTIONSTRING
output COSMOS_CONNECTIONSTRING string = cosmos.outputs.COSMOS_CONNECTIONSTRING
output MONGO_CONNECTIONSTRING string = mongodb.outputs.MONGODB_CONNECTIONSTRING
output MONGO_CONNECTIONSTRING string = mongodb.outputs.MONGO_CONNECTIONSTRING
output MONGOACI_CONNECTIONSTRING string = mongoaci.outputs.MONGO_CONNECTIONSTRING
output MYSQL_CONNECTIONSTRING string = mysql.outputs.MYSQL_CONNECTIONSTRING
output PGSQL_CONNECTIONSTRING string = pgsql.outputs.PGSQL_CONNECTIONSTRING
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,20 @@ public async Task PopulateDatabaseAsync()
return;
}

// Create the indices required for all the tests.
//List<CreateIndexModel<MongoDBMovie>> indices = [];
//string[] props = ["BestPictureWinner", "Duration", "Rating", "ReleaseDate", "Title", "Year", "UpdatedAt", "Deleted"];
//foreach (string prop in props)
//{
// indices.AddRange(GetCompoundIndexDefinitions(prop));
//}

//indices.AddRange(GetCompoundIndexDefinitions("UpdatedAt", "Deleted", includeId: false));
//indices.AddRange(GetCompoundIndexDefinitions("Title", "Year"));
//indices.AddRange(GetCompoundIndexDefinitions("Year", "Title"));
//await Movies.Indexes.CreateManyAsync(indices);

// Now populate the database with the test data, after the indices are defined.
foreach (MongoDBMovie movie in TestData.Movies.OfType<MongoDBMovie>())
{
movie.UpdatedAt = DateTimeOffset.UtcNow;
Expand All @@ -67,4 +81,68 @@ public async Task PopulateDatabaseAsync()
await Movies.InsertOneAsync(movie, options);
}
}

private static IEnumerable<CreateIndexModel<MongoDBMovie>> GetCompoundIndexDefinitions(string field)
{
return [
new CreateIndexModel<MongoDBMovie>(Builders<MongoDBMovie>.IndexKeys.Combine(
Builders<MongoDBMovie>.IndexKeys.Ascending(field),
Builders<MongoDBMovie>.IndexKeys.Ascending("_id")
)),
new CreateIndexModel<MongoDBMovie>(Builders<MongoDBMovie>.IndexKeys.Combine(
Builders<MongoDBMovie>.IndexKeys.Descending(field),
Builders<MongoDBMovie>.IndexKeys.Ascending("_id")
))
];
}

private static IEnumerable<CreateIndexModel<MongoDBMovie>> GetCompoundIndexDefinitions(string field1, string field2, bool includeId = true)
{
if (includeId)
{
return [
new CreateIndexModel<MongoDBMovie>(Builders<MongoDBMovie>.IndexKeys.Combine(
Builders<MongoDBMovie>.IndexKeys.Ascending(field1),
Builders<MongoDBMovie>.IndexKeys.Ascending(field2),
Builders<MongoDBMovie>.IndexKeys.Ascending("_id")
)),
new CreateIndexModel<MongoDBMovie>(Builders<MongoDBMovie>.IndexKeys.Combine(
Builders<MongoDBMovie>.IndexKeys.Ascending(field1),
Builders<MongoDBMovie>.IndexKeys.Descending(field2),
Builders<MongoDBMovie>.IndexKeys.Ascending("_id")
)),
new CreateIndexModel<MongoDBMovie>(Builders<MongoDBMovie>.IndexKeys.Combine(
Builders<MongoDBMovie>.IndexKeys.Descending(field1),
Builders<MongoDBMovie>.IndexKeys.Ascending(field2),
Builders<MongoDBMovie>.IndexKeys.Ascending("_id")
)),
new CreateIndexModel<MongoDBMovie>(Builders<MongoDBMovie>.IndexKeys.Combine(
Builders<MongoDBMovie>.IndexKeys.Descending(field1),
Builders<MongoDBMovie>.IndexKeys.Descending(field2),
Builders<MongoDBMovie>.IndexKeys.Ascending("_id")
)),
];
}
else
{
return [
new CreateIndexModel<MongoDBMovie>(Builders<MongoDBMovie>.IndexKeys.Combine(
Builders<MongoDBMovie>.IndexKeys.Ascending(field1),
Builders<MongoDBMovie>.IndexKeys.Ascending(field2)
)),
new CreateIndexModel<MongoDBMovie>(Builders<MongoDBMovie>.IndexKeys.Combine(
Builders<MongoDBMovie>.IndexKeys.Ascending(field1),
Builders<MongoDBMovie>.IndexKeys.Descending(field2)
)),
new CreateIndexModel<MongoDBMovie>(Builders<MongoDBMovie>.IndexKeys.Combine(
Builders<MongoDBMovie>.IndexKeys.Descending(field1),
Builders<MongoDBMovie>.IndexKeys.Ascending(field2)
)),
new CreateIndexModel<MongoDBMovie>(Builders<MongoDBMovie>.IndexKeys.Combine(
Builders<MongoDBMovie>.IndexKeys.Descending(field1),
Builders<MongoDBMovie>.IndexKeys.Descending(field2)
)),
];
}
}
}
36 changes: 33 additions & 3 deletions tests/CommunityToolkit.Datasync.TestCommon/RepositoryTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,28 @@ public async Task AsQueryableAsync_CanRetrieveFilteredLists()
IRepository<TEntity> Repository = await GetPopulatedRepositoryAsync();
int expected = TestData.Movies.Count<TEntity>(m => m.Rating == MovieRating.R);
IQueryable<TEntity> queryable = await Repository.AsQueryableAsync();
IList<TEntity> actual = await Repository.ToListAsync(queryable.Where(m => m.Rating == MovieRating.R));
IQueryable<TEntity> sut = queryable
.Where(m => m.Rating == MovieRating.R);
IList<TEntity> actual = await Repository.ToListAsync(sut);

actual.Should().HaveCount(expected);
}

[SkippableFact]
public async Task AsQueryableAsync_CanRetrieveOrderedLists()
{
Skip.IfNot(CanRunLiveTests());

IRepository<TEntity> Repository = await GetPopulatedRepositoryAsync();
int expected = TestData.Movies.Count<TEntity>();
IQueryable<TEntity> queryable = await Repository.AsQueryableAsync();

// We pick this set of orderings because we create a CosmosDB composite index for these already.
IQueryable<TEntity> sut = queryable
.OrderBy(m => m.ReleaseDate)
.ThenBy(m => m.Id);

IList<TEntity> actual = await Repository.ToListAsync(sut);

actual.Should().HaveCount(expected);
}
Expand All @@ -85,7 +106,11 @@ public async Task AsQueryableAsync_CanUseTopAndSkip()

IRepository<TEntity> Repository = await GetPopulatedRepositoryAsync();
IQueryable<TEntity> queryable = await Repository.AsQueryableAsync();
IList<TEntity> actual = await Repository.ToListAsync(queryable.Where(m => m.Rating == MovieRating.R).Skip(5).Take(20));
IQueryable<TEntity> sut = queryable
.Where(m => m.Rating == MovieRating.R)
.Skip(5)
.Take(20);
IList<TEntity> actual = await Repository.ToListAsync(sut);

actual.Should().HaveCount(20);
}
Expand All @@ -100,7 +125,12 @@ public async Task AsQueryableAsync_CanRetrievePagedDatasyncQuery()

IRepository<TEntity> Repository = await GetPopulatedRepositoryAsync();
IQueryable<TEntity> queryable = await Repository.AsQueryableAsync();
IList<TEntity> actual = await Repository.ToListAsync(queryable.Where(m => m.UpdatedAt > DateTimeOffset.UnixEpoch && !m.Deleted).OrderBy(m => m.UpdatedAt).Skip(10).Take(10));
IQueryable<TEntity> sut = queryable
.Where(m => m.UpdatedAt > DateTimeOffset.UnixEpoch && !m.Deleted)
.OrderBy(m => m.UpdatedAt)
.Skip(10)
.Take(10);
IList<TEntity> actual = await Repository.ToListAsync(sut);

actual.Should().HaveCount(10);
}
Expand Down

0 comments on commit 7af4b16

Please sign in to comment.