Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Test to support custom field sources. #2146

Draft
wants to merge 8 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
175 changes: 175 additions & 0 deletions src/DocumentDbTests/CustomFields/CustomId.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
#nullable enable
using System;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using Baseline;
using Marten;
using Marten.Linq.Fields;
using Marten.Linq.Filters;
using Marten.Util;
using Newtonsoft.Json;
using Weasel.Postgresql.SqlGeneration;

namespace DocumentDbTests.CustomFields
{
public readonly partial struct CustomId
{
public CustomId(string value)
{
Value = value ?? throw new ArgumentNullException(nameof(value));
}

public string Value { get; }
}

public class CustomIdField: FieldBase
{
public CustomIdField(string dataLocator, Casing casing, MemberInfo[] members): base(dataLocator, "varchar",
casing, members)
{
}

public override ISqlFragment CreateComparison(string op, ConstantExpression value,
Expression memberExpression)
{
if (value.Value == null)
{
return op == "=" ? new IsNullFilter(this) : new IsNotNullFilter(this);
}

var def = new CommandParameter(((CustomId)value.Value).Value);
return new ComparisonFilter(this, def, op);
}

public override string SelectorForDuplication(string pgType)
{
return RawLocator.Replace("d.", "");
}
}

public class CustomIdFieldSource: IFieldSource
{
public bool TryResolve(string dataLocator, StoreOptions options, ISerializer serializer,
Type documentType,
MemberInfo[] members, out IField? field)
{
var fieldType = members.Last().GetRawMemberType();

if (fieldType == null)
{
field = null;
return false;
}

if (fieldType == typeof(CustomId))
{
field = new CustomIdField(dataLocator, serializer.Casing, members);
return true;
}

if (fieldType.IsNullable() &&
fieldType.GetGenericArguments()[0] == typeof(CustomId))
{
field = new NullableTypeField(new CustomIdField(dataLocator, serializer.Casing, members));
return true;
}

field = null;
return false;
}
}

#region Boilerplate

/// <summary>
/// This is all boilerplate that can be abstracted away using a library like StronglyTypedId.
/// Code is based on what the StronglyTypedId would generate for a struct with a string backing field.
/// Value is stored as a string in postgres.
/// For more info, see: https://github.com/andrewlock/StronglyTypedId
/// </summary>
[JsonConverter(typeof(CustomIdNewtonsoftJsonConverter))]
public readonly partial struct CustomId: IComparable<CustomId>, IEquatable<CustomId>
{
public bool Equals(CustomId other)
{
return (Value, other.Value) switch
{
(null, null) => true,
(null, _) => false,
(_, null) => false,
(_, _) => Value.Equals(other.Value)
};
}

public override bool Equals(object? obj)
{
if (obj is null)
{
return false;
}

return obj is CustomId other && Equals(other);
}

public override int GetHashCode()
{
return Value.GetHashCode();
}

public override string ToString()
{
return Value;
}

public static bool operator ==(CustomId a, CustomId b)
{
return a.Equals(b);
}

public static bool operator !=(CustomId a, CustomId b)
{
return !(a == b);
}

public int CompareTo(CustomId other)
{
return (Value, other.Value) switch
{
(null, null) => 0,
(null, _) => -1,
(_, null) => 1,
(_, _) => string.Compare(Value, other.Value, StringComparison.Ordinal)
};
}

private class CustomIdNewtonsoftJsonConverter: JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(CustomId);
}

public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
{
if (value is null)
{
serializer.Serialize(writer, null);
return;
}

var id = (CustomId)value;
serializer.Serialize(writer, id.Value);
}

public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue,
JsonSerializer serializer)
{
var deserialized = serializer.Deserialize<string>(reader);
return deserialized is null ? null : new CustomId(deserialized);
}
}
}

#endregion
}
176 changes: 176 additions & 0 deletions src/DocumentDbTests/CustomFields/custom_field_source_array_queries.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
#nullable enable
using System.Linq;
using Marten;
using Marten.Linq;
using Marten.Testing.Harness;
using Weasel.Postgresql;
using Xunit;
using Xunit.Abstractions;

