Skip to content

Commit 7af4b16

Browse files
author
Adrian Hall
committed
(#245) MongoDB Driver, complete.
1 parent 6ed9c47 commit 7af4b16

File tree

5 files changed

+199
-88
lines changed

5 files changed

+199
-88
lines changed
Lines changed: 9 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -24,86 +24,13 @@ param tags object = {}
2424

2525
/*********************************************************************************/
2626

27-
var compositeIndices = [
28-
[
29-
{ path: '/BestPictureWinner', order: 'ascending' }
30-
{ path: '/id', order: 'ascending' }
31-
]
32-
[
33-
{ path: '/BestPictureWinner', order: 'descending' }
34-
{ path: '/id', order: 'ascending' }
35-
]
36-
[
37-
{ path: '/Duration', order: 'ascending' }
38-
{ path: '/id', order: 'ascending' }
39-
]
40-
[
41-
{ path: '/Duration', order: 'descending' }
42-
{ path: '/id', order: 'ascending' }
43-
]
44-
[
45-
{ path: '/Rating', order: 'ascending' }
46-
{ path: '/id', order: 'ascending' }
47-
]
48-
[
49-
{ path: '/Rating', order: 'descending' }
50-
{ path: '/id', order: 'ascending' }
51-
]
52-
[
53-
{ path: '/ReleaseDate', order: 'ascending' }
54-
{ path: '/id', order: 'ascending' }
55-
]
56-
[
57-
{ path: '/ReleaseDate', order: 'descending' }
58-
{ path: '/id', order: 'ascending' }
59-
]
60-
[
61-
{ path: '/Title', order: 'ascending' }
62-
{ path: '/id', order: 'ascending' }
63-
]
64-
[
65-
{ path: '/Title', order: 'descending' }
66-
{ path: '/id', order: 'ascending' }
67-
]
68-
[
69-
{ path: '/UpdatedAt', order: 'ascending' }
70-
{ path: '/id', order: 'ascending' }
71-
]
72-
[
73-
{ path: '/UpdatedAt', order: 'descending' }
74-
{ path: '/id', order: 'ascending' }
75-
]
76-
[
77-
{ path: '/Year', order: 'ascending' }
78-
{ path: '/id', order: 'ascending' }
79-
]
80-
[
81-
{ path: '/Year', order: 'descending' }
82-
{ path: '/id', order: 'ascending' }
83-
]
84-
[
85-
{ path: '/Year', order: 'ascending' }
86-
{ path: '/Title', order: 'ascending' }
87-
{ path: '/id', order: 'ascending' }
88-
]
89-
[
90-
{ path: '/Year', order: 'descending' }
91-
{ path: '/Title', order: 'ascending' }
92-
{ path: '/id', order: 'ascending' }
93-
]
94-
[
95-
{ path: '/Year', order: 'ascending' }
96-
{ path: '/Title', order: 'descending' }
97-
{ path: '/id', order: 'ascending' }
98-
]
99-
[
100-
{ path: '/Year', order: 'descending' }
101-
{ path: '/Title', order: 'descending' }
102-
{ path: '/id', order: 'ascending' }
103-
]
104-
]
105-
106-
/*********************************************************************************/
27+
/*
28+
** Note that this implements the serverless (RU) model. If you use the dedicated (vCore)
29+
** deployment model, it acts much more like MongoDB Community Edition.
30+
**
31+
** The use of the serverless model means you have to manually add composite keys for just
32+
** about everything. See TestCommon MongoDBContext for how to do this.
33+
*/
10734

10835
resource account 'Microsoft.DocumentDB/databaseAccounts@2022-05-15' = {
10936
name: toLower(serverName)
@@ -155,7 +82,7 @@ resource collection 'Microsoft.DocumentDb/databaseAccounts/mongodbDatabases/coll
15582
resource: {
15683
id: collectionName
15784
shardKey: {
158-
_id: 'Hash'
85+
rating: 'Hash'
15986
}
16087
indexes: [
16188
{
@@ -176,6 +103,7 @@ resource collection 'Microsoft.DocumentDb/databaseAccounts/mongodbDatabases/coll
176103
}
177104
}
178105
}
106+
179107
/*********************************************************************************/
180108

181109
#disable-next-line outputs-should-not-contain-secrets
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
targetScope = 'resourceGroup'
2+
3+
@secure()
4+
@description('The password for the administrator')
5+
param administratorPassword string
6+
7+
@description('The username for the administrator')
8+
param administratorUsername string = 'tester'
9+
10+
@description('The list of firewall rules to install')
11+
param firewallRules FirewallRule[] = [
12+
{ startIpAddress: '0.0.0.0', endIpAddress: '0.0.0.0' }
13+
]
14+
15+
@minLength(1)
16+
@description('Primary location for all resources')
17+
param location string = resourceGroup().location
18+
19+
@description('The name of the Mongo Server to create.')
20+
param serverName string
21+
22+
@description('The list of tags to apply to all resources.')
23+
param tags object = {}
24+
25+
@description('The tier to use for compute')
26+
@allowed([ 'Free', 'M10', 'M20', 'M25', 'M30', 'M40', 'M50', 'M60', 'M80', 'M200', 'M200-Autoscale'])
27+
param tier string = 'M10'
28+
29+
/*********************************************************************************/
30+
31+
resource cluster 'Microsoft.DocumentDB/mongoClusters@2024-07-01' = {
32+
name: toLower(serverName)
33+
location: location
34+
tags: tags
35+
properties: {
36+
administrator: {
37+
userName: administratorUsername
38+
password: administratorPassword
39+
}
40+
compute: { tier: tier }
41+
highAvailability: {
42+
targetMode: 'Disabled'
43+
}
44+
publicNetworkAccess: 'Enabled'
45+
serverVersion: '7.0'
46+
sharding: {
47+
shardCount: 1
48+
}
49+
storage: { sizeGb: 32 }
50+
}
51+
}
52+
53+
resource mongoFirewallRule 'Microsoft.DocumentDB/mongoClusters/firewallRules@2024-07-01' = [
54+
for (fwRule, index) in firewallRules: {
55+
name: fwRule.?name ?? 'rule-${index}'
56+
parent: cluster
57+
properties: {
58+
startIpAddress: fwRule.startIpAddress
59+
endIpAddress: fwRule.endIpAddress
60+
}
61+
}
62+
]
63+
64+
/*********************************************************************************/
65+
66+
#disable-next-line outputs-should-not-contain-secrets
67+
output MONGO_CONNECTIONSTRING string = replace(replace(cluster.listConnectionStrings().connectionStrings[0].connectionString, '<user>', administratorUsername), '<password>', administratorPassword)
68+
69+
/*********************************************************************************/
70+
71+
type FirewallRule = {
72+
name: string?
73+
startIpAddress: string
74+
endIpAddress: string
75+
}

infra/resources.bicep

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -105,14 +105,14 @@ module cosmos './modules/cosmos.bicep' = {
105105
}
106106
}
107107

108-
module mongodb './modules/cosmos-mongodb.bicep' = {
108+
module mongodb './modules/cosmos-mongodb-vcore.bicep' = {
109109
name: 'mongo-deployment-${resourceToken}'
110110
params: {
111111
location: location
112112
tags: tags
113-
databaseName: testDatabaseName
114-
containerName: cosmosContainerName
115113
serverName: mongoServerName
114+
administratorPassword: sqlAdminPassword
115+
administratorUsername: sqlAdminUsername
116116
}
117117
}
118118

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

149149
output AZSQL_CONNECTIONSTRING string = azuresql.outputs.AZSQL_CONNECTIONSTRING
150150
output COSMOS_CONNECTIONSTRING string = cosmos.outputs.COSMOS_CONNECTIONSTRING
151-
output MONGO_CONNECTIONSTRING string = mongodb.outputs.MONGODB_CONNECTIONSTRING
151+
output MONGO_CONNECTIONSTRING string = mongodb.outputs.MONGO_CONNECTIONSTRING
152152
output MONGOACI_CONNECTIONSTRING string = mongoaci.outputs.MONGO_CONNECTIONSTRING
153153
output MYSQL_CONNECTIONSTRING string = mysql.outputs.MYSQL_CONNECTIONSTRING
154154
output PGSQL_CONNECTIONSTRING string = pgsql.outputs.PGSQL_CONNECTIONSTRING

tests/CommunityToolkit.Datasync.TestCommon/Databases/MongoDB/MongoDBContext.cs

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,20 @@ public async Task PopulateDatabaseAsync()
5959
return;
6060
}
6161

62+
// Create the indices required for all the tests.
63+
//List<CreateIndexModel<MongoDBMovie>> indices = [];
64+
//string[] props = ["BestPictureWinner", "Duration", "Rating", "ReleaseDate", "Title", "Year", "UpdatedAt", "Deleted"];
65+
//foreach (string prop in props)
66+
//{
67+
// indices.AddRange(GetCompoundIndexDefinitions(prop));
68+
//}
69+
70+
//indices.AddRange(GetCompoundIndexDefinitions("UpdatedAt", "Deleted", includeId: false));
71+
//indices.AddRange(GetCompoundIndexDefinitions("Title", "Year"));
72+
//indices.AddRange(GetCompoundIndexDefinitions("Year", "Title"));
73+
//await Movies.Indexes.CreateManyAsync(indices);
74+
75+
// Now populate the database with the test data, after the indices are defined.
6276
foreach (MongoDBMovie movie in TestData.Movies.OfType<MongoDBMovie>())
6377
{
6478
movie.UpdatedAt = DateTimeOffset.UtcNow;
@@ -67,4 +81,68 @@ public async Task PopulateDatabaseAsync()
6781
await Movies.InsertOneAsync(movie, options);
6882
}
6983
}
84+
85+
private static IEnumerable<CreateIndexModel<MongoDBMovie>> GetCompoundIndexDefinitions(string field)
86+
{
87+
return [
88+
new CreateIndexModel<MongoDBMovie>(Builders<MongoDBMovie>.IndexKeys.Combine(
89+
Builders<MongoDBMovie>.IndexKeys.Ascending(field),
90+
Builders<MongoDBMovie>.IndexKeys.Ascending("_id")
91+
)),
92+
new CreateIndexModel<MongoDBMovie>(Builders<MongoDBMovie>.IndexKeys.Combine(
93+
Builders<MongoDBMovie>.IndexKeys.Descending(field),
94+
Builders<MongoDBMovie>.IndexKeys.Ascending("_id")
95+
))
96+
];
97+
}
98+
99+
private static IEnumerable<CreateIndexModel<MongoDBMovie>> GetCompoundIndexDefinitions(string field1, string field2, bool includeId = true)
100+
{
101+
if (includeId)
102+
{
103+
return [
104+
new CreateIndexModel<MongoDBMovie>(Builders<MongoDBMovie>.IndexKeys.Combine(
105+
Builders<MongoDBMovie>.IndexKeys.Ascending(field1),
106+
Builders<MongoDBMovie>.IndexKeys.Ascending(field2),
107+
Builders<MongoDBMovie>.IndexKeys.Ascending("_id")
108+
)),
109+
new CreateIndexModel<MongoDBMovie>(Builders<MongoDBMovie>.IndexKeys.Combine(
110+
Builders<MongoDBMovie>.IndexKeys.Ascending(field1),
111+
Builders<MongoDBMovie>.IndexKeys.Descending(field2),
112+
Builders<MongoDBMovie>.IndexKeys.Ascending("_id")
113+
)),
114+
new CreateIndexModel<MongoDBMovie>(Builders<MongoDBMovie>.IndexKeys.Combine(
115+
Builders<MongoDBMovie>.IndexKeys.Descending(field1),
116+
Builders<MongoDBMovie>.IndexKeys.Ascending(field2),
117+
Builders<MongoDBMovie>.IndexKeys.Ascending("_id")
118+
)),
119+
new CreateIndexModel<MongoDBMovie>(Builders<MongoDBMovie>.IndexKeys.Combine(
120+
Builders<MongoDBMovie>.IndexKeys.Descending(field1),
121+
Builders<MongoDBMovie>.IndexKeys.Descending(field2),
122+
Builders<MongoDBMovie>.IndexKeys.Ascending("_id")
123+
)),
124+
];
125+
}
126+
else
127+
{
128+
return [
129+
new CreateIndexModel<MongoDBMovie>(Builders<MongoDBMovie>.IndexKeys.Combine(
130+
Builders<MongoDBMovie>.IndexKeys.Ascending(field1),
131+
Builders<MongoDBMovie>.IndexKeys.Ascending(field2)
132+
)),
133+
new CreateIndexModel<MongoDBMovie>(Builders<MongoDBMovie>.IndexKeys.Combine(
134+
Builders<MongoDBMovie>.IndexKeys.Ascending(field1),
135+
Builders<MongoDBMovie>.IndexKeys.Descending(field2)
136+
)),
137+
new CreateIndexModel<MongoDBMovie>(Builders<MongoDBMovie>.IndexKeys.Combine(
138+
Builders<MongoDBMovie>.IndexKeys.Descending(field1),
139+
Builders<MongoDBMovie>.IndexKeys.Ascending(field2)
140+
)),
141+
new CreateIndexModel<MongoDBMovie>(Builders<MongoDBMovie>.IndexKeys.Combine(
142+
Builders<MongoDBMovie>.IndexKeys.Descending(field1),
143+
Builders<MongoDBMovie>.IndexKeys.Descending(field2)
144+
)),
145+
];
146+
}
147+
}
70148
}

