/// <summary> /// Binds a LambdaToken to metadata. /// </summary> /// <param name="lambdaToken">Token to bind.</param> /// <param name="state">Object to hold the state of binding.</param> /// <returns>A metadata bound any or all node.</returns> internal LambdaNode BindLambdaToken(LambdaToken lambdaToken, BindingState state) { ExceptionUtils.CheckArgumentNotNull(lambdaToken, "LambdaToken"); ExceptionUtils.CheckArgumentNotNull(state, "state"); // Start by binding the parent token CollectionNode parent = this.BindParentToken(lambdaToken.Parent); RangeVariable rangeVariable = null; // Add the lambda variable to the stack if (lambdaToken.Parameter != null) { rangeVariable = NodeFactory.CreateParameterNode(lambdaToken.Parameter, parent); state.RangeVariables.Push(rangeVariable); } // Bind the expression SingleValueNode expression = this.BindExpressionToken(lambdaToken.Expression); // Create the node LambdaNode lambdaNode = NodeFactory.CreateLambdaNode(state, parent, expression, rangeVariable, lambdaToken.Kind); // Remove the lambda variable as it is now out of scope if (rangeVariable != null) { state.RangeVariables.Pop(); } return(lambdaNode); }
/// <summary> /// Constructs a new <see cref="CountNode"/>. /// </summary> /// <param name="source">The value containing the property.</param> /// <exception cref="System.ArgumentNullException">Throws if the input source is null.</exception> public CountNode(CollectionNode source) : this(source, null, null) { ExceptionUtils.CheckArgumentNotNull(source, "source"); this.source = source; }
/// <summary> /// Creates an AnyNode or an AllNode from the given /// </summary> /// <param name="state">State of binding.</param> /// <param name="parent">Parent node to the lambda.</param> /// <param name="lambdaExpression">Bound Lambda expression.</param> /// <param name="newRangeVariable">The new range variable being added by this lambda node.</param> /// <param name="queryTokenKind">Token kind.</param> /// <returns>A new LambdaNode bound to metadata.</returns> internal static LambdaNode CreateLambdaNode( BindingState state, CollectionNode parent, SingleValueNode lambdaExpression, RangeVariable newRangeVariable, QueryTokenKind queryTokenKind) { LambdaNode lambdaNode; if (queryTokenKind == QueryTokenKind.Any) { lambdaNode = new AnyNode(new Collection <RangeVariable>(state.RangeVariables.ToList()), newRangeVariable) { Body = lambdaExpression, Source = parent, }; } else { Debug.Assert(queryTokenKind == QueryTokenKind.All, "LambdaQueryNodes must be Any or All only."); lambdaNode = new AllNode(new Collection <RangeVariable>(state.RangeVariables.ToList()), newRangeVariable) { Body = lambdaExpression, Source = parent, }; } return(lambdaNode); }
/// <summary> /// Binds an Count segment token. /// </summary> /// <param name="countSegmentToken">The Count segment token to bind.</param> /// <param name="state">State of the metadata binding.</param> /// <returns>The bound Count segment token.</returns> internal QueryNode BindCountSegment(CountSegmentToken countSegmentToken) { ExceptionUtils.CheckArgumentNotNull(countSegmentToken, "countSegmentToken"); QueryNode source = this.bindMethod(countSegmentToken.NextToken); CollectionNode node = source as CollectionNode; if (node == null) { throw new ODataException(ODataErrorStrings.MetadataBinder_CountSegmentNextTokenNotCollectionValue()); } FilterClause filterClause = null; SearchClause searchClause = null; BindingState innerBindingState = new BindingState(state.Configuration); innerBindingState.ImplicitRangeVariable = NodeFactory.CreateParameterNode(ExpressionConstants.It, node); MetadataBinder binder = new MetadataBinder(innerBindingState); if (countSegmentToken.FilterOption != null) { FilterBinder filterBinder = new FilterBinder(binder.Bind, innerBindingState); filterClause = filterBinder.BindFilter(countSegmentToken.FilterOption); } if (countSegmentToken.SearchOption != null) { SearchBinder searchBinder = new SearchBinder(binder.Bind); searchClause = searchBinder.BindSearch(countSegmentToken.SearchOption); } return(new CountNode(node, filterClause, searchClause)); }
/// <summary> /// Retrieve CollectionNode bound with given query token. /// </summary> /// <param name="queryToken">The query token</param> /// <param name="expectedType">The expected type that this collection holds</param> /// <param name="model">The Edm model</param> /// <returns>The corresponding CollectionNode</returns> private CollectionNode GetCollectionOperandFromToken(QueryToken queryToken, IEdmTypeReference expectedType, IEdmModel model) { CollectionNode operand = null; LiteralToken literalToken = queryToken as LiteralToken; if (literalToken != null) { string originalLiteralText = literalToken.OriginalText; // Parentheses-based collections are not standard JSON but bracket-based ones are. // Temporarily switch our collection to bracket-based so that the JSON reader will // correctly parse the collection. Then pass the original literal text to the token. string bracketLiteralText = originalLiteralText; if (bracketLiteralText[0] == '(') { Debug.Assert(bracketLiteralText[bracketLiteralText.Length - 1] == ')', "Collection with opening '(' should have corresponding ')'"); StringBuilder replacedText = new StringBuilder(bracketLiteralText); replacedText[0] = '['; replacedText[replacedText.Length - 1] = ']'; bracketLiteralText = replacedText.ToString(); Debug.Assert(expectedType.IsCollection()); string expectedTypeFullName = expectedType.Definition.AsElementType().FullTypeName(); if (expectedTypeFullName.Equals("Edm.String")) { // For collection of strings, need to convert single-quoted string to double-quoted string, // and also, per ABNF, a single quote within a string literal is "encoded" as two consecutive single quotes in either // literal or percent - encoded representation. // Sample: ['a''bc','''def','xyz'''] ==> ["a'bc","'def","xyz'"], which is legitimate Json format. bracketLiteralText = NormalizeStringCollectionItems(bracketLiteralText); } else if (expectedTypeFullName.Equals("Edm.Guid")) { // For collection of Guids, need to convert the Guid literals to single-quoted form, so that it is compatible // with the Json reader used for deserialization. // Sample: [D01663CF-EB21-4A0E-88E0-361C10ACE7FD, 492CF54A-84C9-490C-A7A4-B5010FAD8104] // ==> ['D01663CF-EB21-4A0E-88E0-361C10ACE7FD', '492CF54A-84C9-490C-A7A4-B5010FAD8104'] bracketLiteralText = NormalizeGuidCollectionItems(bracketLiteralText); } } object collection = ODataUriConversionUtils.ConvertFromCollectionValue(bracketLiteralText, model, expectedType); LiteralToken collectionLiteralToken = new LiteralToken(collection, originalLiteralText, expectedType); operand = this.bindMethod(collectionLiteralToken) as CollectionConstantNode; } else { operand = this.bindMethod(queryToken) as CollectionNode; } if (operand == null) { throw new ODataException(ODataErrorStrings.MetadataBinder_RightOperandNotCollectionValue); } return(operand); }
protected string Bind(QueryNode node) { CollectionNode collectionNode = node as CollectionNode; SingleValueNode singleValueNode = node as SingleValueNode; if (collectionNode != null) { switch (node.Kind) { case QueryNodeKind.CollectionNavigationNode: CollectionNavigationNode navigationNode = node as CollectionNavigationNode; return(BindNavigationPropertyNode(navigationNode.Source, navigationNode.NavigationProperty)); case QueryNodeKind.CollectionPropertyAccess: return(BindCollectionPropertyAccessNode(node as CollectionPropertyAccessNode)); } } else if (singleValueNode != null) { switch (node.Kind) { case QueryNodeKind.BinaryOperator: return(BindBinaryOperatorNode(node as BinaryOperatorNode)); case QueryNodeKind.Constant: return(BindConstantNode(node as ConstantNode)); case QueryNodeKind.Convert: return(BindConvertNode(node as ConvertNode)); //case QueryNodeKind.EntityRangeVariableReference: // return BindRangeVariable((node as EntityRangeVariableReferenceNode).RangeVariable); //case QueryNodeKind.NonentityRangeVariableReference: // return BindRangeVariable((node as NonentityRangeVariableReferenceNode).RangeVariable); case QueryNodeKind.SingleValuePropertyAccess: return(BindPropertyAccessQueryNode(node as SingleValuePropertyAccessNode)); case QueryNodeKind.UnaryOperator: return(BindUnaryOperatorNode(node as UnaryOperatorNode)); case QueryNodeKind.SingleValueFunctionCall: return(BindSingleValueFunctionCallNode(node as SingleValueFunctionCallNode)); case QueryNodeKind.SingleNavigationNode: SingleNavigationNode navigationNode = node as SingleNavigationNode; return(BindNavigationPropertyNode(navigationNode.Source, navigationNode.NavigationProperty)); case QueryNodeKind.Any: return(BindAnyNode(node as AnyNode)); case QueryNodeKind.All: return(BindAllNode(node as AllNode)); } } throw new NotSupportedException(String.Format("Nodes of type {0} are not supported", node.Kind)); }
/// <summary> /// Constructs a new <see cref="CountNode"/>. /// </summary> /// <param name="source">The value containing the property.</param> /// <param name="filterOption">The filter option.</param> /// <exception cref="System.ArgumentNullException">Throws if the input source is null.</exception> public CountNode(CollectionNode source, FilterClause filterOption) { ExceptionUtils.CheckArgumentNotNull(source, "source"); FilterOption = filterOption; this.source = source; }
/// <summary> /// Constructs a new <see cref="CountNode"/>. /// </summary> /// <param name="source">The value containing the property.</param> /// <param name="filterClause">The <see cref="Microsoft.OData.UriParser.FilterClause"/>in the count node.</param> /// <param name="searchClause">The <see cref="Microsoft.OData.UriParser.SearchClause"/>in the count node.</param> /// <exception cref="System.ArgumentNullException">Throws if the input source is null.</exception> public CountNode(CollectionNode source, FilterClause filterClause, SearchClause searchClause) { ExceptionUtils.CheckArgumentNotNull(source, "source"); this.source = source; this.filterClause = filterClause; this.searchClause = searchClause; }
/// <summary> /// Binds a an end path token into a PropertyAccessToken, OpenPropertyToken, or FunctionCallToken. /// </summary> /// <param name="endPathToken">The property access token to bind.</param> /// <returns>A Query node representing this endpath token, bound to metadata.</returns> internal QueryNode BindEndPath(EndPathToken endPathToken) { ExceptionUtils.CheckArgumentNotNull(endPathToken, "EndPathToken"); ExceptionUtils.CheckArgumentStringNotNullOrEmpty(endPathToken.Identifier, "EndPathToken.Identifier"); // Set the parent (get the parent type, so you can check whether the Identifier inside EndPathToken really is legit offshoot of the parent type) QueryNode parent = this.DetermineParentNode(endPathToken); QueryNode boundFunction; SingleValueNode singleValueParent = parent as SingleValueNode; if (singleValueParent == null) { if (functionCallBinder.TryBindEndPathAsFunctionCall(endPathToken, parent, state, out boundFunction)) { return(boundFunction); } // Collection with any or all expression is already supported and handled separately. // Add support of collection with $count segment. CollectionNode colNode = parent as CollectionNode; if (colNode != null && endPathToken.Identifier.Equals(UriQueryConstants.CountSegment)) { // create a collection count node for collection node property. return(new CountNode(colNode)); } throw new ODataException(ODataErrorStrings.MetadataBinder_PropertyAccessSourceNotSingleValue(endPathToken.Identifier)); } // Now that we have the parent type, can find its corresponding EDM type IEdmStructuredTypeReference structuredParentType = singleValueParent.TypeReference == null ? null : singleValueParent.TypeReference.AsStructuredOrNull(); IEdmProperty property = structuredParentType == null ? null : this.Resolver.ResolveProperty(structuredParentType.StructuredDefinition(), endPathToken.Identifier); if (property != null) { return(GeneratePropertyAccessQueryNode(singleValueParent as SingleResourceNode, property, state)); } if (endPathToken.Identifier == ExpressionConstants.QueryOptionCount) { return(new CountVirtualPropertyNode()); } if (functionCallBinder.TryBindEndPathAsFunctionCall(endPathToken, singleValueParent, state, out boundFunction)) { return(boundFunction); } return(GeneratePropertyAccessQueryForOpenType(endPathToken, singleValueParent)); }
/// <summary> /// Binds an In operator token. /// </summary> /// <param name="inToken">The In operator token to bind.</param> /// <param name="state">State of the metadata binding.</param> /// <returns>The bound In operator token.</returns> internal QueryNode BindInOperator(InToken inToken, BindingState state) { ExceptionUtils.CheckArgumentNotNull(inToken, "inToken"); SingleValueNode left = this.GetSingleValueOperandFromToken(inToken.Left); CollectionNode right = this.GetCollectionOperandFromToken( inToken.Right, new EdmCollectionTypeReference(new EdmCollectionType(left.TypeReference)), state.Model); return(new InNode(left, right)); }
/// <summary> /// Creates a ParameterQueryNode for an explicit parameter. /// </summary> /// <param name="parameter">Name of the parameter.</param> /// <param name="nodeToIterateOver">CollectionNode that the parameter is iterating over.</param> /// <returns>A new RangeVariable.</returns> internal static RangeVariable CreateParameterNode(string parameter, CollectionNode nodeToIterateOver) { IEdmTypeReference elementType = nodeToIterateOver.ItemType; if (elementType != null && elementType.IsStructured()) { var collectionResourceNode = nodeToIterateOver as CollectionResourceNode; Debug.Assert(collectionResourceNode != null, "IF the element type was structured, the node type should be a resource collection"); return(new ResourceRangeVariable(parameter, elementType as IEdmStructuredTypeReference, collectionResourceNode)); } return(new NonResourceRangeVariable(parameter, elementType, null)); }
/// <summary> /// Create a InNode /// </summary> /// <param name="left">The left operand.</param> /// <param name="right">The right operand.</param> /// <exception cref="System.ArgumentNullException">Throws if the left or right inputs are null.</exception> /// <exception cref="ODataException">Throws if the right operand single item type isn't the same type as the left operand.</exception> public InNode(SingleValueNode left, CollectionNode right) { ExceptionUtils.CheckArgumentNotNull(left, "left"); ExceptionUtils.CheckArgumentNotNull(right, "right"); this.left = left; this.right = right; if (!this.left.GetEdmTypeReference().IsAssignableFrom(this.right.ItemType) && !this.right.ItemType.IsAssignableFrom(this.left.GetEdmTypeReference())) { throw new ArgumentException(ODataErrorStrings.Nodes_InNode_CollectionItemTypeMustBeSameAsSingleItemType( this.right.ItemType.FullName(), this.left.GetEdmTypeReference().FullName())); } }
/// <summary> /// Creates a <see cref="NonResourceRangeVariable"/>. /// </summary> /// <param name="name"> The name of the associated range variable.</param> /// <param name="typeReference">The type of the value the range variable represents.</param> /// <param name="collectionNode">The collection that this rangeVariable node iterates over, can be null in the case of single value nodes.</param> /// <exception cref="System.ArgumentNullException">Throws if the input name is null.</exception> /// <exception cref="ArgumentException">Throws if the input type reference is an entity type.</exception> public NonResourceRangeVariable(string name, IEdmTypeReference typeReference, CollectionNode collectionNode) { ExceptionUtils.CheckArgumentNotNull(name, "name"); this.name = name; if (typeReference != null) { if (typeReference.Definition.TypeKind.IsStructured()) { // TODO: update message #644 throw new ArgumentException( ODataErrorStrings.Nodes_NonentityParameterQueryNodeWithEntityType(typeReference.FullName())); } } this.typeReference = typeReference; this.collectionNode = collectionNode; }
/// <summary> /// Retrieves the type reference associated to a segment. /// </summary> /// <param name="segment">The node to retrive the type reference from.</param> /// <returns>The Type reference of the node (item type reference for collections).</returns> internal static IEdmTypeReference GetEdmTypeReference(this QueryNode segment) { SingleValueNode singleNode = segment as SingleValueNode; if (singleNode != null) { return(singleNode.TypeReference); } CollectionNode collectionNode = segment as CollectionNode; if (collectionNode != null) { return(collectionNode.ItemType); } return(null); }
/// <summary> /// Retrieves type associated to a segment. /// </summary> /// <param name="segment">The node to retrieve the type from.</param> /// <returns>The type of the node, or item type for collections.</returns> internal static IEdmType GetEdmType(this QueryNode segment) { SingleValueNode singleNode = segment as SingleValueNode; if (singleNode != null) { IEdmTypeReference typeRef = singleNode.TypeReference; return((typeRef != null) ? typeRef.Definition : null); } CollectionNode collectionNode = segment as CollectionNode; if (collectionNode != null) { IEdmTypeReference typeRef = collectionNode.ItemType; return((typeRef != null) ? typeRef.Definition : null); } return(null); }
/// <summary> /// Bind the parent of the LambdaToken /// </summary> /// <param name="queryToken">the parent token</param> /// <returns>the bound parent node</returns> private CollectionNode BindParentToken(QueryToken queryToken) { QueryNode parentNode = this.bindMethod(queryToken); CollectionNode parentCollectionNode = parentNode as CollectionNode; if (parentCollectionNode == null) { SingleValueOpenPropertyAccessNode parentOpenPropertyNode = parentNode as SingleValueOpenPropertyAccessNode; if (parentOpenPropertyNode == null) { throw new ODataException(ODataErrorStrings.MetadataBinder_LambdaParentMustBeCollection); } // support open collection properties return(new CollectionOpenPropertyAccessNode(parentOpenPropertyNode.Source, parentOpenPropertyNode.Name)); } return(parentCollectionNode); }
/// <summary> /// Retrieve CollectionNode bound with given query token. /// </summary> /// <param name="queryToken">The query token</param> /// <param name="expectedType">The expected type that this collection holds</param> /// <param name="model">The Edm model</param> /// <returns>The corresponding CollectionNode</returns> private CollectionNode GetCollectionOperandFromToken(QueryToken queryToken, IEdmTypeReference expectedType, IEdmModel model) { CollectionNode operand = null; LiteralToken literalToken = queryToken as LiteralToken; if (literalToken != null) { string originalLiteralText = literalToken.OriginalText; // Parentheses-based collections are not standard JSON but bracket-based ones are. // Temporarily switch our collection to bracket-based so that the JSON reader will // correctly parse the collection. Then pass the original literal text to the token. string bracketLiteralText = originalLiteralText; if (bracketLiteralText[0] == '(') { Debug.Assert(bracketLiteralText[bracketLiteralText.Length - 1] == ')', "Collection with opening '(' should have corresponding ')'"); StringBuilder replacedText = new StringBuilder(bracketLiteralText); replacedText[0] = '['; replacedText[replacedText.Length - 1] = ']'; bracketLiteralText = replacedText.ToString(); } object collection = ODataUriConversionUtils.ConvertFromCollectionValue(bracketLiteralText, model, expectedType); LiteralToken collectionLiteralToken = new LiteralToken(collection, originalLiteralText, expectedType); operand = this.bindMethod(collectionLiteralToken) as CollectionConstantNode; } else { operand = this.bindMethod(queryToken) as CollectionNode; } if (operand == null) { throw new ODataException(ODataErrorStrings.MetadataBinder_RightOperandNotCollectionValue); } return(operand); }
/// <summary> /// Try to bind an identifier to a FunctionCallNode /// </summary> /// <param name="identifier">the identifier to bind</param> /// <param name="arguments">the semantically bound list of arguments.</param> /// <param name="parent">a semantically bound parent node.</param> /// <param name="state">the current state of the binding algorithm</param> /// <param name="boundFunction">a single value function call node representing this function call, if we found one.</param> /// <returns>true if we found a function for this token.</returns> private bool TryBindIdentifier(string identifier, IEnumerable <FunctionParameterToken> arguments, QueryNode parent, BindingState state, out QueryNode boundFunction) { boundFunction = null; IEdmType bindingType = null; SingleValueNode singleValueParent = parent as SingleValueNode; if (singleValueParent != null) { if (singleValueParent.TypeReference != null) { bindingType = singleValueParent.TypeReference.Definition; } } else { CollectionNode collectionValueParent = parent as CollectionNode; if (collectionValueParent != null) { bindingType = collectionValueParent.CollectionType.Definition; } } if (!UriEdmHelpers.IsBindingTypeValid(bindingType)) { return(false); } // All functions should be fully qualified, if they aren't they they aren't functions. // When using extension, there may be function call with unqualified name. So loose the restriction here. if (identifier.IndexOf(".", StringComparison.Ordinal) == -1 && this.Resolver.GetType() == typeof(ODataUriResolver)) { return(false); } IEdmOperation operation; List <FunctionParameterToken> syntacticArguments = arguments == null ? new List <FunctionParameterToken>() : arguments.ToList(); if (!FunctionOverloadResolver.ResolveOperationFromList(identifier, syntacticArguments.Select(ar => ar.ParameterName).ToList(), bindingType, state.Model, out operation, this.Resolver)) { // TODO: FunctionOverloadResolver.ResolveOperationFromList() looks up the function by parameter names, but it shouldn't ignore parameter types. (test case ParseFilter_AliasInFunction_PropertyAsValue_TypeMismatch should fail) return(false); } if (singleValueParent != null && singleValueParent.TypeReference == null) { // if the parent exists, but has no type information, then we're in open type land, and we // shouldn't go any farther. throw new ODataException(ODataErrorStrings.FunctionCallBinder_CallingFunctionOnOpenProperty(identifier)); } if (operation.IsAction()) { return(false); } IEdmFunction function = (IEdmFunction)operation; // TODO: $filter $orderby parameter expression which contains complex or collection should NOT be supported in this way // but should be parsed into token tree, and binded to node tree: parsedParameters.Select(p => this.bindMethod(p)); ICollection <FunctionParameterToken> parsedParameters = HandleComplexOrCollectionParameterValueIfExists(state.Configuration.Model, function, syntacticArguments, state.Configuration.Resolver.EnableCaseInsensitive); IEnumerable <QueryNode> boundArguments = parsedParameters.Select(p => this.bindMethod(p)); boundArguments = boundArguments.ToList(); // force enumerable to run : will immediately evaluate all this.bindMethod(p). IEdmTypeReference returnType = function.ReturnType; IEdmEntitySetBase returnSet = null; SingleResourceNode singleEntityNode = parent as SingleResourceNode; if (singleEntityNode != null) { returnSet = function.GetTargetEntitySet(singleEntityNode.NavigationSource, state.Model); } string functionName = function.FullName(); if (returnType.IsEntity()) { boundFunction = new SingleResourceFunctionCallNode(functionName, new[] { function }, boundArguments, (IEdmEntityTypeReference)returnType.Definition.ToTypeReference(), returnSet, parent); } else if (returnType.IsStructuredCollection()) { IEdmCollectionTypeReference collectionTypeReference = (IEdmCollectionTypeReference)returnType; boundFunction = new CollectionResourceFunctionCallNode(functionName, new[] { function }, boundArguments, collectionTypeReference, returnSet, parent); } else if (returnType.IsCollection()) { IEdmCollectionTypeReference collectionTypeReference = (IEdmCollectionTypeReference)returnType; boundFunction = new CollectionFunctionCallNode(functionName, new[] { function }, boundArguments, collectionTypeReference, parent); } else { boundFunction = new SingleValueFunctionCallNode(functionName, new[] { function }, boundArguments, returnType, parent); } return(true); }