/// <summary> /// Builds an appropriate navigation query node (collection or single) for the given property and parent node. /// </summary> /// <param name="property">Navigation property.</param> /// <param name="parent">Parent Node.</param> /// <param name="namedValues">Named values (key values) that were included in the node we are binding, if any.</param> /// <param name="state">State of binding.</param> /// <param name="keyBinder">Object to perform binding on any key values that are present.</param> /// <param name="navigationSource">The navigation source of the navigation node.</param> /// <returns>A new CollectionNavigationNode or SingleNavigationNode to capture the navigation property access.</returns> internal static QueryNode GetNavigationNode(IEdmNavigationProperty property, SingleResourceNode parent, IEnumerable <NamedValue> namedValues, BindingState state, KeyBinder keyBinder, out IEdmNavigationSource navigationSource) { ExceptionUtils.CheckArgumentNotNull(property, "property"); ExceptionUtils.CheckArgumentNotNull(parent, "parent"); ExceptionUtils.CheckArgumentNotNull(state, "state"); ExceptionUtils.CheckArgumentNotNull(keyBinder, "keyBinder"); // Handle collection navigation property if (property.TargetMultiplicity() == EdmMultiplicity.Many) { CollectionNavigationNode collectionNavigationNode = new CollectionNavigationNode(parent, property, state.ParsedSegments); navigationSource = collectionNavigationNode.NavigationSource; // Doing key lookup on the collection navigation property if (namedValues != null) { return(keyBinder.BindKeyValues(collectionNavigationNode, namedValues, state.Model)); } // Otherwise it's just a normal collection of entities return(collectionNavigationNode); } Debug.Assert(namedValues == null || !namedValues.Any(), "namedValues should not exist if it isn't a collection"); // Otherwise it's a single navigation property SingleNavigationNode singleNavigationNode = new SingleNavigationNode(parent, property, state.ParsedSegments); navigationSource = singleNavigationNode.NavigationSource; return(singleNavigationNode); }
/// <summary> /// Created a SingleResourceCastNode with the given source node and the given type to cast to. /// </summary> /// <param name="source"> Source <see cref="SingleValueNode"/> that is being cast.</param> /// <param name="structuredType">Type to cast to.</param> /// <exception cref="System.ArgumentNullException">Throws if the input entityType is null.</exception> public SingleResourceCastNode(SingleResourceNode source, IEdmStructuredType structuredType) { ExceptionUtils.CheckArgumentNotNull(structuredType, "structuredType"); this.source = source; this.navigationSource = source != null ? source.NavigationSource : null; this.structuredTypeReference = structuredType.GetTypeReference(); }
/// <summary> /// Ensures that the parent node is of structured type, throwing if it is not. /// </summary> /// <param name="parent">Parent node to a navigation property.</param> /// <returns>The given parent node as a SingleResourceNode.</returns> internal static SingleResourceNode EnsureParentIsResourceForNavProp(SingleValueNode parent) { ExceptionUtils.CheckArgumentNotNull(parent, "parent"); SingleResourceNode parentResource = parent as SingleResourceNode; if (parentResource == null) { // TODO: update error message #644 throw new ODataException(ODataErrorStrings.MetadataBinder_NavigationPropertyNotFollowingSingleEntityType); } return(parentResource); }
/// <summary> /// Generates a bound query node representing an <see cref="IEdmProperty"/> given an already semantically bound parent node. /// </summary> /// <param name="parentNode">The semantically bound source node of this end path token</param> /// <param name="property">The <see cref="IEdmProperty"/> that will be bound to this node. Must not be primitive collection</param> /// <param name="state">The state of binding.</param> /// <returns>QueryNode bound to this property.</returns> internal static QueryNode GeneratePropertyAccessQueryNode(SingleResourceNode parentNode, IEdmProperty property, BindingState state) { ExceptionUtils.CheckArgumentNotNull(parentNode, "parentNode"); ExceptionUtils.CheckArgumentNotNull(property, "property"); // TODO: Remove this check. // We should verify that the top level of an expression is a bool rather than arbitrarily restrict property types. // We can get here if there is a query like $filter=MyCollectionProperty eq 'foo' or something. // If it was $filter=MyCollectionProperty/any(...) then we would have gone down the 'NonRootSegment' code path instead of this one if (property.Type.IsNonEntityCollectionType()) { // if this happens to be a top level node (i.e. $filter=MyCollection), then it will fail further up the chain, so // don't need to worry about checking for that here. if (property.Type.IsStructuredCollectionType()) { return(new CollectionComplexNode(parentNode, property)); } else { return(new CollectionPropertyAccessNode(parentNode, property)); } } if (property.PropertyKind == EdmPropertyKind.Navigation) { // These are error cases in practice, but we let ourselves throw later for better context-sensitive error messages IEdmNavigationProperty edmNavigationProperty = (IEdmNavigationProperty)property; if (edmNavigationProperty.TargetMultiplicity() == EdmMultiplicity.Many) { return(new CollectionNavigationNode(parentNode, edmNavigationProperty, state.ParsedSegments)); } return(new SingleNavigationNode(parentNode, edmNavigationProperty, state.ParsedSegments)); } if (property.Type.IsComplex()) { return(new SingleComplexNode(parentNode, property)); } return(new SingleValuePropertyAccessNode(parentNode, property)); }
/// <summary> /// Build a SingleValueFunctionCallNode for a function that isn't bound to a BuiltInFunction /// </summary> /// <param name="functionCallTokenName">Name for the function</param> /// <param name="args">list of already bound query nodes for this function</param> /// <returns>A single value function call node bound to this function.</returns> private SingleValueNode CreateUnboundFunctionNode(string functionCallTokenName, List <QueryNode> args) { // need to figure out the return type and check the correct number of arguments based on the function name IEdmTypeReference returnType = null; switch (functionCallTokenName) { case ExpressionConstants.UnboundFunctionIsOf: { returnType = ValidateAndBuildIsOfArgs(state, ref args); break; } case ExpressionConstants.UnboundFunctionCast: { returnType = ValidateAndBuildCastArgs(state, ref args); if (returnType.IsStructured()) { SingleResourceNode entityNode = args.ElementAt(0) as SingleResourceNode; return(new SingleResourceFunctionCallNode(functionCallTokenName, args, returnType.AsStructured(), entityNode != null ? entityNode.NavigationSource : null)); } break; } default: { break; } } // we have everything else we need, so return the new SingleValueFunctionCallNode. return(new SingleValueFunctionCallNode(functionCallTokenName, args, returnType)); }
/// <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); }
/// <summary> /// Binds a DottedIdentifierToken and it's parent node (if needed). /// </summary> /// <param name="dottedIdentifierToken">Token to bind to metadata.</param> /// <returns>A bound node representing the cast.</returns> internal QueryNode BindDottedIdentifier(DottedIdentifierToken dottedIdentifierToken) { ExceptionUtils.CheckArgumentNotNull(dottedIdentifierToken, "castToken"); ExceptionUtils.CheckArgumentNotNull(state, "state"); QueryNode parent = null; IEdmType parentType = null; if (state.ImplicitRangeVariable != null) { if (dottedIdentifierToken.NextToken == null) { parent = NodeFactory.CreateRangeVariableReferenceNode(state.ImplicitRangeVariable); parentType = state.ImplicitRangeVariable.TypeReference.Definition; } else { parent = this.bindMethod(dottedIdentifierToken.NextToken); parentType = parent.GetEdmType(); } } SingleResourceNode parentAsSingleResource = parent as SingleResourceNode; IEdmSchemaType childType = UriEdmHelpers.FindTypeFromModel(state.Model, dottedIdentifierToken.Identifier, this.Resolver); IEdmStructuredType childStructuredType = childType as IEdmStructuredType; if (childStructuredType == null) { SingleValueNode singleValueNode = parent as SingleValueNode; FunctionCallBinder functionCallBinder = new FunctionCallBinder(bindMethod, state); QueryNode functionCallNode; if (functionCallBinder.TryBindDottedIdentifierAsFunctionCall(dottedIdentifierToken, singleValueNode, out functionCallNode)) { return(functionCallNode); } else if ((!string.IsNullOrEmpty(dottedIdentifierToken.Identifier)) && (dottedIdentifierToken.Identifier[dottedIdentifierToken.Identifier.Length - 1] == '\'')) { // check if it is enum or not QueryNode enumNode; if (EnumBinder.TryBindDottedIdentifierAsEnum(dottedIdentifierToken, parentAsSingleResource, state, this.Resolver, out enumNode)) { return(enumNode); } else { throw new ODataException(ODataErrorStrings.Binder_IsNotValidEnumConstant(dottedIdentifierToken.Identifier)); } } else { IEdmTypeReference edmTypeReference = UriEdmHelpers.FindTypeFromModel(state.Model, dottedIdentifierToken.Identifier, this.Resolver).ToTypeReference(); if (edmTypeReference is IEdmPrimitiveTypeReference || edmTypeReference is IEdmEnumTypeReference) { IEdmPrimitiveType childPrimitiveType = childType as IEdmPrimitiveType; if (childPrimitiveType != null && dottedIdentifierToken.NextToken != null) { return(new SingleValueCastNode(singleValueNode, childPrimitiveType)); } else { return(new ConstantNode(dottedIdentifierToken.Identifier, dottedIdentifierToken.Identifier)); } } else { throw new ODataException(ODataErrorStrings.CastBinder_ChildTypeIsNotEntity(dottedIdentifierToken.Identifier)); } } } // Check whether childType is a derived type of the type of its parent node UriEdmHelpers.CheckRelatedTo(parentType, childType); this.state.ParsedSegments.Add(new TypeSegment(childType, parentType, null)); CollectionResourceNode parentAsCollection = parent as CollectionResourceNode; if (parentAsCollection != null) { return(new CollectionResourceCastNode(parentAsCollection, childStructuredType)); } return(new SingleResourceCastNode(parentAsSingleResource, childStructuredType)); }
/// <summary> /// Binds a <see cref="InnerPathToken"/>. /// This includes more than just navigations - it includes complex property access and primitive collections. /// </summary> /// <param name="segmentToken">The segment token to bind.</param> /// <returns>The bound node.</returns> internal QueryNode BindInnerPathSegment(InnerPathToken segmentToken) { FunctionCallBinder functionCallBinder = new FunctionCallBinder(this.bindMethod, state); // First we get the parent node QueryNode parent = this.DetermineParentNode(segmentToken, state); Debug.Assert(parent != null, "parent should never be null"); SingleValueNode singleValueParent = parent as SingleValueNode; if (singleValueParent == null) { QueryNode boundFunction; if (functionCallBinder.TryBindInnerPathAsFunctionCall(segmentToken, parent, out boundFunction)) { return(boundFunction); } throw new ODataException(ODataErrorStrings.MetadataBinder_PropertyAccessSourceNotSingleValue(segmentToken.Identifier)); } // Using the parent and name of this token, we try to get the IEdmProperty it represents IEdmProperty property = BindProperty(singleValueParent.TypeReference, segmentToken.Identifier, this.Resolver); if (property == null) { QueryNode boundFunction; if (functionCallBinder.TryBindInnerPathAsFunctionCall(segmentToken, parent, out boundFunction)) { return(boundFunction); } if (singleValueParent.TypeReference != null && !singleValueParent.TypeReference.Definition.IsOpen()) { throw ExceptionUtil.CreatePropertyNotFoundException(segmentToken.Identifier, parent.GetEdmTypeReference().FullName()); } return(new SingleValueOpenPropertyAccessNode(singleValueParent, segmentToken.Identifier)); } IEdmStructuralProperty structuralProperty = property as IEdmStructuralProperty; if (property.Type.IsComplex()) { // Generate a segment to parsed segments for the parsed token state.ParsedSegments.Add(new PropertySegment(structuralProperty)); return(new SingleComplexNode(singleValueParent as SingleResourceNode, property)); } else if (property.Type.IsPrimitive()) { return(new SingleValuePropertyAccessNode(singleValueParent, property)); } // Note - this means nonentity collection (primitive or complex) if (property.Type.IsNonEntityCollectionType()) { if (property.Type.IsStructuredCollectionType()) { // Generate a segment to parsed segments for the parsed token state.ParsedSegments.Add(new PropertySegment(structuralProperty)); return(new CollectionComplexNode(singleValueParent as SingleResourceNode, property)); } return(new CollectionPropertyAccessNode(singleValueParent, property)); } IEdmNavigationProperty navigationProperty = property as IEdmNavigationProperty; if (navigationProperty == null) { throw new ODataException(ODataErrorStrings.MetadataBinder_IllegalSegmentType(property.Name)); } SingleResourceNode parentResource = EnsureParentIsResourceForNavProp(singleValueParent); IEdmNavigationSource navigationSource; QueryNode node = GetNavigationNode(navigationProperty, parentResource, segmentToken.NamedValues, state, new KeyBinder(this.bindMethod), out navigationSource); // Generate a segment to parsed segments for the parsed token state.ParsedSegments.Add(new NavigationPropertySegment(navigationProperty, navigationSource)); return(node); }
/// <summary> /// Constructs a <see cref="SingleComplexNode"/>. /// </summary> /// <param name="source">The value containing this property.</param> /// <param name="property">The EDM property which is to be accessed.</param> /// <exception cref="System.ArgumentNullException">Throws if input source or property is null.</exception> /// <exception cref="ArgumentException">Throws if input property is not structural, or is a collection.</exception> public SingleComplexNode(SingleResourceNode source, IEdmProperty property) : this(ExceptionUtils.CheckArgumentNotNull(source, "source").NavigationSource, property) { this.source = source; }
/// <summary> /// Constructs a CollectionNavigationNode. /// </summary> /// <param name="source">The previous node in the path.</param> /// <param name="navigationProperty">The navigation property this node represents.</param> /// <param name="parsedSegments">The path segments parsed in path and query option.</param> internal CollectionNavigationNode(SingleResourceNode source, IEdmNavigationProperty navigationProperty, List <ODataPathSegment> parsedSegments) : this(ExceptionUtils.CheckArgumentNotNull(source, "source").NavigationSource, navigationProperty, parsedSegments) { this.source = source; }
/// <summary> /// Creates a CollectionNavigationNode. /// </summary> /// <param name="source">The parent of this collection navigation node.</param> /// <param name="navigationProperty">The navigation property that defines the collection node.</param> /// <param name="bindingPath">The binding path of navigation property</param> /// <returns>The collection node.</returns> /// <exception cref="System.ArgumentNullException">Throws if the input source or navigation property is null.</exception> /// <exception cref="ArgumentException">Throws if the input navigation doesn't target a collection.</exception> public CollectionNavigationNode(SingleResourceNode source, IEdmNavigationProperty navigationProperty, IEdmPathExpression bindingPath) : this(ExceptionUtils.CheckArgumentNotNull(source, "source").NavigationSource, navigationProperty, bindingPath) { this.source = source; }