Skip to content

Commit

Permalink
Fixes #1817: support key alias
Browse files Browse the repository at this point in the history
Introduce the IEdmPropertyRef interface
Use default interface method for IEdmEntityType avoid breaking changes
This change focus on Edm lib, if it's ready and will move to OData.Core.
  • Loading branch information
xuzhg committed Dec 6, 2024
1 parent 6f5bc5e commit 8540bc5
Show file tree
Hide file tree
Showing 23 changed files with 676 additions and 61 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,14 @@ namespace Microsoft.OData.Edm.Csdl.Parsing.Ast
/// </summary>
internal class CsdlPropertyReference : CsdlElement
{
private readonly string propertyName;

public CsdlPropertyReference(string propertyName, CsdlLocation location)
public CsdlPropertyReference(string propertyName, string propertyAlias, CsdlLocation location)
: base(location)
{
this.propertyName = propertyName;
PropertyName = propertyName;
PropertyAlias = propertyAlias;
}

public string PropertyName
{
get { return this.propertyName; }
}
public string PropertyName { get; }
public string PropertyAlias { get; }
}
}
2 changes: 1 addition & 1 deletion src/Microsoft.OData.Edm/Csdl/Parsing/CsdlDocumentParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -364,7 +364,7 @@ private static CsdlKey OnEntityKeyElement(XmlElementInfo element, XmlElementValu

private CsdlPropertyReference OnPropertyRefElement(XmlElementInfo element, XmlElementValueCollection childValues)
{
return new CsdlPropertyReference(Required(CsdlConstants.Attribute_Name), element.Location);
return new CsdlPropertyReference(Required(CsdlConstants.Attribute_Name), Optional(CsdlConstants.Attribute_Alias), element.Location);
}

private CsdlEnumType OnEnumTypeElement(XmlElementInfo element, XmlElementValueCollection childValues)
Expand Down
24 changes: 20 additions & 4 deletions src/Microsoft.OData.Edm/Csdl/Parsing/SchemaJsonParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -650,21 +650,37 @@ internal static CsdlKey ParseCsdlKey(string name, JsonElement keyArray, JsonPars
Debug.Assert(keyArray.ValueKind == JsonValueKind.Array);
Debug.Assert(name == "$Key", "The name should be $Key.");

string alias = null;
string propertyName = null;
IList<CsdlPropertyReference> properties = keyArray.ParseAsArray(context, (v, p) =>
{
if (v.ValueKind == JsonValueKind.Object)
{
// TODO: ODL doesn't support the key referencing a property of a complex type as below.
// Key properties with a key alias are represented as objects with one member
// whose name is the key alias and whose value is a string containing the path to the property. For example:
// "$Key": [
// {
// "EntityInfoID": "Info/ID"
// }
// ]
p.ReportError(EdmErrorCode.InvalidKeyValue, "It doesn't support the key object.");
var objectProperties = v.EnumerateObject();
if (objectProperties.Count() != 1)
{
p.ReportError(EdmErrorCode.InvalidKeyValue, "Key properties with a key alias are represented as objects with only one member.");
}
else
{
var property = objectProperties.First();
alias = property.Name;
propertyName = property.Value.ParseAsString(context);
}
}
else
{
propertyName = v.ParseAsString(context);
}

string propertyName = v.ParseAsString(context);
return new CsdlPropertyReference(propertyName, context.Location());
return new CsdlPropertyReference(propertyName, alias, context.Location());
});

return new CsdlKey(properties, context.Location());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
//---------------------------------------------------------------------
// <copyright file="UnresolvedProperty.cs" company="Microsoft">
// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
// </copyright>
//---------------------------------------------------------------------

using Microsoft.OData.Edm.Validation;

namespace Microsoft.OData.Edm.Csdl.CsdlSemantics
{
internal class UnresolvedPropertyRef : BadElement, IUnresolvedElement, IEdmPropertyRef
{
public UnresolvedPropertyRef(IEdmStructuredType declaringType, string name, string alias, EdmLocation location)
: base(new EdmError[]
{
new EdmError(location, EdmErrorCode.BadUnresolvedPropertyRef,
Edm.Strings.Bad_UnresolvedPropertyRef(declaringType.FullTypeName(), name, alias))
})
{
}

public IEdmStructuralProperty ReferencedProperty => null;

public string PropertyAlias { get; }

public string Name { get; }

public IEdmPathExpression Path { get; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using System.Collections.Generic;
using System.Linq;
using Microsoft.OData.Edm.Csdl.Parsing.Ast;
using Microsoft.OData.Edm.Vocabularies;

namespace Microsoft.OData.Edm.Csdl.CsdlSemantics
{
Expand All @@ -23,8 +24,8 @@ internal class CsdlSemanticsEntityTypeDefinition : CsdlSemanticsStructuredTypeDe
private static readonly Func<CsdlSemanticsEntityTypeDefinition, IEdmEntityType> ComputeBaseTypeFunc = (me) => me.ComputeBaseType();
private static readonly Func<CsdlSemanticsEntityTypeDefinition, IEdmEntityType> OnCycleBaseTypeFunc = (me) => new CyclicEntityType(me.GetCyclicBaseTypeName(me.entity.BaseTypeName), me.Location);

private readonly Cache<CsdlSemanticsEntityTypeDefinition, IEnumerable<IEdmStructuralProperty>> declaredKeyCache = new Cache<CsdlSemanticsEntityTypeDefinition, IEnumerable<IEdmStructuralProperty>>();
private static readonly Func<CsdlSemanticsEntityTypeDefinition, IEnumerable<IEdmStructuralProperty>> ComputeDeclaredKeyFunc = (me) => me.ComputeDeclaredKey();
private readonly Cache<CsdlSemanticsEntityTypeDefinition, IEnumerable<IEdmPropertyRef>> declaredKeyCache = new Cache<CsdlSemanticsEntityTypeDefinition, IEnumerable<IEdmPropertyRef>>();
private static readonly Func<CsdlSemanticsEntityTypeDefinition, IEnumerable<IEdmPropertyRef>> ComputeDeclaredKeyFunc = (me) => me.ComputeDeclaredKey();

public CsdlSemanticsEntityTypeDefinition(CsdlSemanticsSchema context, CsdlEntityType entity)
: base(context, entity)
Expand Down Expand Up @@ -72,6 +73,14 @@ public bool HasStream
}

public IEnumerable<IEdmStructuralProperty> DeclaredKey
{
get
{
return this.DeclaredKeyRef?.Select(x => x.ReferencedProperty);
}
}

public IEnumerable<IEdmPropertyRef> DeclaredKeyRef
{
get
{
Expand Down Expand Up @@ -105,17 +114,24 @@ private IEdmEntityType ComputeBaseType()
return null;
}

private IEnumerable<IEdmStructuralProperty> ComputeDeclaredKey()
private IEnumerable<IEdmPropertyRef> ComputeDeclaredKey()
{
if (this.entity.Key != null)
{
List<IEdmStructuralProperty> key = new List<IEdmStructuralProperty>();
List<IEdmPropertyRef> key = new List<IEdmPropertyRef>();
foreach (CsdlPropertyReference keyProperty in this.entity.Key.Properties)
{
IEdmStructuralProperty structuralProperty = this.FindProperty(keyProperty.PropertyName) as IEdmStructuralProperty;
IEdmStructuralProperty structuralProperty = FindKeyProperty(keyProperty.PropertyName);
if (structuralProperty != null)
{
key.Add(structuralProperty);
if (keyProperty.PropertyAlias != null)
{
key.Add(new EdmPropertyRef(structuralProperty, new EdmPropertyPathExpression(keyProperty.PropertyName), keyProperty.PropertyAlias));
}
else
{
key.Add(new EdmPropertyRef(structuralProperty));
}
}
else
{
Expand All @@ -125,11 +141,11 @@ private IEnumerable<IEdmStructuralProperty> ComputeDeclaredKey()
structuralProperty = this.DeclaredProperties.FirstOrDefault(p => p.Name == keyProperty.PropertyName) as IEdmStructuralProperty;
if (structuralProperty != null)
{
key.Add(structuralProperty);
key.Add(new EdmPropertyRef(structuralProperty));
}
else
{
key.Add(new UnresolvedProperty(this, keyProperty.PropertyName, this.Location));
key.Add(new UnresolvedPropertyRef(this, keyProperty.PropertyName, keyProperty.PropertyAlias, this.Location));
}
}
}
Expand All @@ -139,5 +155,71 @@ private IEnumerable<IEdmStructuralProperty> ComputeDeclaredKey()

return null;
}

private IEdmStructuralProperty FindKeyProperty(string nameOrPath)
{
if (string.IsNullOrWhiteSpace(nameOrPath))
{
return null;
}

string[] segments = nameOrPath.Split('/');
if (segments.Length == 1)
{
return FindPropertyOnType(this, nameOrPath) as IEdmStructuralProperty;
}
else
{
// OData spec says "The value of Name is a path expression leading to a primitive property."
// The segment in a path expression could be single value property name, collection value property name, type cast, ...
// However, for the key property reference path expression:
// 1) Collection value property name segment is invalid, right?
// 2) Type cast? reference a key on the sub type? it's valid but So far, let's skip it.
IEdmStructuredType edmStructuredType = this;
for (int i = 0; i < segments.Length; ++i)
{
if (edmStructuredType == null)
{
return null;
}

string segment = segments[i];
if (segment.Contains('.', StringComparison.Ordinal))
{
// a type cast, let's skip it for key reference now.
continue;
}

IEdmProperty edmProperty = FindPropertyOnType(edmStructuredType, segment);
if (i == segments.Length - 1)
{
return edmProperty as IEdmStructuralProperty;
}
else if (edmProperty != null)
{
// If the property is a collection value, let's move on using the element type of this collection
edmStructuredType = edmProperty.Type.ToStructuredType();
}
else
{
return null;
}
}
}

return null;
}

private static IEdmProperty FindPropertyOnType(IEdmStructuredType structuredType, string name)
{
IEdmProperty property = structuredType.FindProperty(name);

if (property == null)
{
property = structuredType.DeclaredProperties.FirstOrDefault(p => p.Name.Equals(name, StringComparison.OrdinalIgnoreCase));
}

return property;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -398,24 +398,33 @@ internal override Task WriteDeclaredKeyPropertiesElementHeaderAsync()
/// Write the Key property
/// </summary>
/// <param name="property">The Edm Structural Property.</param>
internal override void WritePropertyRefElement(IEdmStructuralProperty property)
internal override void WritePropertyRefElement(IEdmPropertyRef propertyRef)
{
// Key properties without a key alias are represented as strings containing the property name.
// Key properties with a key alias are represented as objects with one member whose name is the key alias and whose value is a string containing the path to the property.
// TODO: It seems the second one is not supported.
this.jsonWriter.WriteStringValue(property.Name);
if (propertyRef.PropertyAlias != null && propertyRef.PropertyAlias != propertyRef.Path.Path)
{
// Key properties with a key alias are represented as objects with one member
// whose name is the key alias and whose value is a string containing the path to the property.
this.jsonWriter.WriteStartObject();
this.jsonWriter.WriteRequiredProperty(propertyRef.PropertyAlias, propertyRef.Path.Path);
this.jsonWriter.WriteEndObject();
}
else
{
// Key properties without a key alias are represented as strings containing the property name.
this.jsonWriter.WriteStringValue(propertyRef.Path.Path);
}
}

/// <summary>
/// Asynchronously Writes the Key property.
/// </summary>
/// <param name="property">The Edm Structural Property.</param>
/// <returns>Task represents an asynchronous operation that may or may not return a result.</returns>
internal override Task WritePropertyRefElementAsync(IEdmStructuralProperty property)
internal override Task WritePropertyRefElementAsync(IEdmPropertyRef propertyRef)
{
// Key properties without a key alias are represented as strings containing the property name.
// Key properties with a key alias are represented as objects with one member whose name is the key alias and whose value is a string containing the path to the property.
this.jsonWriter.WriteStringValue(property.Name);
WritePropertyRefElement(propertyRef);

return Task.CompletedTask;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,8 @@ internal EdmModelCsdlSchemaWriter(IEdmModel model, Version edmVersion)
internal abstract void WriteDeclaredKeyPropertiesElementHeader();
internal abstract Task WriteDeclaredKeyPropertiesElementHeaderAsync();

internal abstract void WritePropertyRefElement(IEdmStructuralProperty property);
internal abstract Task WritePropertyRefElementAsync(IEdmStructuralProperty property);
internal abstract void WritePropertyRefElement(IEdmPropertyRef propertyRef);
internal abstract Task WritePropertyRefElementAsync(IEdmPropertyRef propertyRef);

internal abstract void WriteNavigationPropertyElementHeader(IEdmNavigationProperty property);
internal abstract Task WriteNavigationPropertyElementHeaderAsync(IEdmNavigationProperty property);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -400,23 +400,35 @@ internal override Task WriteDeclaredKeyPropertiesElementHeaderAsync()
/// <summary>
/// Writes the PropertyRef element.
/// </summary>
/// <param name="property">The Edm Structural Property.</param>
internal override void WritePropertyRefElement(IEdmStructuralProperty property)
/// <param name="propertyRef">The Edm property ref.</param>
internal override void WritePropertyRefElement(IEdmPropertyRef propertyRef)
{
this.xmlWriter.WriteStartElement(CsdlConstants.Element_PropertyRef);
this.WriteRequiredAttribute(CsdlConstants.Attribute_Name, property.Name, EdmValueWriter.StringAsXml);

this.WriteRequiredAttribute(CsdlConstants.Attribute_Name, propertyRef.Path.Path, EdmValueWriter.StringAsXml);

if (propertyRef.PropertyAlias != null && propertyRef.PropertyAlias != propertyRef.Path.Path)
{
this.WriteRequiredAttribute(CsdlConstants.Attribute_Alias, propertyRef.PropertyAlias, EdmValueWriter.StringAsXml);
}

this.WriteEndElement();
}

/// <summary>
/// Asynchronously writes the PropertyRef element.
/// </summary>
/// <param name="property">The Edm Structural Property.</param>
/// <param name="propertyRef">The Edm property ref.</param>
/// <returns>Task represents an asynchronous operation.</returns>
internal override async Task WritePropertyRefElementAsync(IEdmStructuralProperty property)
internal override async Task WritePropertyRefElementAsync(IEdmPropertyRef propertyRef)
{
await this.xmlWriter.WriteStartElementAsync(null, CsdlConstants.Element_PropertyRef, null).ConfigureAwait(false);
await this.WriteRequiredAttributeAsync(CsdlConstants.Attribute_Name, property.Name, EdmValueWriter.StringAsXml).ConfigureAwait(false);
await this.WriteRequiredAttributeAsync(CsdlConstants.Attribute_Name, propertyRef.Path.Path, EdmValueWriter.StringAsXml).ConfigureAwait(false);

if (propertyRef.PropertyAlias != null && propertyRef.PropertyAlias != propertyRef.Path.Path)
{
await this.WriteRequiredAttributeAsync(CsdlConstants.Attribute_Name, propertyRef.PropertyAlias, EdmValueWriter.StringAsXml).ConfigureAwait(false);
}
await this.WriteEndElementAsync().ConfigureAwait(false);
}

Expand Down
Loading

0 comments on commit 8540bc5

Please sign in to comment.