2016-08-03 10:59:49 +00:00
//---------------------------------------------------------------------
// <copyright file="Function.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//
2017-08-21 15:34:15 +00:00
// @owner Microsoft
// @backupOwner Microsoft
2016-08-03 10:59:49 +00:00
//---------------------------------------------------------------------
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 ;
/// <summary>
/// class representing the Schema element in the schema
/// </summary>
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 < Parameter > _parameters = null ;
protected List < ReturnType > _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 ( @"^(?<modifier>((Collection)|(Ref)))\s*\(\s*(?<typeName>\S*)\s*\)$" , System . Text . RegularExpressions . RegexOptions . Compiled ) ;
/// <summary>
///
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
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
/// <summary>
/// ctor for a schema function
/// </summary>
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 < ReturnType > ReturnTypeList
{
get
{
return null ! = _returnTypeList ? new ReadOnlyCollection < ReturnType > ( _returnTypeList ) : null ;
}
}
public SchemaElementLookUpTable < Parameter > Parameters
{
get
{
if ( _parameters = = null )
{
_parameters = new SchemaElementLookUpTable < Parameter > ( ) ;
}
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 ( ) ;
}
}
/// <summary>
/// Perform local validation on function definition.
/// </summary>
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
/// <summary>
///
/// </summary>
/// <value></value>
internal string UnresolvedReturnType
{
get
{
return _unresolvedType ;
}
set
{
_unresolvedType = value ;
}
}
#endregion //Internal Properties
#region Private Methods
/// <summary>
/// The method that is called when a DbSchema attribute is encountered.
/// </summary>
/// <param name="reader">An XmlReader positioned at the Type attribute.</param>
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 ;
}
/// <summary>
/// Handler for the Version attribute
/// </summary>
/// <param name="reader">xml reader currently positioned at Version attribute</param>
private void HandleAggregateAttribute ( XmlReader reader )
{
Debug . Assert ( reader ! = null ) ;
bool isAggregate = false ;
HandleBoolAttribute ( reader , ref isAggregate ) ;
IsAggregate = isAggregate ;
}
/// <summary>
/// Handler for the Namespace attribute
/// </summary>
/// <param name="reader">xml reader currently positioned at Namespace attribute</param>
private void HandleBuiltInAttribute ( XmlReader reader )
{
Debug . Assert ( reader ! = null ) ;
bool isBuiltIn = false ;
HandleBoolAttribute ( reader , ref isBuiltIn ) ;
IsBuiltIn = isBuiltIn ;
}
/// <summary>
/// Handler for the Alias attribute
/// </summary>
/// <param name="reader">xml reader currently positioned at Alias attribute</param>
private void HandleStoreFunctionNameAttribute ( XmlReader reader )
{
Debug . Assert ( reader ! = null ) ;
string value = reader . Value . ToString ( ) ;
if ( ! String . IsNullOrEmpty ( value ) )
{
value = value . Trim ( ) ;
StoreFunctionName = value ;
}
}
/// <summary>
/// Handler for the NiladicFunctionAttribute attribute
/// </summary>
/// <param name="reader">xml reader currently positioned at Namespace attribute</param>
private void HandleNiladicFunctionAttribute ( XmlReader reader )
{
Debug . Assert ( reader ! = null ) ;
bool isNiladicFunction = false ;
HandleBoolAttribute ( reader , ref isNiladicFunction ) ;
IsNiladicFunction = isNiladicFunction ;
}
/// <summary>
/// Handler for the IsComposableAttribute attribute
/// </summary>
/// <param name="reader">xml reader currently positioned at Namespace attribute</param>
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 ;
}
/// <summary>
/// Handler for the Parameter Element
/// </summary>
/// <param name="reader">xml reader currently positioned at Parameter Element</param>
protected void HandleParameterElement ( XmlReader reader )
{
Debug . Assert ( reader ! = null ) ;
Parameter parameter = new Parameter ( this ) ;
parameter . Parse ( reader ) ;
Parameters . Add ( parameter , true , Strings . ParameterNameAlreadyDefinedDuplicate ) ;
}
/// <summary>
/// Handler for the ReturnType element
/// </summary>
/// <param name="reader">xml reader currently positioned at ReturnType element</param>
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 < ReturnType > ( ) ;
}
this . _returnTypeList . Add ( returnType ) ;
}
/// <summary>
/// Handles ParameterTypeSemantics attribute
/// </summary>
/// <param name="reader"></param>
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
}
}