Skip to content

Commit

Permalink
Query by array field.
Browse files Browse the repository at this point in the history
  • Loading branch information
SebastianStehle committed Apr 19, 2019
1 parent 7b6aeb1 commit 5243842
Show file tree
Hide file tree
Showing 8 changed files with 112 additions and 29 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================

using System;
using System.Linq;
using Microsoft.OData.Edm;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure;

namespace Squidex.Domain.Apps.Core.GenerateEdmSchema
{
public delegate (EdmComplexType Type, bool Created) EdmTypeFactory(string names);

public static class EdmSchemaExtensions
{
public static string EscapeEdmField(this string field)
Expand All @@ -25,30 +25,39 @@ public static string UnescapeEdmField(this string field)
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));

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)
{
var edmValueType = EdmTypeVisitor.CreateEdmType(field);
if (!withHidden && field.IsHidden)
{
continue;
}

var fieldEdmType = field.Accept(visitor);

if (edmValueType == null)
if (fieldEdmType == null)
{
continue;
}

var partitionType = typeResolver(new EdmComplexType("Squidex", $"{schemaName}{field.Name.ToPascalCase()}Property"));
var partition = partitionResolver(field.Partitioning);
var (partitionType, created) = typeFactory($"Data.{field.Name.ToPascalCase()}");

foreach (var partitionItem in partition)
if (created)
{
partitionType.AddStructuralProperty(partitionItem.Key.EscapeEdmField(), edmValueType);
var partition = partitionResolver(field.Partitioning);

foreach (var partitionItem in partition)
{
partitionType.AddStructuralProperty(partitionItem.Key.EscapeEdmField(), fieldEdmType);
}
}

edmType.AddStructuralProperty(field.Name.EscapeEdmField(), new EdmComplexTypeReference(partitionType, false));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,25 +7,42 @@

using Microsoft.OData.Edm;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure;

namespace Squidex.Domain.Apps.Core.GenerateEdmSchema
{
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)
{
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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,18 @@ private static Func<IReadOnlyList<string>, IReadOnlyList<string>> PathConverter(
}
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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,7 @@ private Query ParseQuery(QueryContext context, string query, ISchemaEntity schem
{
try
{
var model = modelBuilder.BuildEdmModel(schema, context.App);
var model = modelBuilder.BuildEdmModel(context.App, schema, context.IsFrontendClient);

var result = model.ParseQuery(query).ToQuery();

Expand Down
44 changes: 36 additions & 8 deletions src/Squidex.Domain.Apps.Entities/Contents/Edm/EdmModelBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
// ==========================================================================

using System;
using System.Linq;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.OData.Edm;
using Squidex.Domain.Apps.Core;
Expand All @@ -26,34 +27,56 @@ public EdmModelBuilder(IMemoryCache cache)
{
}

public virtual IEdmModel BuildEdmModel(ISchemaEntity schema, IAppEntity app)
public virtual IEdmModel BuildEdmModel(IAppEntity app, ISchemaEntity schema, bool withHidden)
{
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 =>
{
entry.AbsoluteExpirationRelativeToNow = CacheTime;
return BuildEdmModel(schema.SchemaDef, app.PartitionResolver());
return BuildEdmModel(schema.SchemaDef, app, withHidden);
});

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 schemaType = schema.BuildEdmType(partitionResolver, x =>
var pascalAppName = app.Name.ToPascalCase();
var pascalSchemaName = schema.Name.ToPascalCase();

var typeFactory = new EdmTypeFactory(name =>
{
model.AddElement(x);
var finalName = pascalSchemaName;
if (!string.IsNullOrWhiteSpace(name))
{
finalName += ".";
finalName += name;
}
var result = model.SchemaElements.OfType<EdmComplexType>().FirstOrDefault(x => x.Name == finalName);
if (result != null)
{
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.Created).ToCamelCase(), EdmPrimitiveTypeKind.DateTimeOffset);
entityType.AddStructuralProperty(nameof(IContentEntity.CreatedBy).ToCamelCase(), EdmPrimitiveTypeKind.String);
Expand All @@ -73,5 +96,10 @@ private static EdmModel BuildEdmModel(Schema schema, PartitionResolver partition

return model;
}

private static string BuildCacheKey(IAppEntity app, ISchemaEntity schema, bool withHidden)
{
return string.Join("_", schema.Id, schema.Version, app.Id, app.Version, withHidden);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================

using Microsoft.OData.Edm;
using Squidex.Domain.Apps.Core.Apps;
using Squidex.Domain.Apps.Core.GenerateEdmSchema;
using Squidex.Infrastructure;
Expand All @@ -31,7 +32,12 @@ public void Should_build_edm_model()
{
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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -310,7 +310,7 @@ public Task Should_throw_if_query_is_invalid()
SetupClaims();
SetupSchema();

A.CallTo(() => modelBuilder.BuildEdmModel(schema, app))
A.CallTo(() => modelBuilder.BuildEdmModel(app, schema, A<bool>.Ignored))
.Throws(new ODataException());

return Assert.ThrowsAsync<ValidationException>(() => sut.QueryAsync(context, schemaId.Name, Q.Empty.WithODataQuery("query")));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ public MongoDbQueryTests()
new ReferencesFieldProperties())
.AddString(8, "dashed-field", Partitioning.Invariant,
new StringFieldProperties())
.AddArray(9, "hobbies", Partitioning.Invariant, a => a
.AddString(91, "name"))
.Update(new SchemaProperties());

var schema = A.Dummy<ISchemaEntity>();
Expand Down Expand Up @@ -178,6 +180,15 @@ public void Should_make_query_with_references_equals()
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]
public void Should_make_query_with_assets_equals()
{
Expand Down

0 comments on commit 5243842

Please sign in to comment.