Browse Source

Query by array field.

pull/353/head
Sebastian Stehle 7 years ago
parent
commit
5243842bc6
  1. 31
      src/Squidex.Domain.Apps.Core.Operations/GenerateEdmSchema/EdmSchemaExtensions.cs
  2. 27
      src/Squidex.Domain.Apps.Core.Operations/GenerateEdmSchema/EdmTypeVisitor.cs
  3. 12
      src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Visitors/FilterFactory.cs
  4. 2
      src/Squidex.Domain.Apps.Entities/Contents/ContentQueryService.cs
  5. 44
      src/Squidex.Domain.Apps.Entities/Contents/Edm/EdmModelBuilder.cs
  6. 8
      tests/Squidex.Domain.Apps.Core.Tests/Operations/GenerateEdmSchema/EdmTests.cs
  7. 2
      tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentQueryServiceTests.cs
  8. 11
      tests/Squidex.Domain.Apps.Entities.Tests/Contents/MongoDb/MongoDbQueryTests.cs

31
src/Squidex.Domain.Apps.Core.Operations/GenerateEdmSchema/EdmSchemaExtensions.cs

@ -5,14 +5,14 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System;
using System.Linq;
using Microsoft.OData.Edm; using Microsoft.OData.Edm;
using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure; using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Core.GenerateEdmSchema namespace Squidex.Domain.Apps.Core.GenerateEdmSchema
{ {
public delegate (EdmComplexType Type, bool Created) EdmTypeFactory(string names);
public static class EdmSchemaExtensions public static class EdmSchemaExtensions
{ {
public static string EscapeEdmField(this string field) public static string EscapeEdmField(this string field)
@ -25,30 +25,39 @@ namespace Squidex.Domain.Apps.Core.GenerateEdmSchema
return field.Replace("_", "-"); return field.Replace("_", "-");
} }
public static EdmComplexType BuildEdmType(this Schema schema, PartitionResolver partitionResolver, Func<EdmComplexType, EdmComplexType> typeResolver) public static EdmComplexType BuildEdmType(this Schema schema, bool withHidden, PartitionResolver partitionResolver, EdmTypeFactory typeFactory)
{ {
Guard.NotNull(typeResolver, nameof(typeResolver)); Guard.NotNull(typeFactory, nameof(typeFactory));
Guard.NotNull(partitionResolver, nameof(partitionResolver)); Guard.NotNull(partitionResolver, nameof(partitionResolver));
var schemaName = schema.Name.ToPascalCase(); var (edmType, _) = typeFactory("Data");
var edmType = new EdmComplexType("Squidex", schemaName); var visitor = new EdmTypeVisitor(typeFactory);
foreach (var field in schema.FieldsByName.Values.Where(x => !x.IsHidden)) foreach (var field in schema.FieldsByName.Values)
{
if (!withHidden && field.IsHidden)
{ {
var edmValueType = EdmTypeVisitor.CreateEdmType(field); continue;
}
if (edmValueType == null) var fieldEdmType = field.Accept(visitor);
if (fieldEdmType == null)
{ {
continue; continue;
} }
var partitionType = typeResolver(new EdmComplexType("Squidex", $"{schemaName}{field.Name.ToPascalCase()}Property")); var (partitionType, created) = typeFactory($"Data.{field.Name.ToPascalCase()}");
if (created)
{
var partition = partitionResolver(field.Partitioning); var partition = partitionResolver(field.Partitioning);
foreach (var partitionItem in partition) foreach (var partitionItem in partition)
{ {
partitionType.AddStructuralProperty(partitionItem.Key.EscapeEdmField(), edmValueType); partitionType.AddStructuralProperty(partitionItem.Key.EscapeEdmField(), fieldEdmType);
}
} }
edmType.AddStructuralProperty(field.Name.EscapeEdmField(), new EdmComplexTypeReference(partitionType, false)); edmType.AddStructuralProperty(field.Name.EscapeEdmField(), new EdmComplexTypeReference(partitionType, false));

27
src/Squidex.Domain.Apps.Core.Operations/GenerateEdmSchema/EdmTypeVisitor.cs

@ -7,25 +7,42 @@
using Microsoft.OData.Edm; using Microsoft.OData.Edm;
using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Core.GenerateEdmSchema namespace Squidex.Domain.Apps.Core.GenerateEdmSchema
{ {
public sealed class EdmTypeVisitor : IFieldVisitor<IEdmTypeReference> public sealed class EdmTypeVisitor : IFieldVisitor<IEdmTypeReference>
{ {
private static readonly EdmTypeVisitor Instance = new EdmTypeVisitor(); private readonly EdmTypeFactory typeFactory;
private EdmTypeVisitor() internal EdmTypeVisitor(EdmTypeFactory typeFactory)
{ {
this.typeFactory = typeFactory;
} }
public static IEdmTypeReference CreateEdmType(IField field) public IEdmTypeReference CreateEdmType(IField field)
{ {
return field.Accept(Instance); return field.Accept(this);
} }
public IEdmTypeReference Visit(IArrayField field) public IEdmTypeReference Visit(IArrayField field)
{ {
return null; var (fieldEdmType, created) = typeFactory($"Data.{field.Name.ToPascalCase()}.Item");
if (created)
{
foreach (var nestedField in field.Fields)
{
var nestedEdmType = nestedField.Accept(this);
if (nestedEdmType != null)
{
fieldEdmType.AddStructuralProperty(nestedField.Name.EscapeEdmField(), nestedEdmType);
}
}
}
return new EdmComplexTypeReference(fieldEdmType, false);
} }
public IEdmTypeReference Visit(IField<AssetsFieldProperties> field) public IEdmTypeReference Visit(IField<AssetsFieldProperties> field)

12
src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Visitors/FilterFactory.cs

@ -65,6 +65,18 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Visitors
} }
result[1] = field.Id.ToString(); result[1] = field.Id.ToString();
if (field is IArrayField arrayField && result.Count > 3)
{
var nestedEdmName = result[3].UnescapeEdmField();
if (!arrayField.FieldsByName.TryGetValue(nestedEdmName, out var nestedField))
{
throw new NotSupportedException();
}
result[3] = nestedField.Id.ToString();
}
} }
if (result.Count > 2) if (result.Count > 2)

2
src/Squidex.Domain.Apps.Entities/Contents/ContentQueryService.cs

@ -262,7 +262,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
{ {
try try
{ {
var model = modelBuilder.BuildEdmModel(schema, context.App); var model = modelBuilder.BuildEdmModel(context.App, schema, context.IsFrontendClient);
var result = model.ParseQuery(query).ToQuery(); var result = model.ParseQuery(query).ToQuery();

44
src/Squidex.Domain.Apps.Entities/Contents/Edm/EdmModelBuilder.cs

@ -6,6 +6,7 @@
// ========================================================================== // ==========================================================================
using System; using System;
using System.Linq;
using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Caching.Memory;
using Microsoft.OData.Edm; using Microsoft.OData.Edm;
using Squidex.Domain.Apps.Core; using Squidex.Domain.Apps.Core;
@ -26,34 +27,56 @@ namespace Squidex.Domain.Apps.Entities.Contents.Edm
{ {
} }
public virtual IEdmModel BuildEdmModel(ISchemaEntity schema, IAppEntity app) public virtual IEdmModel BuildEdmModel(IAppEntity app, ISchemaEntity schema, bool withHidden)
{ {
Guard.NotNull(schema, nameof(schema)); Guard.NotNull(schema, nameof(schema));
var cacheKey = $"{schema.Id}_{schema.Version}_{app.Id}_{app.Version}"; var cacheKey = BuildCacheKey(app, schema, withHidden);
var result = Cache.GetOrCreate<IEdmModel>(cacheKey, entry => var result = Cache.GetOrCreate<IEdmModel>(cacheKey, entry =>
{ {
entry.AbsoluteExpirationRelativeToNow = CacheTime; entry.AbsoluteExpirationRelativeToNow = CacheTime;
return BuildEdmModel(schema.SchemaDef, app.PartitionResolver()); return BuildEdmModel(schema.SchemaDef, app, withHidden);
}); });
return result; return result;
} }
private static EdmModel BuildEdmModel(Schema schema, PartitionResolver partitionResolver) private static EdmModel BuildEdmModel(Schema schema, IAppEntity app, bool withHidden)
{ {
var model = new EdmModel(); var model = new EdmModel();
var schemaType = schema.BuildEdmType(partitionResolver, x => var pascalAppName = app.Name.ToPascalCase();
var pascalSchemaName = schema.Name.ToPascalCase();
var typeFactory = new EdmTypeFactory(name =>
{
var finalName = pascalSchemaName;
if (!string.IsNullOrWhiteSpace(name))
{
finalName += ".";
finalName += name;
}
var result = model.SchemaElements.OfType<EdmComplexType>().FirstOrDefault(x => x.Name == finalName);
if (result != null)
{ {
model.AddElement(x); return (result, false);
}
result = new EdmComplexType(pascalAppName, finalName);
return x; model.AddElement(result);
return (result, true);
}); });
var entityType = new EdmEntityType("Squidex", schema.Name); var schemaType = schema.BuildEdmType(withHidden, app.PartitionResolver(), typeFactory);
var entityType = new EdmEntityType(app.Name.ToPascalCase(), schema.Name);
entityType.AddStructuralProperty(nameof(IContentEntity.Id).ToCamelCase(), EdmPrimitiveTypeKind.String); entityType.AddStructuralProperty(nameof(IContentEntity.Id).ToCamelCase(), EdmPrimitiveTypeKind.String);
entityType.AddStructuralProperty(nameof(IContentEntity.Created).ToCamelCase(), EdmPrimitiveTypeKind.DateTimeOffset); entityType.AddStructuralProperty(nameof(IContentEntity.Created).ToCamelCase(), EdmPrimitiveTypeKind.DateTimeOffset);
entityType.AddStructuralProperty(nameof(IContentEntity.CreatedBy).ToCamelCase(), EdmPrimitiveTypeKind.String); entityType.AddStructuralProperty(nameof(IContentEntity.CreatedBy).ToCamelCase(), EdmPrimitiveTypeKind.String);
@ -73,5 +96,10 @@ namespace Squidex.Domain.Apps.Entities.Contents.Edm
return model; return model;
} }
private static string BuildCacheKey(IAppEntity app, ISchemaEntity schema, bool withHidden)
{
return string.Join("_", schema.Id, schema.Version, app.Id, app.Version, withHidden);
}
} }
} }

8
tests/Squidex.Domain.Apps.Core.Tests/Operations/GenerateEdmSchema/EdmTests.cs

@ -5,6 +5,7 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using Microsoft.OData.Edm;
using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Core.Apps;
using Squidex.Domain.Apps.Core.GenerateEdmSchema; using Squidex.Domain.Apps.Core.GenerateEdmSchema;
using Squidex.Infrastructure; using Squidex.Infrastructure;
@ -31,7 +32,12 @@ namespace Squidex.Domain.Apps.Core.Operations.GenerateEdmSchema
{ {
var languagesConfig = LanguagesConfig.Build(Language.DE, Language.EN); var languagesConfig = LanguagesConfig.Build(Language.DE, Language.EN);
var edmModel = TestUtils.MixedSchema().BuildEdmType(languagesConfig.ToResolver(), x => x); var typeFactory = new EdmTypeFactory(names =>
{
return (new EdmComplexType("Squidex", string.Join(".", names)), true);
});
var edmModel = TestUtils.MixedSchema().BuildEdmType(true, languagesConfig.ToResolver(), typeFactory);
Assert.NotNull(edmModel); Assert.NotNull(edmModel);
} }

2
tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentQueryServiceTests.cs

@ -310,7 +310,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
SetupClaims(); SetupClaims();
SetupSchema(); SetupSchema();
A.CallTo(() => modelBuilder.BuildEdmModel(schema, app)) A.CallTo(() => modelBuilder.BuildEdmModel(app, schema, A<bool>.Ignored))
.Throws(new ODataException()); .Throws(new ODataException());
return Assert.ThrowsAsync<ValidationException>(() => sut.QueryAsync(context, schemaId.Name, Q.Empty.WithODataQuery("query"))); return Assert.ThrowsAsync<ValidationException>(() => sut.QueryAsync(context, schemaId.Name, Q.Empty.WithODataQuery("query")));

11
tests/Squidex.Domain.Apps.Entities.Tests/Contents/MongoDb/MongoDbQueryTests.cs

@ -60,6 +60,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.MongoDb
new ReferencesFieldProperties()) new ReferencesFieldProperties())
.AddString(8, "dashed-field", Partitioning.Invariant, .AddString(8, "dashed-field", Partitioning.Invariant,
new StringFieldProperties()) new StringFieldProperties())
.AddArray(9, "hobbies", Partitioning.Invariant, a => a
.AddString(91, "name"))
.Update(new SchemaProperties()); .Update(new SchemaProperties());
var schema = A.Dummy<ISchemaEntity>(); var schema = A.Dummy<ISchemaEntity>();
@ -178,6 +180,15 @@ namespace Squidex.Domain.Apps.Entities.Contents.MongoDb
Assert.Equal(o, i); Assert.Equal(o, i);
} }
[Fact]
public void Should_make_query_with_array_field()
{
var i = F(FilterBuilder.Eq("data/hobbies/iv/name", "PC"));
var o = C("{ 'do.9.iv.91' : 'PC' }");
Assert.Equal(o, i);
}
[Fact] [Fact]
public void Should_make_query_with_assets_equals() public void Should_make_query_with_assets_equals()
{ {

Loading…
Cancel
Save