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);