Skip to content

Commit e823df1

Browse files
#150 - Supports split database field override
1 parent 318ee30 commit e823df1

14 files changed

+124
-17
lines changed

README.md

+2
Original file line numberDiff line numberDiff line change
@@ -536,6 +536,8 @@ public class MyDeathStarContext : CouchContext
536536
> When multiple `CouchDatabase` point to the same **database**, a `split_discriminator` field is added on document creation.
537537
>
538538
> When querying, a filter by `split_discriminator` is added automatically.
539+
>
540+
> The field name can be overriden with the `WithDatabaseSplitDiscriminator`.
539541

540542
If you are not using `CouchContext`, you can still use the database split feature:
541543
```csharp

src/CouchDB.Driver/CouchClient.cs

+2-1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ namespace CouchDB.Driver
2323
/// </summary>
2424
public partial class CouchClient : ICouchClient
2525
{
26+
public const string DefaultDatabaseSplitDiscriminator = "split_discriminator";
2627
private DateTime? _cookieCreationDate;
2728
private string? _cookieToken;
2829

@@ -91,7 +92,7 @@ private IFlurlClient GetConfiguredClient() =>
9192
{
9293
s.JsonSerializer = new NewtonsoftJsonSerializer(new JsonSerializerSettings
9394
{
94-
ContractResolver = new CouchContractResolver(_options.PropertiesCase),
95+
ContractResolver = new CouchContractResolver(_options.PropertiesCase, _options.DatabaseSplitDiscriminator),
9596
NullValueHandling = _options.NullValueHandling ?? NullValueHandling.Include
9697
});
9798
s.BeforeCallAsync = OnBeforeCallAsync;

src/CouchDB.Driver/Helpers/CouchContractResolver.cs

+11-5
Original file line numberDiff line numberDiff line change
@@ -9,26 +9,32 @@ namespace CouchDB.Driver.Helpers
99
public class CouchContractResolver : DefaultContractResolver
1010
{
1111
private readonly PropertyCaseType _propertyCaseType;
12+
private readonly string? _databaseSplitDiscriminator;
1213

13-
internal CouchContractResolver(PropertyCaseType propertyCaseType)
14+
internal CouchContractResolver(PropertyCaseType propertyCaseType, string? databaseSplitDiscriminator)
1415
{
1516
_propertyCaseType = propertyCaseType;
17+
_databaseSplitDiscriminator = databaseSplitDiscriminator;
1618
}
1719

1820
protected override JsonProperty? CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
1921
{
2022
Check.NotNull(member, nameof(member));
2123

2224
JsonProperty property = base.CreateProperty(member, memberSerialization);
23-
if (property != null && !property.Ignored)
25+
if (property is { Ignored: false })
2426
{
25-
var declaringNamespace = member.DeclaringType?.Namespace;
26-
if (declaringNamespace != null && !declaringNamespace.Contains("CouchDB.Driver"))
27+
if (member.DeclaringType!.Assembly != GetType().Assembly)
2728
{
2829
property.PropertyName = member.GetCouchPropertyName(_propertyCaseType);
2930
}
3031
}
32+
33+
if (property.PropertyName == CouchClient.DefaultDatabaseSplitDiscriminator && !string.IsNullOrWhiteSpace(_databaseSplitDiscriminator))
34+
{
35+
property.PropertyName = _databaseSplitDiscriminator;
36+
}
3137
return property;
3238
}
3339
}
34-
}
40+
}

src/CouchDB.Driver/Options/CouchOptions.cs

+2
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ public abstract class CouchOptions
2727
internal bool PluralizeEntities { get; set; }
2828
internal DocumentCaseType DocumentsCaseType { get; set; }
2929
internal PropertyCaseType PropertiesCase { get; set; }
30+
31+
internal string? DatabaseSplitDiscriminator { get; set; }
3032
internal NullValueHandling? NullValueHandling { get; set; }
3133
internal bool LogOutOnDispose { get; set; }
3234

src/CouchDB.Driver/Options/CouchOptionsBuilder.cs

+11
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,17 @@ public virtual CouchOptionsBuilder SetPropertyCase(PropertyCaseType type)
193193
return this;
194194
}
195195

196+
/// <summary>
197+
/// Set the field to use to identify document types. Default: <c>split_discriminator</c>.
198+
/// </summary>
199+
/// <param name="databaseSplitDiscriminator">The document field to use as discriminator.</param>
200+
/// <returns>Return the current instance to chain calls.</returns>
201+
public virtual CouchOptionsBuilder WithDatabaseSplitDiscriminator(string databaseSplitDiscriminator)
202+
{
203+
Options.DatabaseSplitDiscriminator = databaseSplitDiscriminator;
204+
return this;
205+
}
206+
196207
/// <summary>
197208
/// Sets how to handle null values during serialization.
198209
/// </summary>

src/CouchDB.Driver/Options/CouchOptionsBuilder`.cs

