diff --git a/src/Microsoft.OData.Client/Microsoft.OData.Client.csproj b/src/Microsoft.OData.Client/Microsoft.OData.Client.csproj
index 18144f5028..6b48ebc2ea 100644
--- a/src/Microsoft.OData.Client/Microsoft.OData.Client.csproj
+++ b/src/Microsoft.OData.Client/Microsoft.OData.Client.csproj
@@ -95,6 +95,7 @@
+
diff --git a/src/Microsoft.OData.Client/PublicAPI/net8.0/PublicAPI.Unshipped.txt b/src/Microsoft.OData.Client/PublicAPI/net8.0/PublicAPI.Unshipped.txt
index e69de29bb2..fab29c97d7 100644
--- a/src/Microsoft.OData.Client/PublicAPI/net8.0/PublicAPI.Unshipped.txt
+++ b/src/Microsoft.OData.Client/PublicAPI/net8.0/PublicAPI.Unshipped.txt
@@ -0,0 +1,7 @@
+Microsoft.OData.Client.ALinq.UriParser.ISyntacticTreeVisitor.Visit(Microsoft.OData.Client.ALinq.UriParser.RootPathToken tokenIn) -> T
+Microsoft.OData.Client.ALinq.UriParser.QueryTokenKind.RootPath = 33 -> Microsoft.OData.Client.ALinq.UriParser.QueryTokenKind
+Microsoft.OData.Client.ALinq.UriParser.RootPathToken
+Microsoft.OData.Client.ALinq.UriParser.RootPathToken.RootPathToken() -> void
+Microsoft.OData.Client.ALinq.UriParser.RootPathToken.Segments.get -> System.Collections.Generic.IList
+override Microsoft.OData.Client.ALinq.UriParser.RootPathToken.Accept(Microsoft.OData.Client.ALinq.UriParser.ISyntacticTreeVisitor visitor) -> T
+override Microsoft.OData.Client.ALinq.UriParser.RootPathToken.Kind.get -> Microsoft.OData.Client.ALinq.UriParser.QueryTokenKind
\ No newline at end of file
diff --git a/src/Microsoft.OData.Core/PublicAPI/net8.0/PublicAPI.Unshipped.txt b/src/Microsoft.OData.Core/PublicAPI/net8.0/PublicAPI.Unshipped.txt
index e69de29bb2..aa68926177 100644
--- a/src/Microsoft.OData.Core/PublicAPI/net8.0/PublicAPI.Unshipped.txt
+++ b/src/Microsoft.OData.Core/PublicAPI/net8.0/PublicAPI.Unshipped.txt
@@ -0,0 +1,14 @@
+Microsoft.OData.UriParser.ISyntacticTreeVisitor.Visit(Microsoft.OData.UriParser.RootPathToken tokenIn) -> T
+Microsoft.OData.UriParser.QueryNodeKind.RootPath = 34 -> Microsoft.OData.UriParser.QueryNodeKind
+Microsoft.OData.UriParser.QueryTokenKind.RootPath = 33 -> Microsoft.OData.UriParser.QueryTokenKind
+Microsoft.OData.UriParser.RootPathNode
+Microsoft.OData.UriParser.RootPathNode.Path.get -> Microsoft.OData.UriParser.ODataPath
+Microsoft.OData.UriParser.RootPathNode.RootPathNode(Microsoft.OData.UriParser.ODataPath path, Microsoft.OData.Edm.IEdmTypeReference typeRef) -> void
+Microsoft.OData.UriParser.RootPathToken
+Microsoft.OData.UriParser.RootPathToken.RootPathToken() -> void
+Microsoft.OData.UriParser.RootPathToken.Segments.get -> System.Collections.Generic.IList
+override Microsoft.OData.UriParser.RootPathNode.Accept(Microsoft.OData.UriParser.QueryNodeVisitor visitor) -> T
+override Microsoft.OData.UriParser.RootPathNode.TypeReference.get -> Microsoft.OData.Edm.IEdmTypeReference
+override Microsoft.OData.UriParser.RootPathToken.Accept(Microsoft.OData.UriParser.ISyntacticTreeVisitor visitor) -> T
+override Microsoft.OData.UriParser.RootPathToken.Kind.get -> Microsoft.OData.UriParser.QueryTokenKind
+virtual Microsoft.OData.UriParser.QueryNodeVisitor.Visit(Microsoft.OData.UriParser.RootPathNode nodeIn) -> T
\ No newline at end of file
diff --git a/src/Microsoft.OData.Core/UriParser/Binders/MetadataBinder.cs b/src/Microsoft.OData.Core/UriParser/Binders/MetadataBinder.cs
index e91c06927d..a2fde588a8 100644
--- a/src/Microsoft.OData.Core/UriParser/Binders/MetadataBinder.cs
+++ b/src/Microsoft.OData.Core/UriParser/Binders/MetadataBinder.cs
@@ -12,6 +12,8 @@ namespace Microsoft.OData.UriParser
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Microsoft.OData.Core;
+ using Microsoft.OData.Edm;
+ using Microsoft.OData.Metadata;
#endregion Namespaces
@@ -201,6 +203,9 @@ protected internal QueryNode Bind(QueryToken token)
case QueryTokenKind.CountSegment:
result = this.BindCountSegment((CountSegmentToken)token);
break;
+ case QueryTokenKind.RootPath:
+ result = this.BindRootPath((RootPathToken)token);
+ break;
default:
throw new ODataException(Error.Format(SRResources.MetadataBinder_UnsupportedQueryTokenKind, token.Kind));
}
@@ -384,5 +389,41 @@ protected virtual QueryNode BindCountSegment(CountSegmentToken countSegmentToken
CountSegmentBinder countSegmentBinder = new CountSegmentBinder(this.Bind, this.BindingState);
return countSegmentBinder.BindCountSegment(countSegmentToken);
}
+
+ ///
+ /// Binds a RootPathToken.
+ ///
+ /// The RootPath token to bind.
+ /// The bound RootPath token.
+ protected virtual QueryNode BindRootPath(RootPathToken rootPathToken)
+ {
+ ODataPath path = ODataPathFactory.BindPath(rootPathToken.Segments, this.BindingState.Configuration);
+
+ ODataPathSegment lastSegment = path.LastSegment;
+ if (lastSegment == null)
+ {
+ throw new ODataException("Empty root path is not valid.");
+ }
+
+ // The $root literal can be used in expressions to refer to resources of the same service. It can be used as a single-valued expression or within complex or collection literals.
+ if (!lastSegment.SingleResult)
+ {
+ throw new ODataException("The $root literal can be used in expressions to refer to resources of the same service. It can be used as a single-valued expression or within complex or collection literals.");
+ }
+
+ // The EdmType could be null for Dynamic path segment
+ IEdmTypeReference typeReference = lastSegment.EdmType == null ? null : lastSegment.EdmType.ToTypeReference(true);
+ return new RootPathNode(path, typeReference);
+
+ // Keep them for discussion, will remove after discussion.
+ //// If it's collection segment, the typeReference should not be null and it should be the collection type reference
+ //IEdmCollectionTypeReference collectionTypeRef = typeReference as IEdmCollectionTypeReference;
+ //if (collectionTypeRef == null)
+ //{
+ // throw new ODataException("Empty root path is not valid.");
+ //}
+
+ //return new RootPathCollectionValueNode(path, collectionTypeRef);
+ }
}
}
\ No newline at end of file
diff --git a/src/Microsoft.OData.Core/UriParser/Parsers/UriQueryExpressionParser.cs b/src/Microsoft.OData.Core/UriParser/Parsers/UriQueryExpressionParser.cs
index 8b28d03930..29b824a316 100644
--- a/src/Microsoft.OData.Core/UriParser/Parsers/UriQueryExpressionParser.cs
+++ b/src/Microsoft.OData.Core/UriParser/Parsers/UriQueryExpressionParser.cs
@@ -1064,6 +1064,12 @@ private QueryToken ParsePrimary()
expr = this.ParsePrimaryStart();
}
+ // Pase the $root path, for example: '$root/people(12)'
+ if (expr != null && expr.Kind == QueryTokenKind.RootPath)
+ {
+ return ParseRootPath((RootPathToken)expr);
+ }
+
while (this.lexer.CurrentToken.Kind == ExpressionTokenKind.Slash)
{
this.lexer.NextToken();
@@ -1138,6 +1144,66 @@ private QueryToken ParsePrimaryStart()
}
}
+ private RootPathToken ParseRootPath(RootPathToken rootPathToken)
+ {
+ while (this.lexer.CurrentToken.Kind == ExpressionTokenKind.Slash)
+ {
+ this.lexer.NextToken(); // read over the '/'
+
+ // a function call or an entity with key value is treated same as function call.
+ bool identifierIsFunction = this.lexer.ExpandIdentifierAsFunction();
+ if (identifierIsFunction && TryParseIdentifierAsFunction(lexer, out string result))
+ {
+ rootPathToken.Segments.Add(result);
+ this.lexer.NextToken();
+ continue;
+ }
+
+ if (this.lexer.PeekNextToken().Kind == ExpressionTokenKind.Dot)
+ {
+ ReadOnlySpan dotIdentifier = this.lexer.ReadDottedIdentifier(false);
+ rootPathToken.Segments.Add(dotIdentifier.ToString());
+ //lexer.NextToken();
+ continue;
+ }
+
+ //lexer.ValidateToken(ExpressionTokenKind.Identifier); // could be integerLiteral or others, for example: /people/1234
+ rootPathToken.Segments.Add(this.lexer.CurrentToken.Text.ToString());
+ this.lexer.NextToken();
+ }
+
+ return rootPathToken;
+ }
+
+ private static bool TryParseIdentifierAsFunction(ExpressionLexer lexer, out string result)
+ {
+ result = null;
+ ReadOnlySpan functionName;
+
+ ExpressionLexer.ExpressionLexerPosition position = lexer.SnapshotPosition();
+
+ if (lexer.PeekNextToken().Kind == ExpressionTokenKind.Dot)
+ {
+ // handle the case where we prefix a function with its namespace.
+ functionName = lexer.ReadDottedIdentifier(false);
+ }
+ else
+ {
+ functionName = lexer.CurrentToken.Span;
+ lexer.NextToken();
+ }
+
+ if (lexer.CurrentToken.Kind != ExpressionTokenKind.OpenParen)
+ {
+ lexer.RestorePosition(position);
+ return false;
+ }
+
+ string parameters = lexer.AdvanceThroughBalancedParentheticalExpression();
+ result = $"{functionName}{parameters}";
+ return true;
+ }
+
///
/// Parses parenthesized expressions.
///
@@ -1250,6 +1316,17 @@ private QueryToken ParseSegment(QueryToken parent)
{
string propertyName = this.lexer.CurrentToken.GetIdentifier().ToString();
this.lexer.NextToken();
+
+ if (string.Equals(propertyName, "$root", enableCaseInsensitiveBuiltinIdentifier ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal))
+ {
+ if (parent != null)
+ {
+ throw new ODataException("$root should be the top-level segment");
+ }
+
+ return new RootPathToken();
+ }
+
if (this.parameters.Contains(propertyName) && parent == null)
{
return new RangeVariableToken(propertyName);
diff --git a/src/Microsoft.OData.Core/UriParser/SemanticAst/RootPathNode.cs b/src/Microsoft.OData.Core/UriParser/SemanticAst/RootPathNode.cs
new file mode 100644
index 0000000000..73499b21c0
--- /dev/null
+++ b/src/Microsoft.OData.Core/UriParser/SemanticAst/RootPathNode.cs
@@ -0,0 +1,56 @@
+//---------------------------------------------------------------------
+//
+// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
+//
+//---------------------------------------------------------------------
+
+namespace Microsoft.OData.UriParser
+{
+ using Microsoft.OData.Edm;
+
+ ///
+ /// The $root literal can be used in expressions to refer to resources of the same service. It can be used as a single-valued expression or within complex or collection literals.
+ ///
+ public sealed class RootPathNode : SingleValueNode
+ {
+ ///
+ /// Created a RootPathNode with the given path and the given type.
+ ///
+ /// The OData path.
+ /// The path type. It could be null if the last segment is dynamic.
+ public RootPathNode(ODataPath path, IEdmTypeReference typeRef)
+ {
+ ExceptionUtils.CheckArgumentNotNull(path, "path");
+ Path = path;
+ TypeReference = typeRef;
+ }
+
+ ///
+ /// Gets the OData path.
+ ///
+ public ODataPath Path { get;}
+
+ ///
+ /// Gets the type of the single value this node represents.
+ ///
+ public override IEdmTypeReference TypeReference { get; }
+
+ ///
+ /// Gets the kind of this node.
+ ///
+ internal override InternalQueryNodeKind InternalKind => InternalQueryNodeKind.RootPath;
+
+ ///
+ /// Accept a to walk a tree of s.
+ ///
+ /// Type that the visitor will return after visiting this token.
+ /// An implementation of the visitor interface.
+ /// An object whose type is determined by the type parameter of the visitor.
+ /// Throws if the input visitor is null.
+ public override T Accept(QueryNodeVisitor visitor)
+ {
+ ExceptionUtils.CheckArgumentNotNull(visitor, "visitor");
+ return visitor.Visit(this);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.OData.Core/UriParser/SyntacticAst/QueryTokenKind.cs b/src/Microsoft.OData.Core/UriParser/SyntacticAst/QueryTokenKind.cs
index 56d1b2bc6b..a436557541 100644
--- a/src/Microsoft.OData.Core/UriParser/SyntacticAst/QueryTokenKind.cs
+++ b/src/Microsoft.OData.Core/UriParser/SyntacticAst/QueryTokenKind.cs
@@ -159,6 +159,11 @@ public enum QueryTokenKind
///
/// $count segment
///
- CountSegment = 32
+ CountSegment = 32,
+
+ ///
+ /// $root path
+ ///
+ RootPath = 33
}
}
\ No newline at end of file
diff --git a/src/Microsoft.OData.Core/UriParser/SyntacticAst/RootPathToken.cs b/src/Microsoft.OData.Core/UriParser/SyntacticAst/RootPathToken.cs
new file mode 100644
index 0000000000..506059fe66
--- /dev/null
+++ b/src/Microsoft.OData.Core/UriParser/SyntacticAst/RootPathToken.cs
@@ -0,0 +1,40 @@
+//---------------------------------------------------------------------
+//
+// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
+//
+//---------------------------------------------------------------------
+
+using System.Collections.Generic;
+#if ODATA_CLIENT
+namespace Microsoft.OData.Client.ALinq.UriParser
+#else
+namespace Microsoft.OData.UriParser
+#endif
+{
+ ///
+ /// Lexical token representing a $root path.
+ ///
+ public sealed class RootPathToken : QueryToken
+ {
+ ///
+ /// The kind of the query token.
+ ///
+ public override QueryTokenKind Kind => QueryTokenKind.RootPath;
+
+ ///
+ /// Path segments (without the $root leading segment)
+ ///
+ public IList Segments { get; } = new List();
+
+ ///
+ /// Accept a to walk a tree of s.
+ ///
+ /// Type that the visitor will return after visiting this token.
+ /// An implementation of the visitor interface.
+ /// An object whose type is determined by the type parameter of the visitor.
+ public override T Accept(ISyntacticTreeVisitor visitor)
+ {
+ return visitor.Visit(this);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.OData.Core/UriParser/TreeNodeKinds/QueryNodeKind.cs b/src/Microsoft.OData.Core/UriParser/TreeNodeKinds/QueryNodeKind.cs
index 002e306da1..45c1e10556 100644
--- a/src/Microsoft.OData.Core/UriParser/TreeNodeKinds/QueryNodeKind.cs
+++ b/src/Microsoft.OData.Core/UriParser/TreeNodeKinds/QueryNodeKind.cs
@@ -183,6 +183,11 @@ public enum QueryNodeKind
/// Node that represents a collection of constants.
///
CollectionConstant = InternalQueryNodeKind.CollectionConstant,
+
+ ///
+ /// Node that represents a $root path
+ ///
+ RootPath = InternalQueryNodeKind.RootPath,
}
///
@@ -359,5 +364,10 @@ internal enum InternalQueryNodeKind
/// Node that represents a collection of constants.
///
CollectionConstant = 33,
+
+ ///
+ /// Node that represetns a $root path
+ ///
+ RootPath,
}
}
\ No newline at end of file
diff --git a/src/Microsoft.OData.Core/UriParser/Visitors/ISyntacticTreeVisitor.cs b/src/Microsoft.OData.Core/UriParser/Visitors/ISyntacticTreeVisitor.cs
index 29ef90e9b3..faf2738c22 100644
--- a/src/Microsoft.OData.Core/UriParser/Visitors/ISyntacticTreeVisitor.cs
+++ b/src/Microsoft.OData.Core/UriParser/Visitors/ISyntacticTreeVisitor.cs
@@ -192,5 +192,12 @@ public interface ISyntacticTreeVisitor
/// The GroupByToken to bind
/// A T node bound to this GroupByToken
T Visit(GroupByToken tokenIn);
+
+ ///
+ /// Visits a RootPathToken
+ ///
+ /// The RootPathToken to bind
+ /// A user defined value
+ T Visit(RootPathToken tokenIn);
}
}
\ No newline at end of file
diff --git a/src/Microsoft.OData.Core/UriParser/Visitors/QueryNodeVisitor.cs b/src/Microsoft.OData.Core/UriParser/Visitors/QueryNodeVisitor.cs
index 5fc9b7ac5e..93cc958529 100644
--- a/src/Microsoft.OData.Core/UriParser/Visitors/QueryNodeVisitor.cs
+++ b/src/Microsoft.OData.Core/UriParser/Visitors/QueryNodeVisitor.cs
@@ -313,5 +313,15 @@ public virtual T Visit(InNode nodeIn)
{
throw new NotImplementedException();
}
+
+ ///
+ /// Visit an RootPathNode
+ ///
+ /// the node to visit
+ /// Defined by the implementer
+ public virtual T Visit(RootPathNode nodeIn)
+ {
+ throw new NotImplementedException();
+ }
}
}
\ No newline at end of file
diff --git a/src/Microsoft.OData.Core/UriParser/Visitors/SyntacticTreeVisitor.cs b/src/Microsoft.OData.Core/UriParser/Visitors/SyntacticTreeVisitor.cs
index 98215f4cb7..6b5bd61e17 100644
--- a/src/Microsoft.OData.Core/UriParser/Visitors/SyntacticTreeVisitor.cs
+++ b/src/Microsoft.OData.Core/UriParser/Visitors/SyntacticTreeVisitor.cs
@@ -288,5 +288,15 @@ public virtual T Visit(ComputeExpressionToken tokenIn)
{
throw new NotImplementedException();
}
+
+ ///
+ /// Visits a RootPathToken
+ ///
+ /// The RootPathToken to bind
+ /// A user defined value
+ public virtual T Visit(RootPathToken tokenIn)
+ {
+ throw new NotImplementedException();
+ }
}
}
diff --git a/test/FunctionalTests/Microsoft.OData.Core.Tests/ScenarioTests/UriParser/FilterAndOrderByFunctionalTests.cs b/test/FunctionalTests/Microsoft.OData.Core.Tests/ScenarioTests/UriParser/FilterAndOrderByFunctionalTests.cs
index 83ec39b2f2..3ff466d7f6 100644
--- a/test/FunctionalTests/Microsoft.OData.Core.Tests/ScenarioTests/UriParser/FilterAndOrderByFunctionalTests.cs
+++ b/test/FunctionalTests/Microsoft.OData.Core.Tests/ScenarioTests/UriParser/FilterAndOrderByFunctionalTests.cs
@@ -919,7 +919,58 @@ public void AnyAllOnAPrimitiveShouldThrowODataException()
anyOnPrimitiveType.Throws(SRResources.MetadataBinder_LambdaParentMustBeCollection);
}
-#region Custom Functions
+ [Theory]
+ [InlineData("Name eq $root/People(1245)/Name")]
+ [InlineData("Name eq $root/People/1245/Name")]
+ public void ParseDollarRootPath_WithPropertyAccess_InFilter(string filter)
+ {
+ var filterQueryNode = ParseFilter(filter, HardCodedTestModel.TestModel, HardCodedTestModel.GetPersonType(), HardCodedTestModel.GetPeopleSet());
+ BinaryOperatorNode bod = filterQueryNode.Expression.ShouldBeBinaryOperatorNode(BinaryOperatorKind.Equal);
+ bod.Left.ShouldBeSingleValuePropertyAccessQueryNode(HardCodedTestModel.GetPersonNameProp());
+ RootPathNode rootPathNode = bod.Right.ShouldBeRootPathNode();
+ Assert.Equal(3, rootPathNode.Path.Count);
+
+ rootPathNode.Path.Segments[0].ShouldBeEntitySetSegment(HardCodedTestModel.GetPeopleSet());
+ rootPathNode.Path.Segments[1].ShouldBeKeySegment(new KeyValuePair("ID", 1245));
+ rootPathNode.Path.Segments[2].ShouldBePropertySegment(HardCodedTestModel.GetPersonNameProp());
+ }
+
+ [Fact]
+ public void ParseDollarRootPath_WithTypeCastAndPropertyAccess_InFilter()
+ {
+ string filter = "Name eq $root/People/1245/Fully.Qualified.Namespace.Employee/WorkEmail";
+
+ var filterQueryNode = ParseFilter(filter, HardCodedTestModel.TestModel, HardCodedTestModel.GetPersonType(), HardCodedTestModel.GetPeopleSet());
+
+ BinaryOperatorNode bod = filterQueryNode.Expression.ShouldBeBinaryOperatorNode(BinaryOperatorKind.Equal);
+ bod.Left.ShouldBeSingleValuePropertyAccessQueryNode(HardCodedTestModel.GetPersonNameProp());
+ RootPathNode rootPathNode = bod.Right.ShouldBeRootPathNode();
+ Assert.Equal(4, rootPathNode.Path.Count);
+
+ rootPathNode.Path.Segments[0].ShouldBeEntitySetSegment(HardCodedTestModel.GetPeopleSet());
+ rootPathNode.Path.Segments[1].ShouldBeKeySegment(new KeyValuePair("ID", 1245));
+ rootPathNode.Path.Segments[2].ShouldBeTypeSegment(HardCodedTestModel.GetEmployeeType(), HardCodedTestModel.GetPersonType());
+ rootPathNode.Path.Segments[3].ShouldBePropertySegment(HardCodedTestModel.GetEmployeeWorkEmailProp());
+ }
+
+ [Fact]
+ public void ParseDollarRootPath_WithDollarCount_InFilter()
+ {
+ string filter = "$root/People/$count lt ID";
+
+ var filterQueryNode = ParseFilter(filter, HardCodedTestModel.TestModel, HardCodedTestModel.GetPersonType(), HardCodedTestModel.GetPeopleSet());
+ BinaryOperatorNode bod = filterQueryNode.Expression.ShouldBeBinaryOperatorNode(BinaryOperatorKind.LessThan);
+ ConvertNode convertNode = bod.Right.ShouldBeConvertQueryNode(EdmCoreModel.Instance.GetInt32(true));
+ convertNode.Source.ShouldBeSingleValuePropertyAccessQueryNode(HardCodedTestModel.GetPersonIdProp());
+
+ RootPathNode rootPathNode = bod.Left.ShouldBeRootPathNode();
+ Assert.Equal(2, rootPathNode.Path.Count);
+
+ rootPathNode.Path.Segments[0].ShouldBeEntitySetSegment(HardCodedTestModel.GetPeopleSet());
+ Assert.Same(CountSegment.Instance, rootPathNode.Path.Segments[1]);
+ }
+
+ #region Custom Functions
[Fact]
public void FunctionWithASingleOverloadWorksInFilter()
diff --git a/test/FunctionalTests/Microsoft.OData.Core.Tests/ScenarioTests/UriParser/ParameterAliasFunctionalTests.cs b/test/FunctionalTests/Microsoft.OData.Core.Tests/ScenarioTests/UriParser/ParameterAliasFunctionalTests.cs
index a4c8e0d18b..4b3697de9d 100644
--- a/test/FunctionalTests/Microsoft.OData.Core.Tests/ScenarioTests/UriParser/ParameterAliasFunctionalTests.cs
+++ b/test/FunctionalTests/Microsoft.OData.Core.Tests/ScenarioTests/UriParser/ParameterAliasFunctionalTests.cs
@@ -237,6 +237,20 @@ public void ParsePath_AliasInUnboundFunction_QuotesWithinDoubleQuotedStringsInJs
});
}
+ [Fact]
+ public void ParsePath_AliasInUnboundFunction_WithinDollarRootPath()
+ {
+ ParseUriAndVerify(
+ new Uri("http://gobbledygook/GetRatings(films=@c)?@c=[$root/Films(1),$root/Films(3)]"),
+ (oDataPath, filterClause, orderByClause, selectExpandClause, aliasNodes) =>
+ {
+ oDataPath.LastSegment.ShouldBeOperationImportSegment(HardCodedTestModel.GetFunctionImportForGetRatings());
+
+ var constNode = Assert.IsType(aliasNodes["@c"]);
+ Assert.Equal("[$root/Films(1),$root/Films(3)]", constNode.Value);
+ });
+ }
+
#endregion
#region alias in filter
diff --git a/test/FunctionalTests/Microsoft.OData.Core.Tests/UriParser/HardCodedTestModel.cs b/test/FunctionalTests/Microsoft.OData.Core.Tests/UriParser/HardCodedTestModel.cs
index e52f70e281..8206e42678 100644
--- a/test/FunctionalTests/Microsoft.OData.Core.Tests/UriParser/HardCodedTestModel.cs
+++ b/test/FunctionalTests/Microsoft.OData.Core.Tests/UriParser/HardCodedTestModel.cs
@@ -740,6 +740,7 @@ internal static IEdmModel GetEdmModel()
var FullyQualifiedNamespaceContextPet4Set = FullyQualifiedNamespaceContext.AddEntitySet("Pet4Set", FullyQualifiedNamespacePet4);
var FullyQualifiedNamespaceContextPet5Set = FullyQualifiedNamespaceContext.AddEntitySet("Pet5Set", FullyQualifiedNamespacePet5);
var FullyQualifiedNamespaceContextPet6Set = FullyQualifiedNamespaceContext.AddEntitySet("Pet6Set", FullyQualifiedNamespacePet6);
+ var FullyQualifiedNamespaceContextFilmSet = FullyQualifiedNamespaceContext.AddEntitySet("Films", FullyQualifiedNamespaceFilm);
var FullyQualifiedNamespaceContextChimera = FullyQualifiedNamespaceContext.AddEntitySet("Chimeras", FullyQualifiedNamespaceChimera);
FullyQualifiedNamespaceContext.AddEntitySet("Shapes", fullyQualifiedNamespaceShape);
@@ -912,6 +913,7 @@ internal class HardCodedTestModelXml
+
@@ -1658,6 +1660,11 @@ public static IEdmEntitySet GetPet6Set()
return TestModel.FindEntityContainer("Context").FindEntitySet("Pet6Set");
}
+ public static IEdmEntitySet GetFilmSet()
+ {
+ return TestModel.FindEntityContainer("Context").FindEntitySet("Films");
+ }
+
public static IEdmEntitySet GetPeopleSet()
{
return TestModel.FindEntityContainer("Context").FindEntitySet("People");
diff --git a/test/FunctionalTests/Microsoft.OData.Core.Tests/UriParser/NodeAssertions.cs b/test/FunctionalTests/Microsoft.OData.Core.Tests/UriParser/NodeAssertions.cs
index e648675ae0..14344aafd4 100644
--- a/test/FunctionalTests/Microsoft.OData.Core.Tests/UriParser/NodeAssertions.cs
+++ b/test/FunctionalTests/Microsoft.OData.Core.Tests/UriParser/NodeAssertions.cs
@@ -207,6 +207,14 @@ public static SingleValuePropertyAccessNode ShouldBeSingleValuePropertyAccessQue
return propertyAccessNode;
}
+ public static RootPathNode ShouldBeRootPathNode(this QueryNode node)
+ {
+ Assert.NotNull(node);
+ var rootPathNode = Assert.IsType(node);
+ Assert.NotNull(rootPathNode.Path);
+ return rootPathNode;
+ }
+
public static SingleComplexNode ShouldBeSingleComplexNode(this QueryNode node, IEdmProperty expectedProperty)
{
Assert.NotNull(node);