namespace DocumentDbTests.CustomFields
{
public class custom_field_source_array_queries: IntegrationContext
{
private readonly ITestOutputHelper _output;

public custom_field_source_array_queries(DefaultStoreFixture fixture, ITestOutputHelper output):
base(fixture)
{
_output = output;

PostgresqlProvider.Instance.RegisterMapping(typeof(CustomId), "varchar",
PostgresqlProvider.Instance.StringParameterType);
StoreOptions(_ =>
{
_.Linq.FieldSources.Add(new CustomIdFieldSource());
});
}

[Fact]
public void can_query_array_any()
{
var queryPlan = theSession
.Query<MyClassArray>()
.Where(x => x.CustomIds.Any()).Explain();

WriteQueryPlan(queryPlan);
}

[Fact]
public void can_query_array_length_equals()
{
var queryPlan = theSession
.Query<MyClassArray>()
.Where(x => x.CustomIds.Length == 1).Explain();

WriteQueryPlan(queryPlan);
}

[Fact]
public void can_query_array_count_equals()
{
var queryPlan = theSession
.Query<MyClassArray>()
.Where(x => x.CustomIds.Count() == 1).Explain();

WriteQueryPlan(queryPlan);
}

[Fact]
public void can_query_array_contains()
{
var testValue = new CustomId("test");
var queryPlan = theSession
.Query<MyClassArray>()
.Where(x => x.CustomIds.Contains(testValue)).Explain();

WriteQueryPlan(queryPlan);
}


[Fact]
public void can_query_array_not_contains()
{
var testValue = new CustomId("test");
var queryPlan = theSession
.Query<MyClassArray>()
.Where(x => !x.CustomIds.Contains(testValue)).Explain();

WriteQueryPlan(queryPlan);
}

[Fact]
public void can_query_array_any_with_predicate()
{
var testValue = new CustomId("test");
var queryPlan = theSession
.Query<MyClassArray>()
.Where(x => x.CustomIds.Any(_ => _ == testValue)).Explain();

WriteQueryPlan(queryPlan);
}

[Fact]
public void can_query_array_not_any()
{
var testValue = new CustomId("test");
var queryPlan = theSession
.Query<MyClassArray>()
// ReSharper disable once SimplifyLinqExpressionUseAll
.Where(x => !x.CustomIds.Any(_ => _ == testValue)).Explain();

WriteQueryPlan(queryPlan);
}

[Fact(Skip = "All is not supported")]
public void can_query_array_all_not()
{
var testValue = new CustomId("test");
var queryPlan = theSession
.Query<MyClassArray>()
.Where(x => x.CustomIds.All(_ => _ != testValue)).Explain();

WriteQueryPlan(queryPlan);
}


[Fact(Skip = "All is not supported")]
public void can_query_array_all_equal()
{
var testValue = new CustomId("test");
var queryPlan = theSession
.Query<MyClassArray>()
.Where(x => x.CustomIds.All(_ => _ == testValue)).Explain();

WriteQueryPlan(queryPlan);
}

[Fact]
public void can_query_array_is_one_of()
{
var testValues = new[] {new CustomId("test1"), new CustomId("test2")};
var queryPlan = theSession
.Query<MyClassArray>()
.Where(x => x.CustomIds.IsOneOf(testValues)).Explain();

WriteQueryPlan(queryPlan);
}


[Fact]
public void can_query_array_is_superset_of()
{
var testValues = new[] {new CustomId("test1"), new CustomId("test2")};
var queryPlan = theSession
.Query<MyClassArray>()
.Where(x => x.CustomIds.IsSubsetOf(testValues)).Explain();

WriteQueryPlan(queryPlan);
}


[Fact]
public void can_query_array_is_subset_of()
{
var testValues = new[] {new CustomId("test1"), new CustomId("test2")};
var queryPlan = theSession
.Query<MyClassArray>()
.Where(x => x.CustomIds.IsSubsetOf(testValues)).Explain();

WriteQueryPlan(queryPlan);
}

private void WriteQueryPlan(QueryPlan queryPlan)
{
_output.WriteLine(queryPlan.Command.CommandText);
foreach (var parameter in queryPlan.Command.Parameters.Where(p => p is not null))
_output.WriteLine("{1} {0}: {2}", parameter.ParameterName, parameter.DbType, parameter.NpgsqlValue);
}

public class MyClassArray
{
public string Id { get; set; }
public CustomId[] CustomIds { get; set; }
}
}
}
Loading