tests/CommunityToolkit.Datasync.TestCommon/RepositoryTests.cs

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,28 @@ public async Task AsQueryableAsync_CanRetrieveFilteredLists()
7373
IRepository<TEntity> Repository = await GetPopulatedRepositoryAsync();
7474
int expected = TestData.Movies.Count<TEntity>(m => m.Rating == MovieRating.R);
7575
IQueryable<TEntity> queryable = await Repository.AsQueryableAsync();
76-
IList<TEntity> actual = await Repository.ToListAsync(queryable.Where(m => m.Rating == MovieRating.R));
76+
IQueryable<TEntity> sut = queryable
77+
.Where(m => m.Rating == MovieRating.R);
78+
IList<TEntity> actual = await Repository.ToListAsync(sut);
79+
80+
actual.Should().HaveCount(expected);
81+
}
82+
83+
[SkippableFact]
84+
public async Task AsQueryableAsync_CanRetrieveOrderedLists()
85+
{
86+
Skip.IfNot(CanRunLiveTests());
87+
88+
IRepository<TEntity> Repository = await GetPopulatedRepositoryAsync();
89+
int expected = TestData.Movies.Count<TEntity>();
90+
IQueryable<TEntity> queryable = await Repository.AsQueryableAsync();
91+
92+
// We pick this set of orderings because we create a CosmosDB composite index for these already.
93+
IQueryable<TEntity> sut = queryable
94+
.OrderBy(m => m.ReleaseDate)
95+
.ThenBy(m => m.Id);
96+
97+
IList<TEntity> actual = await Repository.ToListAsync(sut);
7798

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

86107
IRepository<TEntity> Repository = await GetPopulatedRepositoryAsync();
87108
IQueryable<TEntity> queryable = await Repository.AsQueryableAsync();
88-
IList<TEntity> actual = await Repository.ToListAsync(queryable.Where(m => m.Rating == MovieRating.R).Skip(5).Take(20));
109+
IQueryable<TEntity> sut = queryable
110+
.Where(m => m.Rating == MovieRating.R)
111+
.Skip(5)
112+
.Take(20);
113+
IList<TEntity> actual = await Repository.ToListAsync(sut);
89114

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

101126
IRepository<TEntity> Repository = await GetPopulatedRepositoryAsync();
102127
IQueryable<TEntity> queryable = await Repository.AsQueryableAsync();
103-
IList<TEntity> actual = await Repository.ToListAsync(queryable.Where(m => m.UpdatedAt > DateTimeOffset.UnixEpoch && !m.Deleted).OrderBy(m => m.UpdatedAt).Skip(10).Take(10));
128+
IQueryable<TEntity> sut = queryable
129+
.Where(m => m.UpdatedAt > DateTimeOffset.UnixEpoch && !m.Deleted)
130+
.OrderBy(m => m.UpdatedAt)
131+
.Skip(10)
132+
.Take(10);
133+
IList<TEntity> actual = await Repository.ToListAsync(sut);
104134

105135
actual.Should().HaveCount(10);
106136
}

0 commit comments

Comments
 (0)