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

Fixes #1817: support key alias #3145

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
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.");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if the $Key is in lowercase: $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">
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be UnresolvedPropertyRef.cs instead of UnresolvedProperty.cs

Suggested change
// <copyright file="UnresolvedProperty.cs" company="Microsoft">
// <copyright file="UnresolvedPropertyRef.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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
IEdmStructuralProperty structuralProperty = FindKeyProperty(keyProperty.PropertyName);
IEdmStructuralProperty structuralProperty = this.FindKeyProperty(keyProperty.PropertyName);

Or define FindKeyProperty as static method

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