1 //---------------------------------------------------------------------
2 // <copyright file="Function.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
7 // @backupOwner Microsoft
8 //---------------------------------------------------------------------
10 namespace System.Data.EntityModel.SchemaObjectModel
13 using System.Collections.Generic;
14 using System.Collections.ObjectModel;
15 using System.Data.Entity;
16 using System.Data.Metadata.Edm;
17 using System.Diagnostics;
21 /// class representing the Schema element in the schema
23 internal class Function : SchemaType
25 #region Instance Fields
26 // if adding properties also add to InitializeObject()!
27 private bool _isAggregate = false;
28 private bool _isBuiltIn = false;
29 private bool _isNiladicFunction = false;
30 protected bool _isComposable = true;
31 protected FunctionCommandText _commandText = null;
32 private string _storeFunctionName = null;
33 protected SchemaType _type = null;
34 private string _unresolvedType = null;
35 protected bool _isRefType = false;
36 // both are not specified
37 protected SchemaElementLookUpTable<Parameter> _parameters = null;
38 protected List<ReturnType> _returnTypeList = null;
39 private CollectionKind _returnTypeCollectionKind = CollectionKind.None;
40 private ParameterTypeSemantics _parameterTypeSemantics;
41 private string _schema;
43 private string _functionStrongName;
48 private static System.Text.RegularExpressions.Regex s_typeParser = new System.Text.RegularExpressions.Regex(@"^(?<modifier>((Collection)|(Ref)))\s*\(\s*(?<typeName>\S*)\s*\)$", System.Text.RegularExpressions.RegexOptions.Compiled);
53 /// <param name="type"></param>
54 /// <returns></returns>
55 internal static void RemoveTypeModifier(ref string type, out TypeModifier typeModifier, out bool isRefType)
58 typeModifier = TypeModifier.None;
60 System.Text.RegularExpressions.Match match = s_typeParser.Match(type);
63 type = match.Groups["typeName"].Value;
64 switch (match.Groups["modifier"].Value)
67 typeModifier = TypeModifier.Array;
73 Debug.Assert(false, "Unexpected modifier: " + match.Groups["modifier"].Value);
80 internal static string GetTypeNameForErrorMessage(SchemaType type, CollectionKind colKind, bool isRef)
82 string typeName = type.FQName;
85 typeName = "Ref(" + typeName + ")";
89 case CollectionKind.Bag:
90 typeName = "Collection(" + typeName + ")";
93 Debug.Assert(colKind == CollectionKind.None, "Unexpected CollectionKind");
100 #region Public Methods
102 /// ctor for a schema function
104 public Function(Schema parentElement)
105 : base(parentElement)
110 #region Public Properties
112 public bool IsAggregate
120 _isAggregate = value;
124 public bool IsBuiltIn
136 public bool IsNiladicFunction
140 return _isNiladicFunction;
144 _isNiladicFunction = value;
148 public bool IsComposable
152 return _isComposable;
156 _isComposable = value;
160 public string CommandText
164 if (_commandText != null)
166 return _commandText.CommandText;
172 public ParameterTypeSemantics ParameterTypeSemantics
176 return _parameterTypeSemantics;
180 _parameterTypeSemantics = value;
184 public string StoreFunctionName
188 return _storeFunctionName;
192 Debug.Assert(value != null, "StoreFunctionName should never be set null value");
193 _storeFunctionName = value;
197 public virtual SchemaType Type
201 if (null != _returnTypeList)
203 Debug.Assert(_returnTypeList.Count == 1, "Shouldn't use Type if there could be multiple return types");
204 return this._returnTypeList[0].Type;
213 public IList<ReturnType> ReturnTypeList
217 return null != _returnTypeList ? new ReadOnlyCollection<ReturnType>(_returnTypeList) : null;
221 public SchemaElementLookUpTable<Parameter> Parameters
225 if (_parameters == null)
227 _parameters = new SchemaElementLookUpTable<Parameter>();
233 public CollectionKind CollectionKind
237 return _returnTypeCollectionKind;
241 _returnTypeCollectionKind = value;
245 public override string Identity
249 if (String.IsNullOrEmpty(_functionStrongName))
251 string name = this.FQName;
252 System.Text.StringBuilder stringBuilder = new Text.StringBuilder(name);
254 stringBuilder.Append('(');
255 foreach (Parameter parameter in this.Parameters)
259 stringBuilder.Append(',');
265 stringBuilder.Append(Helper.ToString(parameter.ParameterDirection));
266 stringBuilder.Append(' ');
267 // we don't include the facets in the identity, since we are *not*
268 // taking them into consideration inside the
269 // RankFunctionParameters method of TypeResolver.cs
271 parameter.WriteIdentity(stringBuilder);
273 stringBuilder.Append(')');
274 _functionStrongName = stringBuilder.ToString();
276 return _functionStrongName;
280 public bool IsReturnAttributeReftype
288 public virtual bool IsFunctionImport { get { return false; } }
290 public string DbSchema
300 #region Protected Properties
301 protected override bool HandleElement(XmlReader reader)
303 if (base.HandleElement(reader))
307 else if (CanHandleElement(reader, XmlConstants.CommandText))
309 HandleCommandTextFunctionElment(reader);
312 else if (CanHandleElement(reader, XmlConstants.Parameter))
314 HandleParameterElement(reader);
317 else if (CanHandleElement(reader, XmlConstants.ReturnTypeElement))
319 HandleReturnTypeElement(reader);
322 else if (Schema.DataModel == SchemaDataModelOption.EntityDataModel)
324 if (CanHandleElement(reader, XmlConstants.ValueAnnotation))
326 // EF does not support this EDM 3.0 element, so ignore it.
330 else if (CanHandleElement(reader, XmlConstants.TypeAnnotation))
332 // EF does not support this EDM 3.0 element, so ignore it.
340 protected override bool HandleAttribute(XmlReader reader)
342 if (base.HandleAttribute(reader))
346 else if (CanHandleAttribute(reader, XmlConstants.ReturnType))
348 HandleReturnTypeAttribute(reader);
351 else if (CanHandleAttribute(reader, XmlConstants.AggregateAttribute))
353 HandleAggregateAttribute(reader);
356 else if (CanHandleAttribute(reader, XmlConstants.BuiltInAttribute))
358 HandleBuiltInAttribute(reader);
361 else if (CanHandleAttribute(reader, XmlConstants.StoreFunctionName))
363 HandleStoreFunctionNameAttribute(reader);
366 else if (CanHandleAttribute(reader, XmlConstants.NiladicFunction))
368 HandleNiladicFunctionAttribute(reader);
371 else if (CanHandleAttribute(reader, XmlConstants.IsComposable))
373 HandleIsComposableAttribute(reader);
376 else if (CanHandleAttribute(reader, XmlConstants.ParameterTypeSemantics))
378 HandleParameterTypeSemanticsAttribute(reader);
381 else if (CanHandleAttribute(reader, XmlConstants.Schema))
383 HandleDbSchemaAttribute(reader);
391 #region Internal Methods
393 internal override void ResolveTopLevelNames()
395 base.ResolveTopLevelNames();
397 if (_unresolvedType != null)
399 Debug.Assert(Schema.DataModel != SchemaDataModelOption.ProviderManifestModel, "ProviderManifest cannot have ReturnType as an attribute");
400 Schema.ResolveTypeName(this, UnresolvedReturnType, out _type);
403 if (null != _returnTypeList)
405 foreach (ReturnType returnType in _returnTypeList)
407 returnType.ResolveTopLevelNames();
411 foreach (Parameter parameter in this.Parameters)
413 parameter.ResolveTopLevelNames();
418 /// Perform local validation on function definition.
420 internal override void Validate()
424 if (_type != null && _returnTypeList != null)
426 AddError(ErrorCode.ReturnTypeDeclaredAsAttributeAndElement, EdmSchemaErrorSeverity.Error, Strings.TypeDeclaredAsAttributeAndElement);
429 // only call Type if _returnTypeList is empty, to ensure that we don't it when
430 // _returnTypeList has more than one element.
431 if (this._returnTypeList == null && this.Type == null)
433 // Composable functions and function imports must declare return type.
434 if (this.IsComposable)
436 AddError(ErrorCode.ComposableFunctionOrFunctionImportWithoutReturnType, EdmSchemaErrorSeverity.Error,
437 Strings.ComposableFunctionOrFunctionImportMustDeclareReturnType);
442 // Non-composable functions (except function imports) must not declare a return type.
443 if (!this.IsComposable && !this.IsFunctionImport)
445 AddError(ErrorCode.NonComposableFunctionWithReturnType, EdmSchemaErrorSeverity.Error,
446 Strings.NonComposableFunctionMustNotDeclareReturnType);
450 if (Schema.DataModel != SchemaDataModelOption.EntityDataModel)
455 // Make sure that the function has exactly one parameter and that takes
457 if (Parameters.Count != 1)
459 AddError(ErrorCode.InvalidNumberOfParametersForAggregateFunction,
460 EdmSchemaErrorSeverity.Error,
462 System.Data.Entity.Strings.InvalidNumberOfParametersForAggregateFunction(FQName));
464 else if (Parameters.GetElementAt(0).CollectionKind == CollectionKind.None)
466 // Since we have already checked that there should be exactly one parameter, it should be safe to get the
467 // first parameter for the function
468 Parameter param = Parameters.GetElementAt(0);
470 AddError(ErrorCode.InvalidParameterTypeForAggregateFunction,
471 EdmSchemaErrorSeverity.Error,
473 System.Data.Entity.Strings.InvalidParameterTypeForAggregateFunction(param.Name, FQName));
478 if (!this.IsComposable)
480 // All aggregates, built-in and niladic functions must be composable, so throw error here.
481 if (this.IsAggregate ||
482 this.IsNiladicFunction ||
485 AddError(ErrorCode.NonComposableFunctionAttributesNotValid, EdmSchemaErrorSeverity.Error,
486 Strings.NonComposableFunctionHasDisallowedAttribute);
490 if (null != this.CommandText)
492 // Functions with command text are not composable.
493 if (this.IsComposable)
495 AddError(ErrorCode.ComposableFunctionWithCommandText, EdmSchemaErrorSeverity.Error,
496 Strings.CommandTextFunctionsNotComposable);
499 // Functions with command text cannot declare store function name.
500 if (null != this.StoreFunctionName)
502 AddError(ErrorCode.FunctionDeclaresCommandTextAndStoreFunctionName, EdmSchemaErrorSeverity.Error,
503 Strings.CommandTextFunctionsCannotDeclareStoreFunctionName);
508 if (Schema.DataModel == SchemaDataModelOption.ProviderDataModel)
510 // In SSDL function may return a primitive value or a collection of rows with scalar props.
511 // It is not possible to encode "collection of rows" in the ReturnType attribute, so the only check needed here is to make sure that the type is scalar and not a collection.
512 if (_type != null && (_type is ScalarType == false || _returnTypeCollectionKind != Metadata.Edm.CollectionKind.None))
514 AddError(ErrorCode.FunctionWithNonPrimitiveTypeNotSupported,
515 EdmSchemaErrorSeverity.Error,
517 System.Data.Entity.Strings.FunctionWithNonPrimitiveTypeNotSupported(GetTypeNameForErrorMessage(_type, _returnTypeCollectionKind, _isRefType), this.FQName));
521 if (_returnTypeList != null)
523 foreach (ReturnType returnType in _returnTypeList)
525 // FunctiomImportElement has additional validation for return types.
526 returnType.Validate();
530 if (_parameters != null)
532 foreach (var parameter in _parameters)
534 parameter.Validate();
538 if (_commandText != null)
540 _commandText.Validate();
544 internal override void ResolveSecondLevelNames()
546 foreach (var parameter in _parameters)
548 parameter.ResolveSecondLevelNames();
552 internal override SchemaElement Clone(SchemaElement parentElement)
554 // We only support clone for FunctionImports.
555 throw Error.NotImplemented();
557 protected void CloneSetFunctionFields(Function clone)
559 clone._isAggregate = _isAggregate;
560 clone._isBuiltIn = _isBuiltIn;
561 clone._isNiladicFunction = _isNiladicFunction;
562 clone._isComposable = _isComposable;
563 clone._commandText = _commandText;
564 clone._storeFunctionName = _storeFunctionName;
566 clone._returnTypeList = _returnTypeList;
567 clone._returnTypeCollectionKind = _returnTypeCollectionKind;
568 clone._parameterTypeSemantics = _parameterTypeSemantics;
569 clone._schema = _schema;
570 clone.Name = this.Name;
572 // Clone all the parameters
573 foreach (Parameter parameter in this.Parameters)
575 AddErrorKind error = clone.Parameters.TryAdd((Parameter)parameter.Clone(clone));
576 Debug.Assert(error == AddErrorKind.Succeeded, "Since we are cloning a validated function, this should never fail.");
581 #region Internal Properties
586 internal string UnresolvedReturnType
590 return _unresolvedType;
594 _unresolvedType = value;
597 #endregion //Internal Properties
599 #region Private Methods
602 /// The method that is called when a DbSchema attribute is encountered.
604 /// <param name="reader">An XmlReader positioned at the Type attribute.</param>
605 private void HandleDbSchemaAttribute(XmlReader reader)
607 Debug.Assert(Schema.DataModel == SchemaDataModelOption.ProviderDataModel, "We shouldn't see this attribute unless we are parsing ssdl");
608 Debug.Assert(reader != null);
610 _schema = reader.Value;
614 /// Handler for the Version attribute
616 /// <param name="reader">xml reader currently positioned at Version attribute</param>
617 private void HandleAggregateAttribute(XmlReader reader)
619 Debug.Assert(reader != null);
620 bool isAggregate = false;
621 HandleBoolAttribute(reader, ref isAggregate);
622 IsAggregate = isAggregate;
626 /// Handler for the Namespace attribute
628 /// <param name="reader">xml reader currently positioned at Namespace attribute</param>
629 private void HandleBuiltInAttribute(XmlReader reader)
631 Debug.Assert(reader != null);
632 bool isBuiltIn = false;
633 HandleBoolAttribute(reader, ref isBuiltIn);
634 IsBuiltIn = isBuiltIn;
638 /// Handler for the Alias attribute
640 /// <param name="reader">xml reader currently positioned at Alias attribute</param>
641 private void HandleStoreFunctionNameAttribute(XmlReader reader)
643 Debug.Assert(reader != null);
644 string value = reader.Value.ToString();
645 if (!String.IsNullOrEmpty(value))
647 value = value.Trim();
648 StoreFunctionName = value;
653 /// Handler for the NiladicFunctionAttribute attribute
655 /// <param name="reader">xml reader currently positioned at Namespace attribute</param>
656 private void HandleNiladicFunctionAttribute(XmlReader reader)
658 Debug.Assert(reader != null);
659 bool isNiladicFunction = false;
660 HandleBoolAttribute(reader, ref isNiladicFunction);
661 IsNiladicFunction = isNiladicFunction;
665 /// Handler for the IsComposableAttribute attribute
667 /// <param name="reader">xml reader currently positioned at Namespace attribute</param>
668 private void HandleIsComposableAttribute(XmlReader reader)
670 Debug.Assert(reader != null);
671 bool isComposable = true;
672 HandleBoolAttribute(reader, ref isComposable);
673 IsComposable = isComposable;
676 private void HandleCommandTextFunctionElment(XmlReader reader)
678 Debug.Assert(reader != null);
680 FunctionCommandText commandText = new FunctionCommandText(this);
681 commandText.Parse(reader);
682 _commandText = commandText;
685 protected virtual void HandleReturnTypeAttribute(XmlReader reader)
687 Debug.Assert(reader != null);
688 Debug.Assert(UnresolvedReturnType == null);
691 if (!Utils.GetString(Schema, reader, out type))
694 TypeModifier typeModifier;
696 RemoveTypeModifier(ref type, out typeModifier, out _isRefType);
698 switch (typeModifier)
700 case TypeModifier.Array:
701 CollectionKind = CollectionKind.Bag;
703 case TypeModifier.None:
706 Debug.Assert(false, "RemoveTypeModifier already checks for this");
710 if (!Utils.ValidateDottedName(Schema, reader, type))
713 UnresolvedReturnType = type;
717 /// Handler for the Parameter Element
719 /// <param name="reader">xml reader currently positioned at Parameter Element</param>
720 protected void HandleParameterElement(XmlReader reader)
722 Debug.Assert(reader != null);
724 Parameter parameter = new Parameter(this);
726 parameter.Parse(reader);
728 Parameters.Add(parameter, true, Strings.ParameterNameAlreadyDefinedDuplicate);
732 /// Handler for the ReturnType element
734 /// <param name="reader">xml reader currently positioned at ReturnType element</param>
735 protected void HandleReturnTypeElement(XmlReader reader)
737 Debug.Assert(reader != null);
739 ReturnType returnType = new ReturnType(this);
741 returnType.Parse(reader);
743 if (this._returnTypeList == null)
745 this._returnTypeList = new List<ReturnType>();
747 this._returnTypeList.Add(returnType);
751 /// Handles ParameterTypeSemantics attribute
753 /// <param name="reader"></param>
754 private void HandleParameterTypeSemanticsAttribute(XmlReader reader)
756 Debug.Assert(reader != null);
758 string value = reader.Value;
760 if (String.IsNullOrEmpty(value))
765 value = value.Trim();
767 if (!String.IsNullOrEmpty(value))
771 case "ExactMatchOnly":
772 ParameterTypeSemantics = ParameterTypeSemantics.ExactMatchOnly;
774 case "AllowImplicitPromotion":
775 ParameterTypeSemantics = ParameterTypeSemantics.AllowImplicitPromotion;
777 case "AllowImplicitConversion":
778 ParameterTypeSemantics = ParameterTypeSemantics.AllowImplicitConversion;
781 // don't try to use the name of the function, because we are still parsing the
782 // attributes, and we may not be to the name attribute yet.
783 AddError(ErrorCode.InvalidValueForParameterTypeSemantics, EdmSchemaErrorSeverity.Error, reader,
784 System.Data.Entity.Strings.InvalidValueForParameterTypeSemanticsAttribute(