private object EvalFunction(FunctionId id, object[] argumentValues, DataRow row, DataRowVersion version)
{
StorageType storageType;
switch (id)
{
case FunctionId.Abs:
Debug.Assert(_argumentCount == 1, "Invalid argument argumentCount for " + s_funcs[_info]._name + " : " + _argumentCount.ToString(FormatProvider));
storageType = DataStorage.GetStorageType(argumentValues[0].GetType());
if (ExpressionNode.IsInteger(storageType))
{
return(Math.Abs((long)argumentValues[0]));
}
if (ExpressionNode.IsNumeric(storageType))
{
return(Math.Abs((double)argumentValues[0]));
}
throw ExprException.ArgumentTypeInteger(s_funcs[_info]._name, 1);
case FunctionId.cBool:
Debug.Assert(_argumentCount == 1, "Invalid argument argumentCount for " + s_funcs[_info]._name + " : " + _argumentCount.ToString(FormatProvider));
storageType = DataStorage.GetStorageType(argumentValues[0].GetType());
switch (storageType)
{
case StorageType.Boolean:
return((bool)argumentValues[0]);
case StorageType.Int32:
return((int)argumentValues[0] != 0);
case StorageType.Double:
return((double)argumentValues[0] != 0.0);
case StorageType.String:
return(bool.Parse((string)argumentValues[0]));
default:
throw ExprException.DatatypeConvertion(argumentValues[0].GetType(), typeof(bool));
}
case FunctionId.cInt:
Debug.Assert(_argumentCount == 1, "Invalid argument argumentCount for " + s_funcs[_info]._name + " : " + _argumentCount.ToString(FormatProvider));
return(Convert.ToInt32(argumentValues[0], FormatProvider));
case FunctionId.cDate:
Debug.Assert(_argumentCount == 1, "Invalid argument argumentCount for " + s_funcs[_info]._name + " : " + _argumentCount.ToString(FormatProvider));
return(Convert.ToDateTime(argumentValues[0], FormatProvider));
case FunctionId.cDbl:
Debug.Assert(_argumentCount == 1, "Invalid argument argumentCount for " + s_funcs[_info]._name + " : " + _argumentCount.ToString(FormatProvider));
return(Convert.ToDouble(argumentValues[0], FormatProvider));
case FunctionId.cStr:
Debug.Assert(_argumentCount == 1, "Invalid argument argumentCount for " + s_funcs[_info]._name + " : " + _argumentCount.ToString(FormatProvider));
return(Convert.ToString(argumentValues[0], FormatProvider));
case FunctionId.Charindex:
Debug.Assert(_argumentCount == 2, "Invalid argument argumentCount for " + s_funcs[_info]._name + " : " + _argumentCount.ToString(FormatProvider));
Debug.Assert(argumentValues[0] is string, "Invalid argument type for " + s_funcs[_info]._name);
Debug.Assert(argumentValues[1] is string, "Invalid argument type for " + s_funcs[_info]._name);
if (DataStorage.IsObjectNull(argumentValues[0]) || DataStorage.IsObjectNull(argumentValues[1]))
{
return(DBNull.Value);
}
if (argumentValues[0] is SqlString)
{
argumentValues[0] = ((SqlString)argumentValues[0]).Value;
}
if (argumentValues[1] is SqlString)
{
argumentValues[1] = ((SqlString)argumentValues[1]).Value;
}
return(((string)argumentValues[1]).IndexOf((string)argumentValues[0], StringComparison.Ordinal));
case FunctionId.Iif:
Debug.Assert(_argumentCount == 3, "Invalid argument argumentCount: " + _argumentCount.ToString(FormatProvider));
object first = _arguments[0].Eval(row, version);
if (DataExpression.ToBoolean(first) != false)
{
return(_arguments[1].Eval(row, version));
}
else
{
return(_arguments[2].Eval(row, version));
}
case FunctionId.In:
// we never evaluate IN directly: IN as a binary operator, so evaluation of this should be in
// BinaryNode class
throw ExprException.NYI(s_funcs[_info]._name);
case FunctionId.IsNull:
Debug.Assert(_argumentCount == 2, "Invalid argument argumentCount: ");
if (DataStorage.IsObjectNull(argumentValues[0]))
{
return(argumentValues[1]);
}
else
{
return(argumentValues[0]);
}
case FunctionId.Len:
Debug.Assert(_argumentCount == 1, "Invalid argument argumentCount for " + s_funcs[_info]._name + " : " + _argumentCount.ToString(FormatProvider));
Debug.Assert((argumentValues[0] is string) || (argumentValues[0] is SqlString), "Invalid argument type for " + s_funcs[_info]._name);
if (argumentValues[0] is SqlString)
{
if (((SqlString)argumentValues[0]).IsNull)
{
return(DBNull.Value);
}
else
{
argumentValues[0] = ((SqlString)argumentValues[0]).Value;
}
}
return(((string)argumentValues[0]).Length);
case FunctionId.Substring:
Debug.Assert(_argumentCount == 3, "Invalid argument argumentCount: " + _argumentCount.ToString(FormatProvider));
Debug.Assert((argumentValues[0] is string) || (argumentValues[0] is SqlString), "Invalid first argument " + argumentValues[0].GetType().FullName + " in " + s_funcs[_info]._name);
Debug.Assert(argumentValues[1] is int, "Invalid second argument " + argumentValues[1].GetType().FullName + " in " + s_funcs[_info]._name);
Debug.Assert(argumentValues[2] is int, "Invalid third argument " + argumentValues[2].GetType().FullName + " in " + s_funcs[_info]._name);
// work around the differences in .NET and VBA implementation of the Substring function
// 1. The <index> Argument is 0-based in .NET, and 1-based in VBA
// 2. If the <Length> argument is longer then the string length .NET throws an ArgumentException
// but our users still want to get a result.
int start = (int)argumentValues[1] - 1;
int length = (int)argumentValues[2];
if (start < 0)
{
throw ExprException.FunctionArgumentOutOfRange("index", "Substring");
}
if (length < 0)
{
throw ExprException.FunctionArgumentOutOfRange("length", "Substring");
}
if (length == 0)
{
return(string.Empty);
}
if (argumentValues[0] is SqlString)
{
argumentValues[0] = ((SqlString)argumentValues[0]).Value;
}
int src_length = ((string)argumentValues[0]).Length;
if (start > src_length)
{
return(DBNull.Value);
}
if (start + length > src_length)
{
length = src_length - start;
}
return(((string)argumentValues[0]).Substring(start, length));
case FunctionId.Trim:
{
Debug.Assert(_argumentCount == 1, "Invalid argument argumentCount for " + s_funcs[_info]._name + " : " + _argumentCount.ToString(FormatProvider));
Debug.Assert((argumentValues[0] is string) || (argumentValues[0] is SqlString), "Invalid argument type for " + s_funcs[_info]._name);
if (DataStorage.IsObjectNull(argumentValues[0]))
{
return(DBNull.Value);
}
if (argumentValues[0] is SqlString)
{
argumentValues[0] = ((SqlString)argumentValues[0]).Value;
}
return(((string)argumentValues[0]).Trim());
}
case FunctionId.Convert:
if (_argumentCount != 2)
{
throw ExprException.FunctionArgumentCount(_name);
}
if (argumentValues[0] == DBNull.Value)
{
return(DBNull.Value);
}
Type type = (Type)argumentValues[1];
StorageType mytype = DataStorage.GetStorageType(type);
storageType = DataStorage.GetStorageType(argumentValues[0].GetType());
if (mytype == StorageType.DateTimeOffset)
{
if (storageType == StorageType.String)
{
return(SqlConvert.ConvertStringToDateTimeOffset((string)argumentValues[0], FormatProvider));
}
}
if (StorageType.Object != mytype)
{
if ((mytype == StorageType.Guid) && (storageType == StorageType.String))
{
return(new Guid((string)argumentValues[0]));
}
if (ExpressionNode.IsFloatSql(storageType) && ExpressionNode.IsIntegerSql(mytype))
{
if (StorageType.Single == storageType)
{
return(SqlConvert.ChangeType2((float)SqlConvert.ChangeType2(argumentValues[0], StorageType.Single, typeof(float), FormatProvider), mytype, type, FormatProvider));
}
else if (StorageType.Double == storageType)
{
return(SqlConvert.ChangeType2((double)SqlConvert.ChangeType2(argumentValues[0], StorageType.Double, typeof(double), FormatProvider), mytype, type, FormatProvider));
}
else if (StorageType.Decimal == storageType)
{
return(SqlConvert.ChangeType2((decimal)SqlConvert.ChangeType2(argumentValues[0], StorageType.Decimal, typeof(decimal), FormatProvider), mytype, type, FormatProvider));
}
}
// The Convert function can be called lazily, outside of a previous Serialization Guard scope.
// If there was a type limiter scope on the stack at the time this Convert function was created,
// we must manually re-enter the Serialization Guard scope.
DeserializationToken deserializationToken = (_capturedLimiter != null) ? SerializationInfo.StartDeserialization() : default;
using (deserializationToken)
{
return(SqlConvert.ChangeType2(argumentValues[0], mytype, type, FormatProvider));
}
}
return(argumentValues[0]);
case FunctionId.DateTimeOffset:
if (argumentValues[0] == DBNull.Value || argumentValues[1] == DBNull.Value || argumentValues[2] == DBNull.Value)
{
return(DBNull.Value);
}
switch (((DateTime)argumentValues[0]).Kind)
{
case DateTimeKind.Utc:
if ((int)argumentValues[1] != 0 && (int)argumentValues[2] != 0)
{
throw ExprException.MismatchKindandTimeSpan();
}
break;
case DateTimeKind.Local:
if (DateTimeOffset.Now.Offset.Hours != (int)argumentValues[1] && DateTimeOffset.Now.Offset.Minutes != (int)argumentValues[2])
{
throw ExprException.MismatchKindandTimeSpan();
}
break;
case DateTimeKind.Unspecified: break;
}
if ((int)argumentValues[1] < -14 || (int)argumentValues[1] > 14)
{
throw ExprException.InvalidHoursArgument();
}
if ((int)argumentValues[2] < -59 || (int)argumentValues[2] > 59)
{
throw ExprException.InvalidMinutesArgument();
}
// range should be within -14 hours and +14 hours
if ((int)argumentValues[1] == 14 && (int)argumentValues[2] > 0)
{
throw ExprException.InvalidTimeZoneRange();
}
if ((int)argumentValues[1] == -14 && (int)argumentValues[2] < 0)
{
throw ExprException.InvalidTimeZoneRange();
}
return(new DateTimeOffset((DateTime)argumentValues[0], new TimeSpan((int)argumentValues[1], (int)argumentValues[2], 0)));
default:
throw ExprException.UndefinedFunction(s_funcs[_info]._name);
}
}