Skip to content

Commit

Permalink
fixes #1817: support OData key alias
Browse files Browse the repository at this point in the history
  • Loading branch information
xuzhg committed Feb 3, 2023
1 parent 12c18fc commit 5a303fa
Show file tree
Hide file tree
Showing 28 changed files with 696 additions and 70 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,15 @@ 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
25 changes: 21 additions & 4 deletions src/Microsoft.OData.Edm/Csdl/Parsing/SchemaJsonParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -653,19 +653,36 @@ internal static CsdlKey ParseCsdlKey(string name, JsonElement keyArray, JsonPars

IList<CsdlPropertyReference> properties = keyArray.ParseAsArray(context, (v, p) =>
{
string alias = null;
string propertyName = null;
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.
// "$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
{
// Key properties without a key alias are represented as strings containing the property name.
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,28 @@
//---------------------------------------------------------------------
// <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; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ namespace Microsoft.OData.Edm.Csdl.CsdlSemantics
/// <summary>
/// Provides semantics for CsdlEntityType.
/// </summary>
internal class CsdlSemanticsEntityTypeDefinition : CsdlSemanticsStructuredTypeDefinition, IEdmEntityType, IEdmFullNamedElement
internal class CsdlSemanticsEntityTypeDefinition : CsdlSemanticsStructuredTypeDefinition, IEdmEntityType, IEdmFullNamedElement, IEdmKeyPropertyRef
{
private readonly CsdlEntityType entity;
private readonly string fullName;
Expand All @@ -23,8 +23,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,13 +72,22 @@ public bool HasStream
}

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

public IEnumerable<IEdmPropertyRef> DeclaredKeyRef
{
get
{
return this.declaredKeyCache.GetValue(this, ComputeDeclaredKeyFunc, null);
}
}


protected override CsdlStructuredType MyStructured
{
get { return this.entity; }
Expand All @@ -105,32 +114,21 @@ 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);
key.Add(new EdmPropertyRef(structuralProperty, keyProperty.PropertyName, keyProperty.PropertyAlias));
}
else
{
// If keyProperty is a duplicate, it will come back as non-structural from FindProperty, but it still might be structural
// inside the DeclaredProperties, so try it. If it is not in the DeclaredProperties or it is not structural there,
// then fall back to unresolved.
structuralProperty = this.DeclaredProperties.FirstOrDefault(p => p.Name == keyProperty.PropertyName) as IEdmStructuralProperty;
if (structuralProperty != null)
{
key.Add(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 +137,73 @@ 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 this.FindProperty(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 those.
IEdmStructuredType edmStructuredType = this;
for (int i = 0; i < segments.Length; ++i)
{
if (edmStructuredType == null)
{
return null;
}

string segment = segments[i];

if (segment.Contains("."))
{
// a type cast, let's skip it.
continue;
}

IEdmProperty edmProperty = FindPropertyOnType(edmStructuredType, segment);
if (i == segment.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 == name);
}

return property;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -201,15 +201,28 @@ internal override void WriteDeclaredKeyPropertiesElementHeader()
}

/// <summary>
/// Write the Key property
/// Write the Key property ref
/// </summary>
/// <param name="property"></param>
internal override void WritePropertyRefElement(IEdmStructuralProperty property)
/// <param name="propertyRef">The property ref.</param>
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)
{
// 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.WritePropertyName(propertyRef.PropertyAlias);

this.jsonWriter.WriteStringValue(propertyRef.Name);

this.jsonWriter.WriteEndObject();
}
else
{
// Key properties without a key alias are represented as strings containing the property name.
this.jsonWriter.WriteStringValue(propertyRef.Name);
}
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ internal EdmModelCsdlSchemaWriter(IEdmModel model, Version edmVersion)

internal abstract void WriteDeclaredKeyPropertiesElementHeader();

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

internal abstract void WriteNavigationPropertyElementHeader(IEdmNavigationProperty property);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,10 +150,11 @@ internal override void WriteDeclaredKeyPropertiesElementHeader()
this.xmlWriter.WriteStartElement(CsdlConstants.Element_Key);
}

internal override void WritePropertyRefElement(IEdmStructuralProperty property)
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.Name, EdmValueWriter.StringAsXml);
this.WriteOptionalAttribute(CsdlConstants.Attribute_Alias, propertyRef.PropertyAlias, EdmValueWriter.StringAsXml);
this.WriteEndElement();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ protected override void ProcessEntityType(IEdmEntityType element)
this.BeginElement(element, this.schemaWriter.WriteEntityTypeElementHeader);
if (element.DeclaredKey != null && element.DeclaredKey.Any())
{
this.VisitEntityTypeDeclaredKey(element.DeclaredKey);
this.VisitEntityTypeDeclaredKey(element.DeclaredKeyRef());
}

this.VisitProperties(element.DeclaredStructuralProperties().Cast<IEdmProperty>());
Expand Down Expand Up @@ -680,18 +680,18 @@ private void ProcessFacets(IEdmTypeReference element, bool inlineType)
}
}

private void VisitEntityTypeDeclaredKey(IEnumerable<IEdmStructuralProperty> keyProperties)
private void VisitEntityTypeDeclaredKey(IEnumerable<IEdmPropertyRef> keyProperties)
{
this.schemaWriter.WriteDeclaredKeyPropertiesElementHeader();
this.VisitPropertyRefs(keyProperties);
this.schemaWriter.WriteArrayEndElement();
}

private void VisitPropertyRefs(IEnumerable<IEdmStructuralProperty> properties)
private void VisitPropertyRefs(IEnumerable<IEdmPropertyRef> properties)
{
foreach (IEdmStructuralProperty property in properties)
foreach (IEdmPropertyRef propertyRef in properties)
{
this.schemaWriter.WritePropertyRefElement(property);
this.schemaWriter.WritePropertyRefElement(propertyRef);
}
}

Expand Down
Loading

0 comments on commit 5a303fa

Please sign in to comment.