Skip to content
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
9 changes: 9 additions & 0 deletions src/Microsoft.OData.Core/SRResources.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions src/Microsoft.OData.Core/SRResources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -2624,4 +2624,7 @@
<data name="TaskUtils_NullContinuationTask" xml:space="preserve">
<value>The continuation task returned by the operation cannot be null.</value>
</data>
<data name="MetadataBinder_FunctionArgumentNotSingleValueOrCollectionNode" xml:space="preserve">
<value>The argument for an invocation of a function with name '{0}' is not a single value or collection. Arguments for this function must be either a single value or collection.</value>
</data>
</root>
18 changes: 9 additions & 9 deletions src/Microsoft.OData.Core/Uri/ODataUriConversionUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -671,19 +671,19 @@ private static object ConvertFromResourceOrCollectionValue(string value, IEdmMod
{
ODataJsonPropertyAndValueDeserializer deserializer = new ODataJsonPropertyAndValueDeserializer(context);

// TODO: The way JSON array literals look in the URI is different that response payload with an array in it.
// TODO: The way JSON array literals look in the URI is different than response payload with an array in it.
// The fact that we have to manually setup the underlying reader shows this different in the protocol.
// There is a discussion on if we should change this or not.
deserializer.JsonReader.Read(); // Move to first thing
object rawResult = deserializer.ReadNonEntityValue(
null /*payloadTypeName*/,
typeReference,
null /*DuplicatePropertyNameChecker*/,
null /*CollectionWithoutExpectedTypeValidator*/,
true /*validateNullValue*/,
false /*isTopLevelPropertyValue*/,
false /*insideResourceValue*/,
null /*propertyName*/);
payloadTypeName: null,
expectedValueTypeReference: typeReference,
propertyAndAnnotationCollector: null,
collectionValidator: null,
validateNullValue: true,
isTopLevelPropertyValue: false,
insideResourceValue: false,
propertyName: null);
deserializer.ReadPayloadEnd(false);

return rawResult;
Expand Down
4 changes: 2 additions & 2 deletions src/Microsoft.OData.Core/Uri/ODataUriUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ public static object ConvertFromUriLiteral(string value, ODataVersion version, I

if (model == null)
{
model = Microsoft.OData.Edm.EdmCoreModel.Instance;
model = EdmCoreModel.Instance;
}

// Let ExpressionLexer try to get a primitive
Expand Down Expand Up @@ -149,7 +149,7 @@ public static string ConvertToUriLiteral(object value, ODataVersion version, IEd

if (model == null)
{
model = Microsoft.OData.Edm.EdmCoreModel.Instance;
model = EdmCoreModel.Instance;
}

ODataNullValue nullValue = value as ODataNullValue;
Expand Down
141 changes: 98 additions & 43 deletions src/Microsoft.OData.Core/UriParser/Binders/FunctionCallBinder.cs

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions src/Microsoft.OData.Core/UriParser/Binders/LiteralBinder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

namespace Microsoft.OData.UriParser
{
using System.Diagnostics;
using Microsoft.OData.Edm;

/// <summary>
/// Class that knows how to bind literal values.
Expand Down Expand Up @@ -48,8 +48,8 @@ internal static QueryNode BindInLiteral(LiteralToken literalToken)
{
if (literalToken.ExpectedEdmTypeReference != null)
{
OData.Edm.IEdmCollectionTypeReference collectionReference =
literalToken.ExpectedEdmTypeReference as OData.Edm.IEdmCollectionTypeReference;
IEdmCollectionTypeReference collectionReference =
literalToken.ExpectedEdmTypeReference as IEdmCollectionTypeReference;
if (collectionReference != null)
{
ODataCollectionValue collectionValue = literalToken.Value as ODataCollectionValue;
Expand Down
143 changes: 139 additions & 4 deletions src/Microsoft.OData.Core/UriParser/Binders/MetadataBindingUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
namespace Microsoft.OData.UriParser
{
using System;
using System.Collections.Generic;
using System.Diagnostics;
using Microsoft.OData;
using Microsoft.OData.Core;
Expand Down Expand Up @@ -73,7 +74,7 @@ internal static SingleValueNode ConvertToTypeIfNeeded(SingleValueNode source, IE
{
if(enumType.TryParse(memberIntegralValue, out IEdmEnumMember enumMember))
{
string literalText = ODataUriUtils.ConvertToUriLiteral(enumMember.Name, default(ODataVersion));
string literalText = ODataUriUtils.ConvertToUriLiteral(enumMember.Name, default);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Why this change?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Just applied a suggestion by the compiler

return new ConstantNode(new ODataEnumValue(enumMember.Name, enumType.ToString()), literalText, targetTypeReference);
}

Expand All @@ -82,7 +83,7 @@ internal static SingleValueNode ConvertToTypeIfNeeded(SingleValueNode source, IE
string flagsValue = enumType.ParseFlagsFromIntegralValue(memberIntegralValue);
if(!string.IsNullOrEmpty(flagsValue))
{
string literalText = ODataUriUtils.ConvertToUriLiteral(flagsValue, default(ODataVersion));
string literalText = ODataUriUtils.ConvertToUriLiteral(flagsValue, default);
return new ConstantNode(new ODataEnumValue(flagsValue, enumType.ToString()), literalText, targetTypeReference);
}
}
Expand Down Expand Up @@ -142,8 +143,8 @@ internal static SingleValueNode ConvertToTypeIfNeeded(SingleValueNode source, IE
var targetDecimalType = (IEdmDecimalTypeReference)targetTypeReference;
return decimalType.Precision == targetDecimalType.Precision &&
decimalType.Scale == targetDecimalType.Scale ?
(SingleValueNode)candidate :
(SingleValueNode)(new ConvertNode(candidate, targetTypeReference));
candidate :
new ConvertNode(candidate, targetTypeReference);
}
else
{
Expand All @@ -165,6 +166,140 @@ internal static SingleValueNode ConvertToTypeIfNeeded(SingleValueNode source, IE
}
}

/// <summary>
/// Converts a collection node's element type to <paramref name="targetTypeReference"/> when possible,
/// materializing a new <see cref="CollectionConstantNode"/> for constant collections;
/// leaves non-constant/open or non-convertible collections unchanged.
/// </summary>
/// <param name="source">Source collection node.</param>
/// <param name="targetTypeReference">Desired collection type (must be a collection).</param>
/// <returns>Converted collection node or original source.</returns>
internal static CollectionNode ConvertToTypeIfNeeded(CollectionNode source, IEdmTypeReference targetTypeReference)
{
Debug.Assert(source != null, "source != null");

if (targetTypeReference == null)
{
return source;
}

IEdmCollectionTypeReference sourceCollectionType = source.CollectionType;
if (sourceCollectionType == null) // Open collection? Leave as is
{
return source;
}

if (!targetTypeReference.IsCollection())
{
throw new ODataException(Error.Format(SRResources.MetadataBinder_CannotConvertToType, source.CollectionType.FullName(), targetTypeReference.FullName()));
}

IEdmCollectionTypeReference targetCollectionType = targetTypeReference.AsCollection();

if (sourceCollectionType.IsEquivalentTo(targetCollectionType))
{
IEdmTypeReference sourceElemType = sourceCollectionType.ElementType();
IEdmTypeReference targetElemType = targetCollectionType.ElementType();
if (source is CollectionConstantNode colConstantNode
&& sourceElemType.IsTypeDefinition()
&& targetElemType.IsPrimitive()
&& sourceElemType.AsPrimitive().PrimitiveKind() == targetElemType.AsPrimitive().PrimitiveKind())
{
List<ConstantNode> convertedNodes = ConvertNodes(colConstantNode.Collection, targetElemType);

return new CollectionConstantNode(convertedNodes, BuildCollectionLiteral(convertedNodes, targetElemType), targetCollectionType);
}

return source;
}

IEdmTypeReference sourceElementType = sourceCollectionType.ElementType();
IEdmTypeReference targetElementType = targetCollectionType.ElementType();

if (!TypePromotionUtils.CanConvertTo(null, sourceElementType, targetElementType))
{
throw new ODataException(Error.Format(SRResources.MetadataBinder_CannotConvertToType, sourceElementType.FullName(), targetElementType.FullName()));
}

if (source is CollectionConstantNode collectionConstantNode)
{
List<ConstantNode> convertedNodes = ConvertNodes(collectionConstantNode.Collection, targetElementType);

return new CollectionConstantNode(convertedNodes, BuildCollectionLiteral(convertedNodes, targetElementType), targetCollectionType);
}

// Non-constant collections: leave as-is (conversion implicit)
return source;
}

/// <summary>
/// Converts each constant value to <paramref name="targetElementType"/>, applying enum/numeric coercion; preserves null items.
/// </summary>
/// <param name="nodes">Original constant value nodes.</param>
/// <param name="targetElementType">The target primitive type.</param>
/// <returns>List of converted constant nodes.</returns>
private static List<ConstantNode> ConvertNodes(IList<ConstantNode> nodes, IEdmTypeReference targetElementType)
{
List<ConstantNode> convertedNodes = new List<ConstantNode>(nodes.Count);

for (int i = 0; i < nodes.Count; i++)
{
ConstantNode item = nodes[i];
if (item == null)
{
// Preserve null
convertedNodes.Add(new ConstantNode(null, "null", targetElementType));
continue;
}

ConstantNode convertedNode = ConvertToTypeIfNeeded(item, targetElementType) as ConstantNode;

// If ConvertToTypeIfNeeded returned a ConvertNode, force materialization into a ConstantNode
if (convertedNode == null)
{
// Try to keep original literal text if meaningful
string literal = item.LiteralText ?? ODataUriUtils.ConvertToUriLiteral(item.Value, ODataVersion.V4);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Will the behaviour be the same on v4.01?

convertedNode = new ConstantNode(item.Value, literal, targetElementType);
}

convertedNodes.Add(convertedNode);
}

return convertedNodes;
}

/// <summary>
/// Builds a bracketed collection literal (e.g. [1,2,3]) from constant nodes, quoting/escaping strings and preserving nulls.
/// </summary>
/// <param name="nodes">Constant nodes representing items.</param>
/// <param name="typeReference">Element type for string quoting rules.</param>
/// <returns>OData collection literal text.</returns>
private static string BuildCollectionLiteral(List<ConstantNode> nodes, IEdmTypeReference typeReference)
{
Debug.Assert(typeReference != null, $"{nameof(typeReference)} != null");

List<string> list = new List<string>();
for (int i = 0; i < nodes.Count; i++)
{
ConstantNode node = nodes[i];
if (node == null || node.Value == null)
{
list.Add("null");
continue;
}

string literal = node.LiteralText ?? ODataUriUtils.ConvertToUriLiteral(node.Value, ODataVersion.V4);
if (typeReference.IsString() && !(literal.Length > 1 && literal[0] == '\'' && literal[^1] == '\''))
{
literal = $"'{literal.Replace("'", "''", StringComparison.Ordinal)}'";
}

list.Add(literal);
}

return "(" + string.Join(",", list) + ")";
}

/// <summary>
/// Retrieves type associated to a segment.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ private static QueryToken ParseComplexOrCollectionAlias(QueryToken queryToken, I
string valueStr;
if (valueToken != null && (valueStr = valueToken.Value as string) != null && !string.IsNullOrEmpty(valueToken.OriginalText))
{
var lexer = new ExpressionLexer(valueToken.OriginalText, true /*moveToFirstToken*/, false /*useSemicolonDelimiter*/, true /*parsingFunctionParameters*/);
var lexer = new ExpressionLexer(valueToken.OriginalText, moveToFirstToken: true, useSemicolonDelimiter: false, parsingFunctionParameters: true);
if (lexer.CurrentToken.Kind == ExpressionTokenKind.BracketedExpression || lexer.CurrentToken.Kind == ExpressionTokenKind.BracedExpression)
{
object result = valueStr;
Expand Down
3 changes: 2 additions & 1 deletion src/Microsoft.OData.Core/UriParser/ODataUriParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -482,12 +482,13 @@ public ODataUri ParseUri()
ExceptionUtils.CheckArgumentNotNull(this.uri, "uri");

ODataPath path = this.ParsePath();
// NOTE: ParseCompute should be called before ParseSelectAndExpand because $compute may add computed properties to the select/expand clause.
ComputeClause compute = this.ParseCompute();
SelectExpandClause selectExpand = this.ParseSelectAndExpand();
FilterClause filter = this.ParseFilter();
OrderByClause orderBy = this.ParseOrderBy();
SearchClause search = this.ParseSearch();
ApplyClause apply = this.ParseApply();
ComputeClause compute = this.ParseCompute();
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Why not move 'ParseApply()'?

ParseApply() should be called first

long? top = this.ParseTop();
long? skip = this.ParseSkip();
long? index = this.ParseIndex();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,30 @@ public CollectionConstantNode(IEnumerable<object> objectCollection, string liter
}
}

/// <summary>
/// CreateS a CollectionConstantNode.
/// </summary>
/// <param name="nodeCollection">A ConstantNode collection.</param>
/// <param name="literalText">The literal text for this node's value, formatted according to the OData URI literal formatting rules.</param>
/// <param name="collectionType">The reference to the collection type.</param>
/// <exception cref="System.ArgumentNullException">Throws if the input literalText is null.</exception>
internal CollectionConstantNode(IEnumerable<ConstantNode> nodeCollection, string literalText, IEdmCollectionTypeReference collectionType)
{
ExceptionUtils.CheckArgumentNotNull(nodeCollection, nameof(nodeCollection));
ExceptionUtils.CheckArgumentStringNotNullOrEmpty(literalText, nameof(literalText));
ExceptionUtils.CheckArgumentNotNull(collectionType, nameof(collectionType));

this.LiteralText = literalText;
EdmCollectionType edmCollectionType = collectionType.Definition as EdmCollectionType;
this.itemType = edmCollectionType.ElementType;
this.collectionTypeReference = collectionType;

foreach (ConstantNode item in nodeCollection)
{
this.collection.Add(item);
}
}

/// <summary>
/// Gets the collection of ConstantNodes.
/// </summary>
Expand Down
Loading