//--------------------------------------------------------------------- // // Copyright (c) Microsoft Corporation. All rights reserved. // // // @owner [....] // @backupOwner [....] //--------------------------------------------------------------------- namespace System.Data.EntityModel.SchemaObjectModel { using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Data.Entity; using System.Data.Metadata.Edm; using System.Diagnostics; using System.Xml; /// /// class representing the Schema element in the schema /// internal class Function : SchemaType { #region Instance Fields // if adding properties also add to InitializeObject()! private bool _isAggregate = false; private bool _isBuiltIn = false; private bool _isNiladicFunction = false; protected bool _isComposable = true; protected FunctionCommandText _commandText = null; private string _storeFunctionName = null; protected SchemaType _type = null; private string _unresolvedType = null; protected bool _isRefType = false; // both are not specified protected SchemaElementLookUpTable _parameters = null; protected List _returnTypeList = null; private CollectionKind _returnTypeCollectionKind = CollectionKind.None; private ParameterTypeSemantics _parameterTypeSemantics; private string _schema; private string _functionStrongName; #endregion #region Static Fields private static System.Text.RegularExpressions.Regex s_typeParser = new System.Text.RegularExpressions.Regex(@"^(?((Collection)|(Ref)))\s*\(\s*(?\S*)\s*\)$", System.Text.RegularExpressions.RegexOptions.Compiled); /// /// /// /// /// internal static void RemoveTypeModifier(ref string type, out TypeModifier typeModifier, out bool isRefType) { isRefType = false; typeModifier = TypeModifier.None; System.Text.RegularExpressions.Match match = s_typeParser.Match(type); if (match.Success) { type = match.Groups["typeName"].Value; switch (match.Groups["modifier"].Value) { case "Collection": typeModifier = TypeModifier.Array; return; case "Ref": isRefType = true; return; default: Debug.Assert(false, "Unexpected modifier: " + match.Groups["modifier"].Value); break; } } } internal static string GetTypeNameForErrorMessage(SchemaType type, CollectionKind colKind, bool isRef) { string typeName = type.FQName; if (isRef) { typeName = "Ref(" + typeName + ")"; } switch (colKind) { case CollectionKind.Bag: typeName = "Collection(" + typeName + ")"; break; default: Debug.Assert(colKind == CollectionKind.None, "Unexpected CollectionKind"); break; } return typeName; } #endregion #region Public Methods /// /// ctor for a schema function /// public Function(Schema parentElement) : base(parentElement) { } #endregion #region Public Properties public bool IsAggregate { get { return _isAggregate; } internal set { _isAggregate = value; } } public bool IsBuiltIn { get { return _isBuiltIn; } internal set { _isBuiltIn = value; } } public bool IsNiladicFunction { get { return _isNiladicFunction; } internal set { _isNiladicFunction = value; } } public bool IsComposable { get { return _isComposable; } internal set { _isComposable = value; } } public string CommandText { get { if (_commandText != null) { return _commandText.CommandText; } return null; } } public ParameterTypeSemantics ParameterTypeSemantics { get { return _parameterTypeSemantics; } internal set { _parameterTypeSemantics = value; } } public string StoreFunctionName { get { return _storeFunctionName; } internal set { Debug.Assert(value != null, "StoreFunctionName should never be set null value"); _storeFunctionName = value; } } public virtual SchemaType Type { get { if (null != _returnTypeList) { Debug.Assert(_returnTypeList.Count == 1, "Shouldn't use Type if there could be multiple return types"); return this._returnTypeList[0].Type; } else { return this._type; } } } public IList ReturnTypeList { get { return null != _returnTypeList ? new ReadOnlyCollection(_returnTypeList) : null; } } public SchemaElementLookUpTable Parameters { get { if (_parameters == null) { _parameters = new SchemaElementLookUpTable(); } return _parameters; } } public CollectionKind CollectionKind { get { return _returnTypeCollectionKind; } internal set { _returnTypeCollectionKind = value; } } public override string Identity { get { if (String.IsNullOrEmpty(_functionStrongName)) { string name = this.FQName; System.Text.StringBuilder stringBuilder = new Text.StringBuilder(name); bool first = true; stringBuilder.Append('('); foreach (Parameter parameter in this.Parameters) { if (!first) { stringBuilder.Append(','); } else { first = false; } stringBuilder.Append(Helper.ToString(parameter.ParameterDirection)); stringBuilder.Append(' '); // we don't include the facets in the identity, since we are *not* // taking them into consideration inside the // RankFunctionParameters method of TypeResolver.cs parameter.WriteIdentity(stringBuilder); } stringBuilder.Append(')'); _functionStrongName = stringBuilder.ToString(); } return _functionStrongName; } } public bool IsReturnAttributeReftype { get { return _isRefType; } } public virtual bool IsFunctionImport { get { return false; } } public string DbSchema { get { return _schema; } } #endregion #region Protected Properties protected override bool HandleElement(XmlReader reader) { if (base.HandleElement(reader)) { return true; } else if (CanHandleElement(reader, XmlConstants.CommandText)) { HandleCommandTextFunctionElment(reader); return true; } else if (CanHandleElement(reader, XmlConstants.Parameter)) { HandleParameterElement(reader); return true; } else if (CanHandleElement(reader, XmlConstants.ReturnTypeElement)) { HandleReturnTypeElement(reader); return true; } else if (Schema.DataModel == SchemaDataModelOption.EntityDataModel) { if (CanHandleElement(reader, XmlConstants.ValueAnnotation)) { // EF does not support this EDM 3.0 element, so ignore it. SkipElement(reader); return true; } else if (CanHandleElement(reader, XmlConstants.TypeAnnotation)) { // EF does not support this EDM 3.0 element, so ignore it. SkipElement(reader); return true; } } return false; } protected override bool HandleAttribute(XmlReader reader) { if (base.HandleAttribute(reader)) { return true; } else if (CanHandleAttribute(reader, XmlConstants.ReturnType)) { HandleReturnTypeAttribute(reader); return true; } else if (CanHandleAttribute(reader, XmlConstants.AggregateAttribute)) { HandleAggregateAttribute(reader); return true; } else if (CanHandleAttribute(reader, XmlConstants.BuiltInAttribute)) { HandleBuiltInAttribute(reader); return true; } else if (CanHandleAttribute(reader, XmlConstants.StoreFunctionName)) { HandleStoreFunctionNameAttribute(reader); return true; } else if (CanHandleAttribute(reader, XmlConstants.NiladicFunction)) { HandleNiladicFunctionAttribute(reader); return true; } else if (CanHandleAttribute(reader, XmlConstants.IsComposable)) { HandleIsComposableAttribute(reader); return true; } else if (CanHandleAttribute(reader, XmlConstants.ParameterTypeSemantics)) { HandleParameterTypeSemanticsAttribute(reader); return true; } else if (CanHandleAttribute(reader, XmlConstants.Schema)) { HandleDbSchemaAttribute(reader); return true; } return false; } #endregion #region Internal Methods internal override void ResolveTopLevelNames() { base.ResolveTopLevelNames(); if (_unresolvedType != null) { Debug.Assert(Schema.DataModel != SchemaDataModelOption.ProviderManifestModel, "ProviderManifest cannot have ReturnType as an attribute"); Schema.ResolveTypeName(this, UnresolvedReturnType, out _type); } if (null != _returnTypeList) { foreach (ReturnType returnType in _returnTypeList) { returnType.ResolveTopLevelNames(); } } foreach (Parameter parameter in this.Parameters) { parameter.ResolveTopLevelNames(); } } /// /// Perform local validation on function definition. /// internal override void Validate() { base.Validate(); if (_type != null && _returnTypeList != null) { AddError(ErrorCode.ReturnTypeDeclaredAsAttributeAndElement, EdmSchemaErrorSeverity.Error, Strings.TypeDeclaredAsAttributeAndElement); } // only call Type if _returnTypeList is empty, to ensure that we don't it when // _returnTypeList has more than one element. if (this._returnTypeList == null && this.Type == null) { // Composable functions and function imports must declare return type. if (this.IsComposable) { AddError(ErrorCode.ComposableFunctionOrFunctionImportWithoutReturnType, EdmSchemaErrorSeverity.Error, Strings.ComposableFunctionOrFunctionImportMustDeclareReturnType); } } else { // Non-composable functions (except function imports) must not declare a return type. if (!this.IsComposable && !this.IsFunctionImport) { AddError(ErrorCode.NonComposableFunctionWithReturnType, EdmSchemaErrorSeverity.Error, Strings.NonComposableFunctionMustNotDeclareReturnType); } } if (Schema.DataModel != SchemaDataModelOption.EntityDataModel) { if (IsAggregate) { // Make sure that the function has exactly one parameter and that takes // a collection type if (Parameters.Count != 1) { AddError(ErrorCode.InvalidNumberOfParametersForAggregateFunction, EdmSchemaErrorSeverity.Error, this, System.Data.Entity.Strings.InvalidNumberOfParametersForAggregateFunction(FQName)); } else if (Parameters.GetElementAt(0).CollectionKind == CollectionKind.None) { // Since we have already checked that there should be exactly one parameter, it should be safe to get the // first parameter for the function Parameter param = Parameters.GetElementAt(0); AddError(ErrorCode.InvalidParameterTypeForAggregateFunction, EdmSchemaErrorSeverity.Error, this, System.Data.Entity.Strings.InvalidParameterTypeForAggregateFunction(param.Name, FQName)); } } if (!this.IsComposable) { // All aggregates, built-in and niladic functions must be composable, so throw error here. if (this.IsAggregate || this.IsNiladicFunction || this.IsBuiltIn) { AddError(ErrorCode.NonComposableFunctionAttributesNotValid, EdmSchemaErrorSeverity.Error, Strings.NonComposableFunctionHasDisallowedAttribute); } } if (null != this.CommandText) { // Functions with command text are not composable. if (this.IsComposable) { AddError(ErrorCode.ComposableFunctionWithCommandText, EdmSchemaErrorSeverity.Error, Strings.CommandTextFunctionsNotComposable); } // Functions with command text cannot declare store function name. if (null != this.StoreFunctionName) { AddError(ErrorCode.FunctionDeclaresCommandTextAndStoreFunctionName, EdmSchemaErrorSeverity.Error, Strings.CommandTextFunctionsCannotDeclareStoreFunctionName); } } } if (Schema.DataModel == SchemaDataModelOption.ProviderDataModel) { // In SSDL function may return a primitive value or a collection of rows with scalar props. // 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. if (_type != null && (_type is ScalarType == false || _returnTypeCollectionKind != Metadata.Edm.CollectionKind.None)) { AddError(ErrorCode.FunctionWithNonPrimitiveTypeNotSupported, EdmSchemaErrorSeverity.Error, this, System.Data.Entity.Strings.FunctionWithNonPrimitiveTypeNotSupported(GetTypeNameForErrorMessage(_type, _returnTypeCollectionKind, _isRefType), this.FQName)); } } if (_returnTypeList != null) { foreach (ReturnType returnType in _returnTypeList) { // FunctiomImportElement has additional validation for return types. returnType.Validate(); } } if (_parameters != null) { foreach (var parameter in _parameters) { parameter.Validate(); } } if (_commandText != null) { _commandText.Validate(); } } internal override void ResolveSecondLevelNames() { foreach (var parameter in _parameters) { parameter.ResolveSecondLevelNames(); } } internal override SchemaElement Clone(SchemaElement parentElement) { // We only support clone for FunctionImports. throw Error.NotImplemented(); } protected void CloneSetFunctionFields(Function clone) { clone._isAggregate = _isAggregate; clone._isBuiltIn = _isBuiltIn; clone._isNiladicFunction = _isNiladicFunction; clone._isComposable = _isComposable; clone._commandText = _commandText; clone._storeFunctionName = _storeFunctionName; clone._type = _type; clone._returnTypeList = _returnTypeList; clone._returnTypeCollectionKind = _returnTypeCollectionKind; clone._parameterTypeSemantics = _parameterTypeSemantics; clone._schema = _schema; clone.Name = this.Name; // Clone all the parameters foreach (Parameter parameter in this.Parameters) { AddErrorKind error = clone.Parameters.TryAdd((Parameter)parameter.Clone(clone)); Debug.Assert(error == AddErrorKind.Succeeded, "Since we are cloning a validated function, this should never fail."); } } #endregion #region Internal Properties /// /// /// /// internal string UnresolvedReturnType { get { return _unresolvedType; } set { _unresolvedType = value; } } #endregion //Internal Properties #region Private Methods /// /// The method that is called when a DbSchema attribute is encountered. /// /// An XmlReader positioned at the Type attribute. private void HandleDbSchemaAttribute(XmlReader reader) { Debug.Assert(Schema.DataModel == SchemaDataModelOption.ProviderDataModel, "We shouldn't see this attribute unless we are parsing ssdl"); Debug.Assert(reader != null); _schema = reader.Value; } /// /// Handler for the Version attribute /// /// xml reader currently positioned at Version attribute private void HandleAggregateAttribute(XmlReader reader) { Debug.Assert(reader != null); bool isAggregate = false; HandleBoolAttribute(reader, ref isAggregate); IsAggregate = isAggregate; } /// /// Handler for the Namespace attribute /// /// xml reader currently positioned at Namespace attribute private void HandleBuiltInAttribute(XmlReader reader) { Debug.Assert(reader != null); bool isBuiltIn = false; HandleBoolAttribute(reader, ref isBuiltIn); IsBuiltIn = isBuiltIn; } /// /// Handler for the Alias attribute /// /// xml reader currently positioned at Alias attribute private void HandleStoreFunctionNameAttribute(XmlReader reader) { Debug.Assert(reader != null); string value = reader.Value.ToString(); if (!String.IsNullOrEmpty(value)) { value = value.Trim(); StoreFunctionName = value; } } /// /// Handler for the NiladicFunctionAttribute attribute /// /// xml reader currently positioned at Namespace attribute private void HandleNiladicFunctionAttribute(XmlReader reader) { Debug.Assert(reader != null); bool isNiladicFunction = false; HandleBoolAttribute(reader, ref isNiladicFunction); IsNiladicFunction = isNiladicFunction; } /// /// Handler for the IsComposableAttribute attribute /// /// xml reader currently positioned at Namespace attribute private void HandleIsComposableAttribute(XmlReader reader) { Debug.Assert(reader != null); bool isComposable = true; HandleBoolAttribute(reader, ref isComposable); IsComposable = isComposable; } private void HandleCommandTextFunctionElment(XmlReader reader) { Debug.Assert(reader != null); FunctionCommandText commandText = new FunctionCommandText(this); commandText.Parse(reader); _commandText = commandText; } protected virtual void HandleReturnTypeAttribute(XmlReader reader) { Debug.Assert(reader != null); Debug.Assert(UnresolvedReturnType == null); string type; if (!Utils.GetString(Schema, reader, out type)) return; TypeModifier typeModifier; RemoveTypeModifier(ref type, out typeModifier, out _isRefType); switch (typeModifier) { case TypeModifier.Array: CollectionKind = CollectionKind.Bag; break; case TypeModifier.None: break; default: Debug.Assert(false, "RemoveTypeModifier already checks for this"); break; } if (!Utils.ValidateDottedName(Schema, reader, type)) return; UnresolvedReturnType = type; } /// /// Handler for the Parameter Element /// /// xml reader currently positioned at Parameter Element protected void HandleParameterElement(XmlReader reader) { Debug.Assert(reader != null); Parameter parameter = new Parameter(this); parameter.Parse(reader); Parameters.Add(parameter, true, Strings.ParameterNameAlreadyDefinedDuplicate); } /// /// Handler for the ReturnType element /// /// xml reader currently positioned at ReturnType element protected void HandleReturnTypeElement(XmlReader reader) { Debug.Assert(reader != null); ReturnType returnType = new ReturnType(this); returnType.Parse(reader); if (this._returnTypeList == null) { this._returnTypeList = new List(); } this._returnTypeList.Add(returnType); } /// /// Handles ParameterTypeSemantics attribute /// /// private void HandleParameterTypeSemanticsAttribute(XmlReader reader) { Debug.Assert(reader != null); string value = reader.Value; if (String.IsNullOrEmpty(value)) { return; } value = value.Trim(); if (!String.IsNullOrEmpty(value)) { switch (value) { case "ExactMatchOnly": ParameterTypeSemantics = ParameterTypeSemantics.ExactMatchOnly; break; case "AllowImplicitPromotion": ParameterTypeSemantics = ParameterTypeSemantics.AllowImplicitPromotion; break; case "AllowImplicitConversion": ParameterTypeSemantics = ParameterTypeSemantics.AllowImplicitConversion; break; default: // don't try to use the name of the function, because we are still parsing the // attributes, and we may not be to the name attribute yet. AddError(ErrorCode.InvalidValueForParameterTypeSemantics, EdmSchemaErrorSeverity.Error, reader, System.Data.Entity.Strings.InvalidValueForParameterTypeSemanticsAttribute( value)); break; } } } #endregion } }