From 99052521b72021da905b67e6c2bc85626051642f Mon Sep 17 00:00:00 2001 From: PascalSenn Date: Sat, 21 Nov 2020 15:58:34 +0100 Subject: [PATCH] Added support for spatial projections. (#2567) --- ...ions.cs => ProjectionProviderExtension.cs} | 12 +-- .../ProjectionConventionExtensionsTests.cs | 2 +- .../Spatial/HotChocolate.Spatial.sln | 15 +++ ...QueryableSpatialProjectionScalarHandler.cs | 15 +++ ...erProviderDescriptorQueryableExtensions.cs | 9 ++ ...ectionsRequestExecutorBuilderExtensions.cs | 22 +++++ ...atialProjectionsSchemaBuilderExtensions.cs | 22 +++++ .../DatabaseContext.cs | 38 ++++++++ .../Extensions/TestExtensions.cs | 25 +++++ .../Extensions/ToQueryStringExtensions.cs | 51 ++++++++++ .../FilterVisitorTestBase.cs | 94 +++++++++++++++++++ ...Projections.Spatial.SqlServer.Tests.csproj | 26 +++++ .../PostgisConfig.cs | 17 ++++ .../QueryableFilterVisitorTests.cs | 74 +++++++++++++++ .../SchemaCache.cs | 31 ++++++ ...jectionVisitorTests.Create_Expression.snap | 60 ++++++++++++ ...onVisitorTests.Create_Expression__sql.snap | 2 + 17 files changed, 508 insertions(+), 7 deletions(-) rename src/HotChocolate/Data/src/Data/Projections/Convention/{ProjectionProviderExtensions.cs => ProjectionProviderExtension.cs} (85%) create mode 100644 src/HotChocolate/Spatial/src/Data/Projections/Extensions/Extensions/QueryableSpatialProjectionScalarHandler.cs create mode 100644 src/HotChocolate/Spatial/src/Data/Projections/Extensions/Extensions/SpatialFilterProviderDescriptorQueryableExtensions.cs create mode 100644 src/HotChocolate/Spatial/src/Data/Projections/Extensions/SpatialProjectionsRequestExecutorBuilderExtensions.cs create mode 100644 src/HotChocolate/Spatial/src/Data/Projections/Extensions/SpatialProjectionsSchemaBuilderExtensions.cs create mode 100644 src/HotChocolate/Spatial/test/Data.Projections.SqlServer.Tests/DatabaseContext.cs create mode 100644 src/HotChocolate/Spatial/test/Data.Projections.SqlServer.Tests/Extensions/TestExtensions.cs create mode 100644 src/HotChocolate/Spatial/test/Data.Projections.SqlServer.Tests/Extensions/ToQueryStringExtensions.cs create mode 100644 src/HotChocolate/Spatial/test/Data.Projections.SqlServer.Tests/FilterVisitorTestBase.cs create mode 100644 src/HotChocolate/Spatial/test/Data.Projections.SqlServer.Tests/HotChocolate.Data.Projections.Spatial.SqlServer.Tests.csproj create mode 100644 src/HotChocolate/Spatial/test/Data.Projections.SqlServer.Tests/PostgisConfig.cs create mode 100644 src/HotChocolate/Spatial/test/Data.Projections.SqlServer.Tests/QueryableFilterVisitorTests.cs create mode 100644 src/HotChocolate/Spatial/test/Data.Projections.SqlServer.Tests/SchemaCache.cs create mode 100644 src/HotChocolate/Spatial/test/Data.Projections.SqlServer.Tests/__snapshots__/QueryableProjectionVisitorTests.Create_Expression.snap create mode 100644 src/HotChocolate/Spatial/test/Data.Projections.SqlServer.Tests/__snapshots__/QueryableProjectionVisitorTests.Create_Expression__sql.snap diff --git a/src/HotChocolate/Data/src/Data/Projections/Convention/ProjectionProviderExtensions.cs b/src/HotChocolate/Data/src/Data/Projections/Convention/ProjectionProviderExtension.cs similarity index 85% rename from src/HotChocolate/Data/src/Data/Projections/Convention/ProjectionProviderExtensions.cs rename to src/HotChocolate/Data/src/Data/Projections/Convention/ProjectionProviderExtension.cs index a197065428a..74904d42eb8 100644 --- a/src/HotChocolate/Data/src/Data/Projections/Convention/ProjectionProviderExtensions.cs +++ b/src/HotChocolate/Data/src/Data/Projections/Convention/ProjectionProviderExtension.cs @@ -3,19 +3,19 @@ namespace HotChocolate.Data.Projections { - public abstract class ProjectionProviderExtensions - : ConventionExtension, - IProjectionProviderExtension, - IProjectionProviderConvention + public class ProjectionProviderExtension + : ConventionExtension + , IProjectionProviderExtension + , IProjectionProviderConvention { private Action? _configure; - protected ProjectionProviderExtensions() + protected ProjectionProviderExtension() { _configure = Configure; } - public ProjectionProviderExtensions(Action configure) + public ProjectionProviderExtension(Action configure) { _configure = configure ?? throw new ArgumentNullException(nameof(configure)); diff --git a/src/HotChocolate/Data/test/Data.Projections.Tests/ProjectionConventionExtensionsTests.cs b/src/HotChocolate/Data/test/Data.Projections.Tests/ProjectionConventionExtensionsTests.cs index 62f7a342826..e9f70f75ac8 100644 --- a/src/HotChocolate/Data/test/Data.Projections.Tests/ProjectionConventionExtensionsTests.cs +++ b/src/HotChocolate/Data/test/Data.Projections.Tests/ProjectionConventionExtensionsTests.cs @@ -110,7 +110,7 @@ public void Merge_Should_Merge_ProviderExtensions() x => Assert.Equal(provider2, x)); } - private class MockProviderExtensions : ProjectionProviderExtensions + private class MockProviderExtensions : ProjectionProviderExtension { } diff --git a/src/HotChocolate/Spatial/HotChocolate.Spatial.sln b/src/HotChocolate/Spatial/HotChocolate.Spatial.sln index f6653402742..8204559202b 100644 --- a/src/HotChocolate/Spatial/HotChocolate.Spatial.sln +++ b/src/HotChocolate/Spatial/HotChocolate.Spatial.sln @@ -57,6 +57,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HotChocolate.Data.Spatial", EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HotChocolate.Data", "..\Data\src\Data\HotChocolate.Data.csproj", "{2AB20A1C-C927-4596-A003-07E2331C2943}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HotChocolate.Data.Projections.Spatial.SqlServer.Tests", "test\Data.Projections.SqlServer.Tests\HotChocolate.Data.Projections.Spatial.SqlServer.Tests.csproj", "{C30D7D2B-6D7E-4F2E-A237-A7C6B2626838}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -91,6 +93,7 @@ Global {553B323C-6436-4F21-9794-637203E1E371} = {4EE990B2-C327-46DA-8FE8-F95AC228E47F} {5E628010-C2DF-4000-9C9F-9A6A60EC62B7} = {91887A91-7B1C-4287-A1E0-BD4E0DAF24C7} {2AB20A1C-C927-4596-A003-07E2331C2943} = {882EC02D-5E1D-41F5-AD9F-AA06E31D133A} + {C30D7D2B-6D7E-4F2E-A237-A7C6B2626838} = {4EE990B2-C327-46DA-8FE8-F95AC228E47F} EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {D68A0AB9-871A-487B-8D12-1A7544D81B9E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU @@ -345,5 +348,17 @@ Global {2AB20A1C-C927-4596-A003-07E2331C2943}.Release|x64.Build.0 = Release|Any CPU {2AB20A1C-C927-4596-A003-07E2331C2943}.Release|x86.ActiveCfg = Release|Any CPU {2AB20A1C-C927-4596-A003-07E2331C2943}.Release|x86.Build.0 = Release|Any CPU + {C30D7D2B-6D7E-4F2E-A237-A7C6B2626838}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C30D7D2B-6D7E-4F2E-A237-A7C6B2626838}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C30D7D2B-6D7E-4F2E-A237-A7C6B2626838}.Debug|x64.ActiveCfg = Debug|Any CPU + {C30D7D2B-6D7E-4F2E-A237-A7C6B2626838}.Debug|x64.Build.0 = Debug|Any CPU + {C30D7D2B-6D7E-4F2E-A237-A7C6B2626838}.Debug|x86.ActiveCfg = Debug|Any CPU + {C30D7D2B-6D7E-4F2E-A237-A7C6B2626838}.Debug|x86.Build.0 = Debug|Any CPU + {C30D7D2B-6D7E-4F2E-A237-A7C6B2626838}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C30D7D2B-6D7E-4F2E-A237-A7C6B2626838}.Release|Any CPU.Build.0 = Release|Any CPU + {C30D7D2B-6D7E-4F2E-A237-A7C6B2626838}.Release|x64.ActiveCfg = Release|Any CPU + {C30D7D2B-6D7E-4F2E-A237-A7C6B2626838}.Release|x64.Build.0 = Release|Any CPU + {C30D7D2B-6D7E-4F2E-A237-A7C6B2626838}.Release|x86.ActiveCfg = Release|Any CPU + {C30D7D2B-6D7E-4F2E-A237-A7C6B2626838}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection EndGlobal diff --git a/src/HotChocolate/Spatial/src/Data/Projections/Extensions/Extensions/QueryableSpatialProjectionScalarHandler.cs b/src/HotChocolate/Spatial/src/Data/Projections/Extensions/Extensions/QueryableSpatialProjectionScalarHandler.cs new file mode 100644 index 00000000000..5b4f052e03f --- /dev/null +++ b/src/HotChocolate/Spatial/src/Data/Projections/Extensions/Extensions/QueryableSpatialProjectionScalarHandler.cs @@ -0,0 +1,15 @@ +using HotChocolate.Data.Projections.Expressions.Handlers; +using HotChocolate.Execution.Processing; +using HotChocolate.Utilities; +using NetTopologySuite.Geometries; + +namespace HotChocolate.Data.Projections.Spatial +{ + public class QueryableSpatialProjectionScalarHandler + : QueryableProjectionScalarHandler + { + public override bool CanHandle(ISelection selection) => + selection.Field.Member is not null && + typeof(Geometry).IsAssignableFrom(selection.Field.Member.GetReturnType()); + } +} diff --git a/src/HotChocolate/Spatial/src/Data/Projections/Extensions/Extensions/SpatialFilterProviderDescriptorQueryableExtensions.cs b/src/HotChocolate/Spatial/src/Data/Projections/Extensions/Extensions/SpatialFilterProviderDescriptorQueryableExtensions.cs new file mode 100644 index 00000000000..44360ee6a54 --- /dev/null +++ b/src/HotChocolate/Spatial/src/Data/Projections/Extensions/Extensions/SpatialFilterProviderDescriptorQueryableExtensions.cs @@ -0,0 +1,9 @@ +namespace HotChocolate.Data.Projections.Spatial +{ + public static class SpatialProjectionProviderDescriptorQueryableExtensions + { + public static IProjectionProviderDescriptor AddSpatialHandlers( + this IProjectionProviderDescriptor descriptor) => + descriptor.RegisterFieldHandler(); + } +} diff --git a/src/HotChocolate/Spatial/src/Data/Projections/Extensions/SpatialProjectionsRequestExecutorBuilderExtensions.cs b/src/HotChocolate/Spatial/src/Data/Projections/Extensions/SpatialProjectionsRequestExecutorBuilderExtensions.cs new file mode 100644 index 00000000000..3743288eccc --- /dev/null +++ b/src/HotChocolate/Spatial/src/Data/Projections/Extensions/SpatialProjectionsRequestExecutorBuilderExtensions.cs @@ -0,0 +1,22 @@ +using System; +using HotChocolate.Data.Filters; +using HotChocolate.Data.Filters.Spatial; +using HotChocolate.Execution.Configuration; +using Microsoft.Extensions.DependencyInjection; + +namespace HotChocolate +{ + public static class SpatialProjectionsRequestExecutorBuilderExtensions + { + public static IRequestExecutorBuilder AddSpatialProjections( + this IRequestExecutorBuilder builder) + { + if (builder is null) + { + throw new ArgumentNullException(nameof(builder)); + } + + return builder.ConfigureSchema(x => x.AddSpatialProjections()); + } + } +} diff --git a/src/HotChocolate/Spatial/src/Data/Projections/Extensions/SpatialProjectionsSchemaBuilderExtensions.cs b/src/HotChocolate/Spatial/src/Data/Projections/Extensions/SpatialProjectionsSchemaBuilderExtensions.cs new file mode 100644 index 00000000000..38b33cabd00 --- /dev/null +++ b/src/HotChocolate/Spatial/src/Data/Projections/Extensions/SpatialProjectionsSchemaBuilderExtensions.cs @@ -0,0 +1,22 @@ +using System; +using HotChocolate.Data.Projections.Spatial; +using HotChocolate.Data.Projections; + +namespace HotChocolate +{ + public static class SpatialProjectionsSchemaBuilderExtensions + { + public static ISchemaBuilder AddSpatialProjections(this ISchemaBuilder builder) + { + if (builder is null) + { + throw new ArgumentNullException(nameof(builder)); + } + + return builder.AddConvention( + new ProjectionConventionExtension( + x => x.AddProviderExtension( + new ProjectionProviderExtension(y => y.AddSpatialHandlers())))); + } + } +} diff --git a/src/HotChocolate/Spatial/test/Data.Projections.SqlServer.Tests/DatabaseContext.cs b/src/HotChocolate/Spatial/test/Data.Projections.SqlServer.Tests/DatabaseContext.cs new file mode 100644 index 00000000000..495eebdf098 --- /dev/null +++ b/src/HotChocolate/Spatial/test/Data.Projections.SqlServer.Tests/DatabaseContext.cs @@ -0,0 +1,38 @@ +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Squadron; + +namespace HotChocolate.Data.Projections.Spatial +{ + public class DatabaseContext : DbContext where T : class + { + private readonly PostgreSqlResource _resource; + private readonly string _databaseName; + private bool _disposed; + + public DatabaseContext(PostgreSqlResource resource, string databaseName) + { + _resource = resource; + _databaseName = databaseName; + } + + public DbSet Data { get; set; } = default!; + + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + { + optionsBuilder.UseNpgsql( + _resource.GetConnectionString(_databaseName), + o => o.UseNetTopologySuite()); + } + + public override async ValueTask DisposeAsync() + { + await base.DisposeAsync(); + + if (!_disposed) + { + _disposed = true; + } + } + } +} diff --git a/src/HotChocolate/Spatial/test/Data.Projections.SqlServer.Tests/Extensions/TestExtensions.cs b/src/HotChocolate/Spatial/test/Data.Projections.SqlServer.Tests/Extensions/TestExtensions.cs new file mode 100644 index 00000000000..59a2acf6d58 --- /dev/null +++ b/src/HotChocolate/Spatial/test/Data.Projections.SqlServer.Tests/Extensions/TestExtensions.cs @@ -0,0 +1,25 @@ +using HotChocolate.Execution; +using HotChocolate.Tests; +using Snapshooter; +using Snapshooter.Xunit; + +namespace HotChocolate.Data.Projections.Spatial +{ + public static class TestExtensions + { + public static void MatchSqlSnapshot( + this IExecutionResult? result, + string snapshotName) + { + if (result is { }) + { + result.MatchSnapshot(snapshotName); + if (result.ContextData is { } && + result.ContextData.TryGetValue("sql", out object? queryResult)) + { + queryResult.MatchSnapshot(new SnapshotNameExtension(snapshotName + "_sql")); + } + } + } + } +} diff --git a/src/HotChocolate/Spatial/test/Data.Projections.SqlServer.Tests/Extensions/ToQueryStringExtensions.cs b/src/HotChocolate/Spatial/test/Data.Projections.SqlServer.Tests/Extensions/ToQueryStringExtensions.cs new file mode 100644 index 00000000000..0303f1aa916 --- /dev/null +++ b/src/HotChocolate/Spatial/test/Data.Projections.SqlServer.Tests/Extensions/ToQueryStringExtensions.cs @@ -0,0 +1,51 @@ +using System.Linq; +using System.Reflection; +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore.Query.SqlExpressions; +using Microsoft.EntityFrameworkCore.Query; +using System; +using Microsoft.EntityFrameworkCore.Storage; + +namespace HotChocolate.Data.Projections.Spatial +{ + public static class ToQueryStringExtensions + { + public static string ToQueryString( + this IQueryable query) + where TEntity : class + { + IEnumerator? enumerator = query + .Provider + .Execute>(query.Expression) + .GetEnumerator(); + + var relationalCommandCache = enumerator.Private("_relationalCommandCache"); + + SelectExpression? selectExpression = relationalCommandCache + .Private("_selectExpression"); + + IQuerySqlGeneratorFactory? factory = relationalCommandCache + .Private("_querySqlGeneratorFactory"); + + QuerySqlGenerator? sqlGenerator = factory.Create(); + IRelationalCommand? command = sqlGenerator.GetCommand(selectExpression); + + return command.CommandText; + } + + private static object Private( + this object? obj, + string privateField) => + obj?.GetType()? + .GetField(privateField, BindingFlags.Instance | BindingFlags.NonPublic)? + .GetValue(obj) ?? throw new InvalidOperationException(); + + private static T Private( + this object obj, + string privateField) + where T : class => + (T?)obj?.GetType()? + .GetField(privateField, BindingFlags.Instance | BindingFlags.NonPublic)? + .GetValue(obj) ?? throw new InvalidOperationException(); + } +} diff --git a/src/HotChocolate/Spatial/test/Data.Projections.SqlServer.Tests/FilterVisitorTestBase.cs b/src/HotChocolate/Spatial/test/Data.Projections.SqlServer.Tests/FilterVisitorTestBase.cs new file mode 100644 index 00000000000..bd60b7e5ce6 --- /dev/null +++ b/src/HotChocolate/Spatial/test/Data.Projections.SqlServer.Tests/FilterVisitorTestBase.cs @@ -0,0 +1,94 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using HotChocolate.Execution; +using HotChocolate.Resolvers; +using HotChocolate.Types; +using Squadron; + +namespace HotChocolate.Data.Projections.Spatial +{ + public class ProjectionVisitorTestBase + { + private readonly PostgreSqlResource _resource; + + public ProjectionVisitorTestBase(PostgreSqlResource resource) + { + _resource = resource; + } + + private async Task>> BuildResolverAsync( + params T[] results) + where T : class + { + var databaseName = Guid.NewGuid().ToString("N"); + var dbContext = new DatabaseContext(_resource, databaseName); + + var sql = dbContext.Database.GenerateCreateScript(); + await _resource.CreateDatabaseAsync(databaseName); + await _resource.RunSqlScriptAsync( + "CREATE EXTENSION postgis;\n" + sql, + databaseName); + dbContext.AddRange(results); + dbContext.SaveChanges(); + + return ctx => dbContext.Data.AsQueryable(); + } + + protected async Task CreateSchemaAsync( + TEntity[] entities, + ProjectionConvention? convention = null) + where TEntity : class + { + Func> resolver = + await BuildResolverAsync(entities); + + return await new ServiceCollection() + .AddGraphQL() + .AddProjections() + .AddSpatialTypes() + .AddSpatialProjections() + .AddQueryType( + c => c + .Name("Query") + .Field("root") + .Resolver(resolver) + .Use(next => async context => + { + await next(context); + + if (context.Result is IQueryable queryable) + { + try + { + context.ContextData["sql"] = queryable.ToQueryString(); + } + catch (Exception) + { + context.ContextData["sql"] = + "EF Core 3.1 does not support ToQueryString officially"; + } + } + })) + .UseRequest(next => async context => + { + await next(context); + + if (context.Result is IReadOnlyQueryResult result && + context.ContextData.TryGetValue("sql", out var queryString)) + { + context.Result = + QueryResultBuilder + .FromResult(result) + .SetContextData("sql", queryString) + .Create(); + } + }) + .UseDefaultPipeline() + .BuildRequestExecutorAsync(); + } + } +} diff --git a/src/HotChocolate/Spatial/test/Data.Projections.SqlServer.Tests/HotChocolate.Data.Projections.Spatial.SqlServer.Tests.csproj b/src/HotChocolate/Spatial/test/Data.Projections.SqlServer.Tests/HotChocolate.Data.Projections.Spatial.SqlServer.Tests.csproj new file mode 100644 index 00000000000..9ec400ee2ff --- /dev/null +++ b/src/HotChocolate/Spatial/test/Data.Projections.SqlServer.Tests/HotChocolate.Data.Projections.Spatial.SqlServer.Tests.csproj @@ -0,0 +1,26 @@ + + + + HotChocolate.Data.Projections.Spatial.SqlServer + HotChocolate.Data.Projections + + + + + + + + + + + + + + + + + + + + + diff --git a/src/HotChocolate/Spatial/test/Data.Projections.SqlServer.Tests/PostgisConfig.cs b/src/HotChocolate/Spatial/test/Data.Projections.SqlServer.Tests/PostgisConfig.cs new file mode 100644 index 00000000000..a82922402b3 --- /dev/null +++ b/src/HotChocolate/Spatial/test/Data.Projections.SqlServer.Tests/PostgisConfig.cs @@ -0,0 +1,17 @@ +using System; +using Squadron; + +namespace HotChocolate.Data.Projections.Spatial +{ + public class PostgisConfig : PostgreSqlDefaultOptions + { + public override void Configure(ContainerResourceBuilder builder) => + builder + .WaitTimeout(120) + .Name("postgis") + .Image("postgis/postgis:latest") + .Username("postgis") + .Password(Guid.NewGuid().ToString("N").Substring(12)) + .InternalPort(5432); + } +} diff --git a/src/HotChocolate/Spatial/test/Data.Projections.SqlServer.Tests/QueryableFilterVisitorTests.cs b/src/HotChocolate/Spatial/test/Data.Projections.SqlServer.Tests/QueryableFilterVisitorTests.cs new file mode 100644 index 00000000000..5f766f55069 --- /dev/null +++ b/src/HotChocolate/Spatial/test/Data.Projections.SqlServer.Tests/QueryableFilterVisitorTests.cs @@ -0,0 +1,74 @@ +using System.Threading.Tasks; +using HotChocolate.Execution; +using NetTopologySuite.Geometries; +using Squadron; +using Xunit; + +namespace HotChocolate.Data.Projections.Spatial +{ + public class QueryableProjectionVisitorTests + : SchemaCache + , IClassFixture> + { + private static readonly Polygon _truePolygon = new Polygon( + new LinearRing( + new[] + { + new Coordinate(0, 0), + new Coordinate(0, 2), + new Coordinate(2, 2), + new Coordinate(2, 0), + new Coordinate(0, 0) + })); + + private static readonly Polygon _falsePolygon = new Polygon( + new LinearRing( + new[] + { + new Coordinate(0, 0), + new Coordinate(0, -2), + new Coordinate(-2, -2), + new Coordinate(-2, 0), + new Coordinate(0, 0) + })); + + private static readonly Foo[] _fooEntities = + { + new Foo { Id = 1, Bar = _truePolygon }, new Foo { Id = 2, Bar = _falsePolygon } + }; + + public QueryableProjectionVisitorTests(PostgreSqlResource resource) + : base(resource) + { + } + + [Fact] + public async Task Create_Expression() + { + // arrange + IRequestExecutor tester = await CreateSchemaAsync(_fooEntities); + + // act + // assert + IExecutionResult res1 = await tester.ExecuteAsync( + QueryRequestBuilder.New() + .SetQuery( + @"{ + root { + id + bar { coordinates } + } + }") + .Create()); + + res1.MatchSqlSnapshot(""); + } + + public class Foo + { + public int Id { get; set; } + + public Polygon Bar { get; set; } = null!; + } + } +} diff --git a/src/HotChocolate/Spatial/test/Data.Projections.SqlServer.Tests/SchemaCache.cs b/src/HotChocolate/Spatial/test/Data.Projections.SqlServer.Tests/SchemaCache.cs new file mode 100644 index 00000000000..4cda05b422a --- /dev/null +++ b/src/HotChocolate/Spatial/test/Data.Projections.SqlServer.Tests/SchemaCache.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Concurrent; +using System.Threading.Tasks; +using HotChocolate.Execution; +using Squadron; + +namespace HotChocolate.Data.Projections.Spatial +{ + public class SchemaCache + : ProjectionVisitorTestBase + , IDisposable + { + private readonly ConcurrentDictionary<(Type, object), Task> _cache = + new ConcurrentDictionary<(Type, object), Task>(); + + public SchemaCache(PostgreSqlResource resouce) : base(resouce) + { + } + + public Task CreateSchemaAsync(T[] entities) + where T : class + { + (Type, T[] entites) key = (typeof(T), entities); + return _cache.GetOrAdd(key, k => base.CreateSchemaAsync(entities)); + } + + public void Dispose() + { + } + } +} diff --git a/src/HotChocolate/Spatial/test/Data.Projections.SqlServer.Tests/__snapshots__/QueryableProjectionVisitorTests.Create_Expression.snap b/src/HotChocolate/Spatial/test/Data.Projections.SqlServer.Tests/__snapshots__/QueryableProjectionVisitorTests.Create_Expression.snap new file mode 100644 index 00000000000..2bc1fcfe4bb --- /dev/null +++ b/src/HotChocolate/Spatial/test/Data.Projections.SqlServer.Tests/__snapshots__/QueryableProjectionVisitorTests.Create_Expression.snap @@ -0,0 +1,60 @@ +{ + "data": { + "root": [ + { + "id": 1, + "bar": { + "coordinates": [ + [ + 0, + 0 + ], + [ + 0, + 2 + ], + [ + 2, + 2 + ], + [ + 2, + 0 + ], + [ + 0, + 0 + ] + ] + } + }, + { + "id": 2, + "bar": { + "coordinates": [ + [ + 0, + 0 + ], + [ + 0, + -2 + ], + [ + -2, + -2 + ], + [ + -2, + 0 + ], + [ + 0, + 0 + ] + ] + } + } + ] + } +} diff --git a/src/HotChocolate/Spatial/test/Data.Projections.SqlServer.Tests/__snapshots__/QueryableProjectionVisitorTests.Create_Expression__sql.snap b/src/HotChocolate/Spatial/test/Data.Projections.SqlServer.Tests/__snapshots__/QueryableProjectionVisitorTests.Create_Expression__sql.snap new file mode 100644 index 00000000000..ecd168e496f --- /dev/null +++ b/src/HotChocolate/Spatial/test/Data.Projections.SqlServer.Tests/__snapshots__/QueryableProjectionVisitorTests.Create_Expression__sql.snap @@ -0,0 +1,2 @@ +SELECT d."Id", d."Bar" +FROM "Data" AS d