+8
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,14 @@ public CouchOptionsBuilder(CouchOptions<TContext> options) : base(options) { }
129129
/// <returns>Return the current instance to chain calls.</returns>
130130
public new virtual CouchOptionsBuilder<TContext> SetPropertyCase(PropertyCaseType type)
131131
=> (CouchOptionsBuilder<TContext>)base.SetPropertyCase(type);
132+
133+
/// <summary>
134+
/// Set the field to use to identify document types. Default: <c>split_discriminator</c>.
135+
/// </summary>
136+
/// <param name="databaseSplitDiscriminator">The document field to use as discriminator.</param>
137+
/// <returns>Return the current instance to chain calls.</returns>
138+
public new virtual CouchOptionsBuilder<TContext> WithDatabaseSplitDiscriminator(string databaseSplitDiscriminator)
139+
=> (CouchOptionsBuilder<TContext>)base.WithDatabaseSplitDiscriminator(databaseSplitDiscriminator);
132140

133141
/// <summary>
134142
/// Sets how to handle null values during serialization.

src/CouchDB.Driver/Query/QueryOptimizer.cs

+3-3
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ namespace CouchDB.Driver.Query
1616
/// </summary>
1717
internal class QueryOptimizer : ExpressionVisitor, IQueryOptimizer
1818
{
19-
private static readonly MethodInfo WrapInWhereGenericMethod
19+
private static readonly MethodInfo WrapInDiscriminatorFilterGenericMethod
2020
= typeof(MethodCallExpressionBuilder).GetMethod(nameof(MethodCallExpressionBuilder.WrapInDiscriminatorFilter));
2121
private bool _isVisitingWhereMethodOrChild;
2222
private readonly Queue<MethodCallExpression> _nextWhereCalls;
@@ -33,13 +33,13 @@ public Expression Optimize(Expression e, string? discriminator)
3333
if (e.Type.IsGenericType)
3434
{
3535
Type? sourceType = e.Type.GetGenericArguments()[0];
36-
MethodInfo wrapInWhere = WrapInWhereGenericMethod.MakeGenericMethod(sourceType);
36+
MethodInfo wrapInWhere = WrapInDiscriminatorFilterGenericMethod.MakeGenericMethod(sourceType);
3737
e = (Expression)wrapInWhere.Invoke(null, new object[] { e, discriminator });
3838
}
3939
else
4040
{
4141
Type sourceType = e.Type;
42-
MethodInfo wrapInWhere = WrapInWhereGenericMethod.MakeGenericMethod(sourceType);
42+
MethodInfo wrapInWhere = WrapInDiscriminatorFilterGenericMethod.MakeGenericMethod(sourceType);
4343

4444
var rootMethodCallExpression = e as MethodCallExpression;
4545
Expression source = rootMethodCallExpression!.Arguments[0];

src/CouchDB.Driver/Query/Translators/MemberExpressionTranslator.cs

+10-1
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,23 @@
11
using System.Linq.Expressions;
22
using CouchDB.Driver.Extensions;
3+
using CouchDB.Driver.Types;
34

45
namespace CouchDB.Driver.Query
56
{
67
internal partial class QueryTranslator
78
{
89
protected override Expression VisitMember(MemberExpression m)
910
{
11+
// Override database split if needed
12+
if (m.Member.DeclaringType == typeof(CouchDocument) &&
13+
m.Member.Name == nameof(CouchDocument.SplitDiscriminator) &&
14+
!string.IsNullOrWhiteSpace(_options.DatabaseSplitDiscriminator))
15+
{
16+
_sb.Append($"\"{_options.DatabaseSplitDiscriminator}\"");
17+
return m;
18+
}
19+
1020
var propName = m.GetPropertyName(_options);
11-
1221
_sb.Append($"\"{propName}\"");
1322
return m;
1423
}

src/CouchDB.Driver/Types/CouchDocument.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ protected CouchDocument()
111111
/// Used for database splitting
112112
/// </summary>
113113
[DataMember]
114-
[JsonProperty("split_discriminator", NullValueHandling = NullValueHandling.Ignore)]
114+
[JsonProperty(CouchClient.DefaultDatabaseSplitDiscriminator, NullValueHandling = NullValueHandling.Ignore)]
115115
internal string SplitDiscriminator { get; set; }
116116

117117
[OnDeserialized]

tests/CouchDB.Driver.E2ETests/Client_Tests.cs

+1-2
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,9 @@
55
using System.Threading;
66
using System.Threading.Tasks;
77
using CouchDB.Driver.E2ETests;
8-
using CouchDB.Driver.E2ETests._Models;
8+
using CouchDB.Driver.E2ETests.Models;
99
using CouchDB.Driver.Extensions;
1010
using CouchDB.Driver.Local;
11-
using E2ETests.Models;
1211
using Xunit;
1312

1413
namespace CouchDB.Driver.E2E

tests/CouchDB.Driver.E2ETests/MyDeathStarContext.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
using CouchDB.Driver.Options;
2-
using E2ETests.Models;
1+
using CouchDB.Driver.E2ETests.Models;
2+
using CouchDB.Driver.Options;
33

44
namespace CouchDB.Driver.E2ETests
55
{

tests/CouchDB.Driver.E2ETests/_Models/Rebel.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
using System.Collections.Generic;
22
using CouchDB.Driver.Types;
33

4-
namespace E2ETests.Models
4+
namespace CouchDB.Driver.E2ETests.Models
55
{
66
public class Rebel : CouchDocument
77
{

tests/CouchDB.Driver.E2ETests/_Models/RebelSettings.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
using CouchDB.Driver.Types;
22

3-
namespace CouchDB.Driver.E2ETests._Models
3+
namespace CouchDB.Driver.E2ETests.Models
44
{
55
public class RebelSettings: CouchDocument
66
{

tests/CouchDB.Driver.UnitTests/CouchDbContext_Tests.cs

+69
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,31 @@ protected override void OnDatabaseCreating(CouchDatabaseBuilder databaseBuilder)
3535
.ToDatabase("shared-rebels");
3636
}
3737
}
38+
39+
private class MyDeathStarContextCustomSplit: CouchContext
40+
{
41+
public CouchDatabase<OtherRebel> OtherRebels { get; set; }
42+
public CouchDatabase<SimpleRebel> SimpleRebels { get; set; }
43+
44+
protected override void OnConfiguring(CouchOptionsBuilder optionsBuilder)
45+
{
46+
optionsBuilder
47+
.UseEndpoint("http://localhost:5984/")
48+
.UseBasicAuthentication("admin", "admin")
49+
.WithDatabaseSplitDiscriminator("docType");
50+
}
51+
52+
protected override void OnDatabaseCreating(CouchDatabaseBuilder databaseBuilder)
53+
{
54+
databaseBuilder
55+
.Document<OtherRebel>()
56+
.ToDatabase("shared-rebels");
57+
58+
databaseBuilder
59+
.Document<SimpleRebel>()
60+
.ToDatabase("shared-rebels");
61+
}
62+
}
3863

3964
[Fact]
4065
public async Task Context_Query()
@@ -111,6 +136,50 @@ await context.OtherRebels.AddAsync(new OtherRebel
111136
Assert.Equal(@"{""selector"":{""split_discriminator"":""OtherRebel""}}", httpTest.CallLog[2].RequestBody);
112137
}
113138

139+
[Fact]
140+
public async Task Context_Query_Discriminator_Override()
141+
{
142+
using var httpTest = new HttpTest();
143+
httpTest.RespondWithJson(new
144+
{
145+
Id = "176694",
146+
Ok = true,
147+
Rev = "1-54f8e950cc338d2385d9b0cda2fd918e"
148+
});
149+
httpTest.RespondWithJson(new
150+
{
151+
Id = "173694",
152+
Ok = true,
153+
Rev = "1-54f8e950cc338d2385d9b0cda2fd918e"
154+
});
155+
httpTest.RespondWithJson(new
156+
{
157+
docs = new object[] {
158+
new {
159+
Id = "176694",
160+
Rev = "1-54f8e950cc338d2385d9b0cda2fd918e",
161+
Name = "Luke"
162+
}
163+
}
164+
});
165+
166+
await using var context = new MyDeathStarContextCustomSplit();
167+
await context.SimpleRebels.AddAsync(new SimpleRebel
168+
{
169+
Name = "Leia"
170+
});
171+
await context.OtherRebels.AddAsync(new OtherRebel
172+
{
173+
Name = "Luke"
174+
});
175+
var result = await context.OtherRebels.ToListAsync();
176+
Assert.NotEmpty(result);
177+
Assert.Equal("Luke", result[0].Name);
178+
Assert.Equal(@"{""name"":""Leia"",""age"":0,""docType"":""SimpleRebel"",""_attachments"":{}}", httpTest.CallLog[0].RequestBody);
179+
Assert.Equal(@"{""rebel_bith_date"":""0001-01-01T00:00:00"",""name"":""Luke"",""age"":0,""isJedi"":false,""species"":0,""guid"":""00000000-0000-0000-0000-000000000000"",""docType"":""OtherRebel"",""_attachments"":{}}", httpTest.CallLog[1].RequestBody);
180+
Assert.Equal(@"{""selector"":{""docType"":""OtherRebel""}}", httpTest.CallLog[2].RequestBody);
181+
}
182+
114183
[Fact]
115184
public async Task Context_Query_MultiThread()
116185
{

0 commit comments

Comments
 (0)