536cd135cc
Former-commit-id: 5624ac747d633e885131e8349322922b6a59baaa
1828 lines
94 KiB
C#
1828 lines
94 KiB
C#
//---------------------------------------------------------------------
|
|
// <copyright file="SqlFunctionCallHandler.cs" company="Microsoft">
|
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
// </copyright>
|
|
//
|
|
// @owner Microsoft
|
|
// @backupOwner Microsoft
|
|
//---------------------------------------------------------------------
|
|
|
|
namespace System.Data.SqlClient.SqlGen
|
|
{
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Data.Common;
|
|
using System.Data.Common.CommandTrees;
|
|
using System.Data.Common.CommandTrees.ExpressionBuilder;
|
|
using System.Data.Common.Utils;
|
|
using System.Data.Metadata.Edm;
|
|
using System.Data.Spatial;
|
|
using System.Data.SqlClient;
|
|
using System.Diagnostics;
|
|
using System.Linq;
|
|
using System.Text;
|
|
|
|
/// <summary>
|
|
/// Enacapsulates the logic required to translate function calls represented as instances of DbFunctionExpression into SQL.
|
|
/// There are several special cases that modify how the translation should proceed. These include:
|
|
/// - 'Special' canonical functions, for which the function name or arguments differ between the EDM canonical function and the SQL function
|
|
/// - 'Special' server functions, which are similar to the 'special' canonical functions but sourced by the SQL Server provider manifest
|
|
/// - Niladic functions, which require the parentheses that would usually follow the function name to be omitted
|
|
/// - Spatial canonical functions, which must translate to a static method call, instance method call, or instance property access against
|
|
/// one of the built-in spatial CLR UDTs (geography/geometry).
|
|
/// </summary>
|
|
internal static class SqlFunctionCallHandler
|
|
{
|
|
#region Static fields, include dictionaries used to dispatch function handling
|
|
|
|
static private readonly Dictionary<string, FunctionHandler> _storeFunctionHandlers = InitializeStoreFunctionHandlers();
|
|
static private readonly Dictionary<string, FunctionHandler> _canonicalFunctionHandlers = InitializeCanonicalFunctionHandlers();
|
|
static private readonly Dictionary<string, string> _functionNameToOperatorDictionary = InitializeFunctionNameToOperatorDictionary();
|
|
static private readonly Dictionary<string, string> _dateAddFunctionNameToDatepartDictionary = InitializeDateAddFunctionNameToDatepartDictionary();
|
|
static private readonly Dictionary<string, string> _dateDiffFunctionNameToDatepartDictionary = InitializeDateDiffFunctionNameToDatepartDictionary();
|
|
static private readonly Dictionary<string, FunctionHandler> _geographyFunctionNameToStaticMethodHandlerDictionary = InitializeGeographyStaticMethodFunctionsDictionary();
|
|
static private readonly Dictionary<string, string> _geographyFunctionNameToInstancePropertyNameDictionary = InitializeGeographyInstancePropertyFunctionsDictionary();
|
|
static private readonly Dictionary<string, string> _geographyRenamedInstanceMethodFunctionDictionary = InitializeRenamedGeographyInstanceMethodFunctions();
|
|
static private readonly Dictionary<string, FunctionHandler> _geometryFunctionNameToStaticMethodHandlerDictionary = InitializeGeometryStaticMethodFunctionsDictionary();
|
|
static private readonly Dictionary<string, string> _geometryFunctionNameToInstancePropertyNameDictionary = InitializeGeometryInstancePropertyFunctionsDictionary();
|
|
static private readonly Dictionary<string, string> _geometryRenamedInstanceMethodFunctionDictionary = InitializeRenamedGeometryInstanceMethodFunctions();
|
|
static private readonly Set<string> _datepartKeywords = new Set<string>(new string[] { "year", "yy", "yyyy",
|
|
"quarter", "qq", "q",
|
|
"month", "mm", "m",
|
|
"dayofyear", "dy", "y",
|
|
"day", "dd", "d",
|
|
"week", "wk", "ww",
|
|
"weekday", "dw", "w",
|
|
"hour", "hh",
|
|
"minute", "mi", "n",
|
|
"second", "ss", "s",
|
|
"millisecond", "ms",
|
|
"microsecond", "mcs",
|
|
"nanosecond", "ns",
|
|
"tzoffset", "tz",
|
|
"iso_week", "isoww", "isowk"},
|
|
StringComparer.OrdinalIgnoreCase).MakeReadOnly();
|
|
static private readonly Set<string> _functionRequiresReturnTypeCastToInt64 = new Set<string>(new string[] { "SqlServer.CHARINDEX" },
|
|
StringComparer.Ordinal).MakeReadOnly();
|
|
static private readonly Set<string> _functionRequiresReturnTypeCastToInt32 = new Set<string>(new string[] { "SqlServer.LEN" ,
|
|
"SqlServer.PATINDEX" ,
|
|
"SqlServer.DATALENGTH" ,
|
|
"SqlServer.CHARINDEX" ,
|
|
"Edm.IndexOf" ,
|
|
"Edm.Length" },
|
|
StringComparer.Ordinal).MakeReadOnly();
|
|
static private readonly Set<string> _functionRequiresReturnTypeCastToInt16 = new Set<string>(new string[] { "Edm.Abs" },
|
|
StringComparer.Ordinal).MakeReadOnly();
|
|
static private readonly Set<string> _functionRequiresReturnTypeCastToSingle = new Set<string>(new string[] { "Edm.Abs" ,
|
|
"Edm.Round" ,
|
|
"Edm.Floor" ,
|
|
"Edm.Ceiling" },
|
|
StringComparer.Ordinal).MakeReadOnly();
|
|
static private readonly Set<string> _maxTypeNames = new Set<string>(new string[] { "varchar(max)" ,
|
|
"nvarchar(max)" ,
|
|
"text" ,
|
|
"ntext" ,
|
|
"varbinary(max)" ,
|
|
"image" ,
|
|
"xml" },
|
|
StringComparer.Ordinal).MakeReadOnly();
|
|
|
|
#endregion
|
|
|
|
#region Static dictionary initialization
|
|
|
|
private delegate ISqlFragment FunctionHandler(SqlGenerator sqlgen, DbFunctionExpression functionExpr);
|
|
|
|
/// <summary>
|
|
/// All special store functions and their handlers
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
private static Dictionary<string, FunctionHandler> InitializeStoreFunctionHandlers()
|
|
{
|
|
Dictionary<string, FunctionHandler> functionHandlers = new Dictionary<string, FunctionHandler>(15, StringComparer.Ordinal);
|
|
functionHandlers.Add("CONCAT", HandleConcatFunction);
|
|
functionHandlers.Add("DATEADD", HandleDatepartDateFunction);
|
|
functionHandlers.Add("DATEDIFF", HandleDatepartDateFunction);
|
|
functionHandlers.Add("DATENAME", HandleDatepartDateFunction);
|
|
functionHandlers.Add("DATEPART", HandleDatepartDateFunction);
|
|
|
|
// Spatial functions are mapped to static or instance members of geography/geometry
|
|
// Geography Static functions
|
|
functionHandlers.Add("POINTGEOGRAPHY", (sqlgen, functionExpression) => HandleFunctionDefaultGivenName(sqlgen, functionExpression, "geography::Point"));
|
|
|
|
// Geometry Static functions
|
|
functionHandlers.Add("POINTGEOMETRY", (sqlgen, functionExpression) => HandleFunctionDefaultGivenName(sqlgen, functionExpression, "geometry::Point"));
|
|
|
|
// Spatial Instance functions (shared)
|
|
functionHandlers.Add("ASTEXTZM", (sqlgen, functionExpression) => WriteInstanceFunctionCall(sqlgen, "AsTextZM", functionExpression, isPropertyAccess: false));
|
|
functionHandlers.Add("BUFFERWITHTOLERANCE", (sqlgen, functionExpression) => WriteInstanceFunctionCall(sqlgen, "BufferWithTolerance", functionExpression, isPropertyAccess: false));
|
|
functionHandlers.Add("ENVELOPEANGLE", (sqlgen, functionExpression) => WriteInstanceFunctionCall(sqlgen, "EnvelopeAngle", functionExpression, isPropertyAccess: false));
|
|
functionHandlers.Add("ENVELOPECENTER", (sqlgen, functionExpression) => WriteInstanceFunctionCall(sqlgen, "EnvelopeCenter", functionExpression, isPropertyAccess: false));
|
|
functionHandlers.Add("INSTANCEOF", (sqlgen, functionExpression) => WriteInstanceFunctionCall(sqlgen, "InstanceOf", functionExpression, isPropertyAccess: false));
|
|
functionHandlers.Add("FILTER", (sqlgen, functionExpression) => WriteInstanceFunctionCall(sqlgen, "Filter", functionExpression, isPropertyAccess: false));
|
|
functionHandlers.Add("MAKEVALID", (sqlgen, functionExpression) => WriteInstanceFunctionCall(sqlgen, "MakeValid", functionExpression, isPropertyAccess: false));
|
|
functionHandlers.Add("REDUCE", (sqlgen, functionExpression) => WriteInstanceFunctionCall(sqlgen, "Reduce", functionExpression, isPropertyAccess: false));
|
|
functionHandlers.Add("NUMRINGS", (sqlgen, functionExpression) => WriteInstanceFunctionCall(sqlgen, "NumRings", functionExpression, isPropertyAccess: false));
|
|
functionHandlers.Add("RINGN", (sqlgen, functionExpression) => WriteInstanceFunctionCall(sqlgen, "RingN", functionExpression, isPropertyAccess: false));
|
|
|
|
return functionHandlers;
|
|
}
|
|
|
|
/// <summary>
|
|
/// All special non-aggregate canonical functions and their handlers
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
private static Dictionary<string, FunctionHandler> InitializeCanonicalFunctionHandlers()
|
|
{
|
|
Dictionary<string, FunctionHandler> functionHandlers = new Dictionary<string, FunctionHandler>(16, StringComparer.Ordinal);
|
|
functionHandlers.Add("IndexOf", HandleCanonicalFunctionIndexOf);
|
|
functionHandlers.Add("Length", HandleCanonicalFunctionLength);
|
|
functionHandlers.Add("NewGuid", HandleCanonicalFunctionNewGuid);
|
|
functionHandlers.Add("Round", HandleCanonicalFunctionRound);
|
|
functionHandlers.Add("Truncate", HandleCanonicalFunctionTruncate);
|
|
functionHandlers.Add("Abs", HandleCanonicalFunctionAbs);
|
|
functionHandlers.Add("ToLower", HandleCanonicalFunctionToLower);
|
|
functionHandlers.Add("ToUpper", HandleCanonicalFunctionToUpper);
|
|
functionHandlers.Add("Trim", HandleCanonicalFunctionTrim);
|
|
functionHandlers.Add("Contains", HandleCanonicalFunctionContains);
|
|
functionHandlers.Add("StartsWith", HandleCanonicalFunctionStartsWith);
|
|
functionHandlers.Add("EndsWith", HandleCanonicalFunctionEndsWith);
|
|
|
|
//DateTime Functions
|
|
functionHandlers.Add("Year", HandleCanonicalFunctionDatepart);
|
|
functionHandlers.Add("Month", HandleCanonicalFunctionDatepart);
|
|
functionHandlers.Add("Day", HandleCanonicalFunctionDatepart);
|
|
functionHandlers.Add("Hour", HandleCanonicalFunctionDatepart);
|
|
functionHandlers.Add("Minute", HandleCanonicalFunctionDatepart);
|
|
functionHandlers.Add("Second", HandleCanonicalFunctionDatepart);
|
|
functionHandlers.Add("Millisecond", HandleCanonicalFunctionDatepart);
|
|
functionHandlers.Add("DayOfYear", HandleCanonicalFunctionDatepart);
|
|
functionHandlers.Add("CurrentDateTime", HandleCanonicalFunctionCurrentDateTime);
|
|
functionHandlers.Add("CurrentUtcDateTime", HandleCanonicalFunctionCurrentUtcDateTime);
|
|
functionHandlers.Add("CurrentDateTimeOffset", HandleCanonicalFunctionCurrentDateTimeOffset);
|
|
functionHandlers.Add("GetTotalOffsetMinutes", HandleCanonicalFunctionGetTotalOffsetMinutes);
|
|
functionHandlers.Add("TruncateTime", HandleCanonicalFunctionTruncateTime);
|
|
functionHandlers.Add("CreateDateTime", HandleCanonicalFunctionCreateDateTime);
|
|
functionHandlers.Add("CreateDateTimeOffset", HandleCanonicalFunctionCreateDateTimeOffset);
|
|
functionHandlers.Add("CreateTime", HandleCanonicalFunctionCreateTime);
|
|
functionHandlers.Add("AddYears", HandleCanonicalFunctionDateAdd);
|
|
functionHandlers.Add("AddMonths", HandleCanonicalFunctionDateAdd);
|
|
functionHandlers.Add("AddDays", HandleCanonicalFunctionDateAdd);
|
|
functionHandlers.Add("AddHours", HandleCanonicalFunctionDateAdd);
|
|
functionHandlers.Add("AddMinutes", HandleCanonicalFunctionDateAdd);
|
|
functionHandlers.Add("AddSeconds", HandleCanonicalFunctionDateAdd);
|
|
functionHandlers.Add("AddMilliseconds", HandleCanonicalFunctionDateAdd);
|
|
functionHandlers.Add("AddMicroseconds", HandleCanonicalFunctionDateAddKatmaiOrNewer);
|
|
functionHandlers.Add("AddNanoseconds", HandleCanonicalFunctionDateAddKatmaiOrNewer);
|
|
functionHandlers.Add("DiffYears", HandleCanonicalFunctionDateDiff);
|
|
functionHandlers.Add("DiffMonths", HandleCanonicalFunctionDateDiff);
|
|
functionHandlers.Add("DiffDays", HandleCanonicalFunctionDateDiff);
|
|
functionHandlers.Add("DiffHours", HandleCanonicalFunctionDateDiff);
|
|
functionHandlers.Add("DiffMinutes", HandleCanonicalFunctionDateDiff);
|
|
functionHandlers.Add("DiffSeconds", HandleCanonicalFunctionDateDiff);
|
|
functionHandlers.Add("DiffMilliseconds", HandleCanonicalFunctionDateDiff);
|
|
functionHandlers.Add("DiffMicroseconds", HandleCanonicalFunctionDateDiffKatmaiOrNewer);
|
|
functionHandlers.Add("DiffNanoseconds", HandleCanonicalFunctionDateDiffKatmaiOrNewer);
|
|
|
|
//Functions that translate to operators
|
|
functionHandlers.Add("Concat", HandleConcatFunction);
|
|
functionHandlers.Add("BitwiseAnd", HandleCanonicalFunctionBitwise);
|
|
functionHandlers.Add("BitwiseNot", HandleCanonicalFunctionBitwise);
|
|
functionHandlers.Add("BitwiseOr", HandleCanonicalFunctionBitwise);
|
|
functionHandlers.Add("BitwiseXor", HandleCanonicalFunctionBitwise);
|
|
|
|
return functionHandlers;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initalizes the mapping from functions to TSql operators
|
|
/// for all functions that translate to TSql operators
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
private static Dictionary<string, string> InitializeFunctionNameToOperatorDictionary()
|
|
{
|
|
Dictionary<string, string> functionNameToOperatorDictionary = new Dictionary<string, string>(5, StringComparer.Ordinal);
|
|
functionNameToOperatorDictionary.Add("Concat", "+"); //canonical
|
|
functionNameToOperatorDictionary.Add("CONCAT", "+"); //store
|
|
functionNameToOperatorDictionary.Add("BitwiseAnd", "&");
|
|
functionNameToOperatorDictionary.Add("BitwiseNot", "~");
|
|
functionNameToOperatorDictionary.Add("BitwiseOr", "|");
|
|
functionNameToOperatorDictionary.Add("BitwiseXor", "^");
|
|
return functionNameToOperatorDictionary;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initalizes the mapping from names of canonical function for date/time addition
|
|
/// to corresponding dateparts
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
private static Dictionary<string, string> InitializeDateAddFunctionNameToDatepartDictionary()
|
|
{
|
|
Dictionary<string, string> dateAddFunctionNameToDatepartDictionary = new Dictionary<string, string>(5, StringComparer.Ordinal);
|
|
dateAddFunctionNameToDatepartDictionary.Add("AddYears", "year");
|
|
dateAddFunctionNameToDatepartDictionary.Add("AddMonths", "month");
|
|
dateAddFunctionNameToDatepartDictionary.Add("AddDays", "day");
|
|
dateAddFunctionNameToDatepartDictionary.Add("AddHours", "hour");
|
|
dateAddFunctionNameToDatepartDictionary.Add("AddMinutes", "minute");
|
|
dateAddFunctionNameToDatepartDictionary.Add("AddSeconds", "second");
|
|
dateAddFunctionNameToDatepartDictionary.Add("AddMilliseconds", "millisecond");
|
|
dateAddFunctionNameToDatepartDictionary.Add("AddMicroseconds", "microsecond");
|
|
dateAddFunctionNameToDatepartDictionary.Add("AddNanoseconds", "nanosecond");
|
|
return dateAddFunctionNameToDatepartDictionary;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initalizes the mapping from names of canonical function for date/time difference
|
|
/// to corresponding dateparts
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
private static Dictionary<string, string> InitializeDateDiffFunctionNameToDatepartDictionary()
|
|
{
|
|
Dictionary<string, string> dateDiffFunctionNameToDatepartDictionary = new Dictionary<string, string>(5, StringComparer.Ordinal);
|
|
dateDiffFunctionNameToDatepartDictionary.Add("DiffYears", "year");
|
|
dateDiffFunctionNameToDatepartDictionary.Add("DiffMonths", "month");
|
|
dateDiffFunctionNameToDatepartDictionary.Add("DiffDays", "day");
|
|
dateDiffFunctionNameToDatepartDictionary.Add("DiffHours", "hour");
|
|
dateDiffFunctionNameToDatepartDictionary.Add("DiffMinutes", "minute");
|
|
dateDiffFunctionNameToDatepartDictionary.Add("DiffSeconds", "second");
|
|
dateDiffFunctionNameToDatepartDictionary.Add("DiffMilliseconds", "millisecond");
|
|
dateDiffFunctionNameToDatepartDictionary.Add("DiffMicroseconds", "microsecond");
|
|
dateDiffFunctionNameToDatepartDictionary.Add("DiffNanoseconds", "nanosecond");
|
|
return dateDiffFunctionNameToDatepartDictionary;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initalizes the mapping from names of canonical function that represent static geography methods to their corresponding
|
|
/// static method name, qualified with the 'geography::' prefix.
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
private static Dictionary<string, FunctionHandler> InitializeGeographyStaticMethodFunctionsDictionary()
|
|
{
|
|
Dictionary<string, FunctionHandler> staticGeographyFunctions = new Dictionary<string, FunctionHandler>();
|
|
|
|
// Well Known Text constructors
|
|
staticGeographyFunctions.Add("GeographyFromText", HandleSpatialFromTextFunction);
|
|
staticGeographyFunctions.Add("GeographyPointFromText", (sqlgen, functionExpression) => HandleFunctionDefaultGivenName(sqlgen, functionExpression, "geography::STPointFromText"));
|
|
staticGeographyFunctions.Add("GeographyLineFromText", (sqlgen, functionExpression) => HandleFunctionDefaultGivenName(sqlgen, functionExpression, "geography::STLineFromText"));
|
|
staticGeographyFunctions.Add("GeographyPolygonFromText", (sqlgen, functionExpression) => HandleFunctionDefaultGivenName(sqlgen, functionExpression, "geography::STPolyFromText"));
|
|
staticGeographyFunctions.Add("GeographyMultiPointFromText", (sqlgen, functionExpression) => HandleFunctionDefaultGivenName(sqlgen, functionExpression, "geography::STMPointFromText"));
|
|
staticGeographyFunctions.Add("GeographyMultiLineFromText", (sqlgen, functionExpression) => HandleFunctionDefaultGivenName(sqlgen, functionExpression, "geography::STMLineFromText"));
|
|
staticGeographyFunctions.Add("GeographyMultiPolygonFromText", (sqlgen, functionExpression) => HandleFunctionDefaultGivenName(sqlgen, functionExpression, "geography::STMPolyFromText"));
|
|
staticGeographyFunctions.Add("GeographyCollectionFromText", (sqlgen, functionExpression) => HandleFunctionDefaultGivenName(sqlgen, functionExpression, "geography::STGeomCollFromText"));
|
|
|
|
// Well Known Binary constructors
|
|
staticGeographyFunctions.Add("GeographyFromBinary", HandleSpatialFromBinaryFunction);
|
|
staticGeographyFunctions.Add("GeographyPointFromBinary", (sqlgen, functionExpression) => HandleFunctionDefaultGivenName(sqlgen, functionExpression, "geography::STPointFromWKB"));
|
|
staticGeographyFunctions.Add("GeographyLineFromBinary", (sqlgen, functionExpression) => HandleFunctionDefaultGivenName(sqlgen, functionExpression, "geography::STLineFromWKB"));
|
|
staticGeographyFunctions.Add("GeographyPolygonFromBinary", (sqlgen, functionExpression) => HandleFunctionDefaultGivenName(sqlgen, functionExpression, "geography::STPolyFromWKB"));
|
|
staticGeographyFunctions.Add("GeographyMultiPointFromBinary", (sqlgen, functionExpression) => HandleFunctionDefaultGivenName(sqlgen, functionExpression, "geography::STMPointFromWKB"));
|
|
staticGeographyFunctions.Add("GeographyMultiLineFromBinary", (sqlgen, functionExpression) => HandleFunctionDefaultGivenName(sqlgen, functionExpression, "geography::STMLineFromWKB"));
|
|
staticGeographyFunctions.Add("GeographyMultiPolygonFromBinary", (sqlgen, functionExpression) => HandleFunctionDefaultGivenName(sqlgen, functionExpression, "geography::STMPolyFromWKB"));
|
|
staticGeographyFunctions.Add("GeographyCollectionFromBinary", (sqlgen, functionExpression) => HandleFunctionDefaultGivenName(sqlgen, functionExpression, "geography::STGeomCollFromWKB"));
|
|
|
|
// GML constructor (non-OGC)
|
|
staticGeographyFunctions.Add("GeographyFromGml", HandleSpatialFromGmlFunction);
|
|
|
|
return staticGeographyFunctions;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initalizes the mapping from names of canonical function that represent geography instance properties to their corresponding
|
|
/// store property name.
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
private static Dictionary<string, string> InitializeGeographyInstancePropertyFunctionsDictionary()
|
|
{
|
|
Dictionary<string, string> instancePropGeographyFunctions = new Dictionary<string, string>();
|
|
|
|
instancePropGeographyFunctions.Add("CoordinateSystemId", "STSrid");
|
|
instancePropGeographyFunctions.Add("Latitude", "Lat");
|
|
instancePropGeographyFunctions.Add("Longitude", "Long");
|
|
instancePropGeographyFunctions.Add("Measure", "M");
|
|
instancePropGeographyFunctions.Add("Elevation", "Z");
|
|
|
|
return instancePropGeographyFunctions;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initalizes the mapping of canonical function name to instance method name for geography instance functions that differ in name from the sql server equivalent.
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
private static Dictionary<string, string> InitializeRenamedGeographyInstanceMethodFunctions()
|
|
{
|
|
Dictionary<string, string> renamedInstanceMethodFunctions = new Dictionary<string, string>();
|
|
|
|
renamedInstanceMethodFunctions.Add("AsText", "STAsText");
|
|
renamedInstanceMethodFunctions.Add("AsBinary", "STAsBinary");
|
|
renamedInstanceMethodFunctions.Add("SpatialTypeName", "STGeometryType");
|
|
renamedInstanceMethodFunctions.Add("SpatialDimension", "STDimension");
|
|
renamedInstanceMethodFunctions.Add("IsEmptySpatial", "STIsEmpty");
|
|
renamedInstanceMethodFunctions.Add("SpatialEquals", "STEquals");
|
|
renamedInstanceMethodFunctions.Add("SpatialDisjoint", "STDisjoint");
|
|
renamedInstanceMethodFunctions.Add("SpatialIntersects", "STIntersects");
|
|
renamedInstanceMethodFunctions.Add("SpatialBuffer", "STBuffer");
|
|
renamedInstanceMethodFunctions.Add("Distance", "STDistance");
|
|
renamedInstanceMethodFunctions.Add("SpatialUnion", "STUnion");
|
|
renamedInstanceMethodFunctions.Add("SpatialIntersection", "STIntersection");
|
|
renamedInstanceMethodFunctions.Add("SpatialDifference", "STDifference");
|
|
renamedInstanceMethodFunctions.Add("SpatialSymmetricDifference", "STSymDifference");
|
|
renamedInstanceMethodFunctions.Add("SpatialElementCount", "STNumGeometries");
|
|
renamedInstanceMethodFunctions.Add("SpatialElementAt", "STGeometryN");
|
|
renamedInstanceMethodFunctions.Add("SpatialLength", "STLength");
|
|
renamedInstanceMethodFunctions.Add("StartPoint", "STStartPoint");
|
|
renamedInstanceMethodFunctions.Add("EndPoint", "STEndPoint");
|
|
renamedInstanceMethodFunctions.Add("IsClosedSpatial", "STIsClosed");
|
|
renamedInstanceMethodFunctions.Add("PointCount", "STNumPoints");
|
|
renamedInstanceMethodFunctions.Add("PointAt", "STPointN");
|
|
renamedInstanceMethodFunctions.Add("Area", "STArea");
|
|
|
|
return renamedInstanceMethodFunctions;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initalizes the mapping from names of canonical function that represent static geometry methods to their corresponding
|
|
/// static method name, qualified with the 'geometry::' prefix.
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
private static Dictionary<string, FunctionHandler> InitializeGeometryStaticMethodFunctionsDictionary()
|
|
{
|
|
Dictionary<string, FunctionHandler> staticGeometryFunctions = new Dictionary<string, FunctionHandler>();
|
|
|
|
// Well Known Text constructors
|
|
staticGeometryFunctions.Add("GeometryFromText", HandleSpatialFromTextFunction);
|
|
staticGeometryFunctions.Add("GeometryPointFromText", (sqlgen, functionExpression) => HandleFunctionDefaultGivenName(sqlgen, functionExpression, "geometry::STPointFromText"));
|
|
staticGeometryFunctions.Add("GeometryLineFromText", (sqlgen, functionExpression) => HandleFunctionDefaultGivenName(sqlgen, functionExpression, "geometry::STLineFromText"));
|
|
staticGeometryFunctions.Add("GeometryPolygonFromText", (sqlgen, functionExpression) => HandleFunctionDefaultGivenName(sqlgen, functionExpression, "geometry::STPolyFromText"));
|
|
staticGeometryFunctions.Add("GeometryMultiPointFromText", (sqlgen, functionExpression) => HandleFunctionDefaultGivenName(sqlgen, functionExpression, "geometry::STMPointFromText"));
|
|
staticGeometryFunctions.Add("GeometryMultiLineFromText", (sqlgen, functionExpression) => HandleFunctionDefaultGivenName(sqlgen, functionExpression, "geometry::STMLineFromText"));
|
|
staticGeometryFunctions.Add("GeometryMultiPolygonFromText", (sqlgen, functionExpression) => HandleFunctionDefaultGivenName(sqlgen, functionExpression, "geometry::STMPolyFromText"));
|
|
staticGeometryFunctions.Add("GeometryCollectionFromText", (sqlgen, functionExpression) => HandleFunctionDefaultGivenName(sqlgen, functionExpression, "geometry::STGeomCollFromText"));
|
|
|
|
// Well Known Binary constructors
|
|
staticGeometryFunctions.Add("GeometryFromBinary", HandleSpatialFromBinaryFunction);
|
|
staticGeometryFunctions.Add("GeometryPointFromBinary", (sqlgen, functionExpression) => HandleFunctionDefaultGivenName(sqlgen, functionExpression, "geometry::STPointFromWKB"));
|
|
staticGeometryFunctions.Add("GeometryLineFromBinary", (sqlgen, functionExpression) => HandleFunctionDefaultGivenName(sqlgen, functionExpression, "geometry::STLineFromWKB"));
|
|
staticGeometryFunctions.Add("GeometryPolygonFromBinary", (sqlgen, functionExpression) => HandleFunctionDefaultGivenName(sqlgen, functionExpression, "geometry::STPolyFromWKB"));
|
|
staticGeometryFunctions.Add("GeometryMultiPointFromBinary", (sqlgen, functionExpression) => HandleFunctionDefaultGivenName(sqlgen, functionExpression, "geometry::STMPointFromWKB"));
|
|
staticGeometryFunctions.Add("GeometryMultiLineFromBinary", (sqlgen, functionExpression) => HandleFunctionDefaultGivenName(sqlgen, functionExpression, "geometry::STMLineFromWKB"));
|
|
staticGeometryFunctions.Add("GeometryMultiPolygonFromBinary", (sqlgen, functionExpression) => HandleFunctionDefaultGivenName(sqlgen, functionExpression, "geometry::STMPolyFromWKB"));
|
|
staticGeometryFunctions.Add("GeometryCollectionFromBinary", (sqlgen, functionExpression) => HandleFunctionDefaultGivenName(sqlgen, functionExpression, "geometry::STGeomCollFromWKB"));
|
|
|
|
// GML constructor (non-OGC)
|
|
staticGeometryFunctions.Add("GeometryFromGml", HandleSpatialFromGmlFunction);
|
|
|
|
return staticGeometryFunctions;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initalizes the mapping from names of canonical function that represent geometry instance properties to their corresponding
|
|
/// store property name.
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
private static Dictionary<string, string> InitializeGeometryInstancePropertyFunctionsDictionary()
|
|
{
|
|
Dictionary<string, string> instancePropGeometryFunctions = new Dictionary<string, string>();
|
|
|
|
instancePropGeometryFunctions.Add("CoordinateSystemId", "STSrid");
|
|
instancePropGeometryFunctions.Add("Measure", "M");
|
|
instancePropGeometryFunctions.Add("XCoordinate", "STX");
|
|
instancePropGeometryFunctions.Add("YCoordinate", "STY");
|
|
instancePropGeometryFunctions.Add("Elevation", "Z");
|
|
|
|
return instancePropGeometryFunctions;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initalizes the mapping of canonical function name to instance method name for geometry instance functions that differ in name from the sql server equivalent.
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
private static Dictionary<string, string> InitializeRenamedGeometryInstanceMethodFunctions()
|
|
{
|
|
Dictionary<string, string> renamedInstanceMethodFunctions = new Dictionary<string, string>();
|
|
|
|
renamedInstanceMethodFunctions.Add("AsText", "STAsText");
|
|
renamedInstanceMethodFunctions.Add("AsBinary", "STAsBinary");
|
|
renamedInstanceMethodFunctions.Add("SpatialTypeName", "STGeometryType");
|
|
renamedInstanceMethodFunctions.Add("SpatialDimension", "STDimension");
|
|
renamedInstanceMethodFunctions.Add("IsEmptySpatial", "STIsEmpty");
|
|
renamedInstanceMethodFunctions.Add("IsSimpleGeometry", "STIsSimple");
|
|
renamedInstanceMethodFunctions.Add("IsValidGeometry", "STIsValid");
|
|
renamedInstanceMethodFunctions.Add("SpatialBoundary", "STBoundary");
|
|
renamedInstanceMethodFunctions.Add("SpatialEnvelope", "STEnvelope");
|
|
renamedInstanceMethodFunctions.Add("SpatialEquals", "STEquals");
|
|
renamedInstanceMethodFunctions.Add("SpatialDisjoint", "STDisjoint");
|
|
renamedInstanceMethodFunctions.Add("SpatialIntersects", "STIntersects");
|
|
renamedInstanceMethodFunctions.Add("SpatialTouches", "STTouches");
|
|
renamedInstanceMethodFunctions.Add("SpatialCrosses", "STCrosses");
|
|
renamedInstanceMethodFunctions.Add("SpatialWithin", "STWithin");
|
|
renamedInstanceMethodFunctions.Add("SpatialContains", "STContains");
|
|
renamedInstanceMethodFunctions.Add("SpatialOverlaps", "STOverlaps");
|
|
renamedInstanceMethodFunctions.Add("SpatialRelate", "STRelate");
|
|
renamedInstanceMethodFunctions.Add("SpatialBuffer", "STBuffer");
|
|
renamedInstanceMethodFunctions.Add("SpatialConvexHull", "STConvexHull");
|
|
renamedInstanceMethodFunctions.Add("Distance", "STDistance");
|
|
renamedInstanceMethodFunctions.Add("SpatialUnion", "STUnion");
|
|
renamedInstanceMethodFunctions.Add("SpatialIntersection", "STIntersection");
|
|
renamedInstanceMethodFunctions.Add("SpatialDifference", "STDifference");
|
|
renamedInstanceMethodFunctions.Add("SpatialSymmetricDifference", "STSymDifference");
|
|
renamedInstanceMethodFunctions.Add("SpatialElementCount", "STNumGeometries");
|
|
renamedInstanceMethodFunctions.Add("SpatialElementAt", "STGeometryN");
|
|
renamedInstanceMethodFunctions.Add("SpatialLength", "STLength");
|
|
renamedInstanceMethodFunctions.Add("StartPoint", "STStartPoint");
|
|
renamedInstanceMethodFunctions.Add("EndPoint", "STEndPoint");
|
|
renamedInstanceMethodFunctions.Add("IsClosedSpatial", "STIsClosed");
|
|
renamedInstanceMethodFunctions.Add("IsRing", "STIsRing");
|
|
renamedInstanceMethodFunctions.Add("PointCount", "STNumPoints");
|
|
renamedInstanceMethodFunctions.Add("PointAt", "STPointN");
|
|
renamedInstanceMethodFunctions.Add("Area", "STArea");
|
|
renamedInstanceMethodFunctions.Add("Centroid", "STCentroid");
|
|
renamedInstanceMethodFunctions.Add("PointOnSurface", "STPointOnSurface");
|
|
renamedInstanceMethodFunctions.Add("ExteriorRing", "STExteriorRing");
|
|
renamedInstanceMethodFunctions.Add("InteriorRingCount", "STNumInteriorRing");
|
|
renamedInstanceMethodFunctions.Add("InteriorRingAt", "STInteriorRingN");
|
|
|
|
return renamedInstanceMethodFunctions;
|
|
}
|
|
|
|
private static ISqlFragment HandleSpatialFromTextFunction(SqlGenerator sqlgen, DbFunctionExpression functionExpression)
|
|
{
|
|
string functionNameWithSrid = (TypeSemantics.IsPrimitiveType(functionExpression.ResultType, PrimitiveTypeKind.Geometry) ? "geometry::STGeomFromText" : "geography::STGeomFromText");
|
|
string functionNameWithoutSrid = (TypeSemantics.IsPrimitiveType(functionExpression.ResultType, PrimitiveTypeKind.Geometry) ? "geometry::Parse" : "geography::Parse");
|
|
|
|
if (functionExpression.Arguments.Count == 2)
|
|
{
|
|
return HandleFunctionDefaultGivenName(sqlgen, functionExpression, functionNameWithSrid);
|
|
}
|
|
else
|
|
{
|
|
Debug.Assert(functionExpression.Arguments.Count == 1, "FromText function should have text or text + srid arguments only");
|
|
return HandleFunctionDefaultGivenName(sqlgen, functionExpression, functionNameWithoutSrid);
|
|
}
|
|
}
|
|
|
|
|
|
private static ISqlFragment HandleSpatialFromGmlFunction(SqlGenerator sqlgen, DbFunctionExpression functionExpression)
|
|
{
|
|
return HandleSpatialStaticMethodFunctionAppendSrid(sqlgen, functionExpression, (TypeSemantics.IsPrimitiveType(functionExpression.ResultType, PrimitiveTypeKind.Geometry) ? "geometry::GeomFromGml" : "geography::GeomFromGml"));
|
|
}
|
|
|
|
private static ISqlFragment HandleSpatialFromBinaryFunction(SqlGenerator sqlgen, DbFunctionExpression functionExpression)
|
|
{
|
|
return HandleSpatialStaticMethodFunctionAppendSrid(sqlgen, functionExpression, (TypeSemantics.IsPrimitiveType(functionExpression.ResultType, PrimitiveTypeKind.Geometry) ? "geometry::STGeomFromWKB" : "geography::STGeomFromWKB"));
|
|
}
|
|
|
|
private static readonly DbExpression defaultGeographySridExpression = DbExpressionBuilder.Constant(DbGeography.DefaultCoordinateSystemId);
|
|
private static readonly DbExpression defaultGeometrySridExpression = DbExpressionBuilder.Constant(DbGeometry.DefaultCoordinateSystemId);
|
|
|
|
private static ISqlFragment HandleSpatialStaticMethodFunctionAppendSrid(SqlGenerator sqlgen, DbFunctionExpression functionExpression, string functionName)
|
|
{
|
|
if (functionExpression.Arguments.Count == 2)
|
|
{
|
|
return HandleFunctionDefaultGivenName(sqlgen, functionExpression, functionName);
|
|
}
|
|
else
|
|
{
|
|
DbExpression sridExpression = (TypeSemantics.IsPrimitiveType(functionExpression.ResultType, PrimitiveTypeKind.Geometry) ? defaultGeometrySridExpression : defaultGeographySridExpression);
|
|
SqlBuilder result = new SqlBuilder();
|
|
result.Append(functionName);
|
|
WriteFunctionArguments(sqlgen, functionExpression.Arguments.Concat(new[] { sridExpression }), result);
|
|
return result;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
internal static ISqlFragment GenerateFunctionCallSql(SqlGenerator sqlgen, DbFunctionExpression functionExpression)
|
|
{
|
|
//
|
|
// check if function requires special case processing, if so, delegates to it
|
|
//
|
|
if (IsSpecialCanonicalFunction(functionExpression))
|
|
{
|
|
return HandleSpecialCanonicalFunction(sqlgen, functionExpression);
|
|
}
|
|
|
|
if (IsSpecialStoreFunction(functionExpression))
|
|
{
|
|
return HandleSpecialStoreFunction(sqlgen, functionExpression);
|
|
}
|
|
|
|
PrimitiveTypeKind spatialTypeKind;
|
|
if(IsSpatialCanonicalFunction(functionExpression, out spatialTypeKind))
|
|
{
|
|
return HandleSpatialCanonicalFunction(sqlgen, functionExpression, spatialTypeKind);
|
|
}
|
|
|
|
return HandleFunctionDefault(sqlgen, functionExpression);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Determines whether the given function is a store function that
|
|
/// requires special handling
|
|
/// </summary>
|
|
/// <param name="e"></param>
|
|
/// <returns></returns>
|
|
private static bool IsSpecialStoreFunction(DbFunctionExpression e)
|
|
{
|
|
return IsStoreFunction(e.Function)
|
|
&& _storeFunctionHandlers.ContainsKey(e.Function.Name);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Determines whether the given function is a canonical function that
|
|
/// requires special handling
|
|
/// </summary>
|
|
/// <param name="e"></param>
|
|
/// <returns></returns>
|
|
private static bool IsSpecialCanonicalFunction(DbFunctionExpression e)
|
|
{
|
|
return TypeHelpers.IsCanonicalFunction(e.Function)
|
|
&& _canonicalFunctionHandlers.ContainsKey(e.Function.Name);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Determines whether the given function is a canonical function the translates
|
|
/// to a spatial (geography/geometry) property access or method call.
|
|
/// </summary>
|
|
/// <param name="e"></param>
|
|
/// <returns></returns>
|
|
private static bool IsSpatialCanonicalFunction(DbFunctionExpression e, out PrimitiveTypeKind spatialTypeKind)
|
|
{
|
|
if (TypeHelpers.IsCanonicalFunction(e.Function))
|
|
{
|
|
if (Helper.IsSpatialType(e.ResultType, out spatialTypeKind))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
foreach (FunctionParameter functionParameter in e.Function.Parameters)
|
|
{
|
|
if (Helper.IsSpatialType(functionParameter.TypeUsage, out spatialTypeKind))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
spatialTypeKind = default(PrimitiveTypeKind);
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Default handling for functions.
|
|
/// Translates them to FunctionName(arg1, arg2, ..., argn)
|
|
/// </summary>
|
|
/// <param name="e"></param>
|
|
/// <returns></returns>
|
|
private static ISqlFragment HandleFunctionDefault(SqlGenerator sqlgen, DbFunctionExpression e)
|
|
{
|
|
return HandleFunctionDefaultGivenName(sqlgen, e, null);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Default handling for functions with a given name.
|
|
/// Translates them to FunctionName(arg1, arg2, ..., argn)
|
|
/// </summary>
|
|
/// <param name="e"></param>
|
|
/// <param name="functionName"></param>
|
|
/// <returns></returns>
|
|
private static ISqlFragment HandleFunctionDefaultGivenName(SqlGenerator sqlgen, DbFunctionExpression e, string functionName)
|
|
{
|
|
// NOTE: The order of checks is important in case of CHARINDEX.
|
|
if (CastReturnTypeToInt64(e))
|
|
{
|
|
return HandleFunctionDefaultCastReturnValue(sqlgen, e, functionName, "bigint");
|
|
}
|
|
else if (CastReturnTypeToInt32(sqlgen, e))
|
|
{
|
|
return HandleFunctionDefaultCastReturnValue(sqlgen, e, functionName, "int");
|
|
}
|
|
else if (CastReturnTypeToInt16(e))
|
|
{
|
|
return HandleFunctionDefaultCastReturnValue(sqlgen, e, functionName, "smallint");
|
|
}
|
|
else if (CastReturnTypeToSingle(e))
|
|
{
|
|
return HandleFunctionDefaultCastReturnValue(sqlgen, e, functionName, "real");
|
|
}
|
|
else
|
|
{
|
|
return HandleFunctionDefaultCastReturnValue(sqlgen, e, functionName, null);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Default handling for functions with a given name and given return value cast.
|
|
/// Translates them to CAST(FunctionName(arg1, arg2, ..., argn) AS returnType)
|
|
/// </summary>
|
|
/// <param name="e"></param>
|
|
/// <param name="functionName"></param>
|
|
/// <param name="returnType"></param>
|
|
/// <returns></returns>
|
|
private static ISqlFragment HandleFunctionDefaultCastReturnValue(SqlGenerator sqlgen, DbFunctionExpression e, string functionName, string returnType)
|
|
{
|
|
return WrapWithCast(returnType, result =>
|
|
{
|
|
if (functionName == null)
|
|
{
|
|
WriteFunctionName(result, e.Function);
|
|
}
|
|
else
|
|
{
|
|
result.Append(functionName);
|
|
}
|
|
|
|
HandleFunctionArgumentsDefault(sqlgen, e, result);
|
|
});
|
|
}
|
|
|
|
private static ISqlFragment WrapWithCast(string returnType, Action<SqlBuilder> toWrap)
|
|
{
|
|
SqlBuilder result = new SqlBuilder();
|
|
if (returnType != null)
|
|
{
|
|
result.Append(" CAST(");
|
|
}
|
|
|
|
toWrap(result);
|
|
|
|
if (returnType != null)
|
|
{
|
|
result.Append(" AS ");
|
|
result.Append(returnType);
|
|
result.Append(")");
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Default handling on function arguments.
|
|
/// Appends the list of arguments to the given result
|
|
/// If the function is niladic it does not append anything,
|
|
/// otherwise it appends (arg1, arg2, .., argn)
|
|
/// </summary>
|
|
/// <param name="e"></param>
|
|
/// <param name="result"></param>
|
|
private static void HandleFunctionArgumentsDefault(SqlGenerator sqlgen, DbFunctionExpression e, SqlBuilder result)
|
|
{
|
|
bool isNiladicFunction = e.Function.NiladicFunctionAttribute;
|
|
Debug.Assert(!(isNiladicFunction && (0 < e.Arguments.Count)), "function attributed as NiladicFunction='true' in the provider manifest cannot have arguments");
|
|
if (isNiladicFunction && e.Arguments.Count > 0)
|
|
{
|
|
EntityUtil.Metadata(System.Data.Entity.Strings.SqlGen_NiladicFunctionsCannotHaveParameters);
|
|
}
|
|
|
|
if (!isNiladicFunction)
|
|
{
|
|
WriteFunctionArguments(sqlgen, e.Arguments, result);
|
|
}
|
|
}
|
|
|
|
private static void WriteFunctionArguments(SqlGenerator sqlgen, IEnumerable<DbExpression> functionArguments, SqlBuilder result)
|
|
{
|
|
result.Append("(");
|
|
string separator = "";
|
|
foreach (DbExpression arg in functionArguments)
|
|
{
|
|
result.Append(separator);
|
|
result.Append(arg.Accept(sqlgen));
|
|
separator = ", ";
|
|
}
|
|
result.Append(")");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Handler for functions that need to be translated to different store function based on version
|
|
/// </summary>
|
|
/// <param name="e"></param>
|
|
/// <param name="preKatmaiName"></param>
|
|
/// <param name="katmaiName"></param>
|
|
/// <returns></returns>
|
|
private static ISqlFragment HandleFunctionGivenNameBasedOnVersion(SqlGenerator sqlgen, DbFunctionExpression e, string preKatmaiName, string katmaiName)
|
|
{
|
|
if (sqlgen.IsPreKatmai)
|
|
{
|
|
return HandleFunctionDefaultGivenName(sqlgen, e, preKatmaiName);
|
|
}
|
|
return HandleFunctionDefaultGivenName(sqlgen, e, katmaiName);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Handler for special build in functions
|
|
/// </summary>
|
|
/// <param name="e"></param>
|
|
/// <returns></returns>
|
|
private static ISqlFragment HandleSpecialStoreFunction(SqlGenerator sqlgen, DbFunctionExpression e)
|
|
{
|
|
return HandleSpecialFunction(_storeFunctionHandlers, sqlgen, e);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Handler for special canonical functions
|
|
/// </summary>
|
|
/// <param name="e"></param>
|
|
/// <returns></returns>
|
|
private static ISqlFragment HandleSpecialCanonicalFunction(SqlGenerator sqlgen, DbFunctionExpression e)
|
|
{
|
|
return HandleSpecialFunction(_canonicalFunctionHandlers, sqlgen, e);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Dispatches the special function processing to the appropriate handler
|
|
/// </summary>
|
|
/// <param name="handlers"></param>
|
|
/// <param name="e"></param>
|
|
/// <returns></returns>
|
|
private static ISqlFragment HandleSpecialFunction(Dictionary<string, FunctionHandler> handlers, SqlGenerator sqlgen, DbFunctionExpression e)
|
|
{
|
|
Debug.Assert(handlers.ContainsKey(e.Function.Name), "Special handling should be called only for functions in the list of special functions");
|
|
return handlers[e.Function.Name](sqlgen, e);
|
|
}
|
|
|
|
private static ISqlFragment HandleSpatialCanonicalFunction(SqlGenerator sqlgen, DbFunctionExpression functionExpression, PrimitiveTypeKind spatialTypeKind)
|
|
{
|
|
Debug.Assert(spatialTypeKind == PrimitiveTypeKind.Geography || spatialTypeKind == PrimitiveTypeKind.Geometry, "Spatial function does not refer to a valid spatial primitive type kind?");
|
|
if (spatialTypeKind == PrimitiveTypeKind.Geography)
|
|
{
|
|
return HandleSpatialCanonicalFunction(sqlgen, functionExpression, _geographyFunctionNameToStaticMethodHandlerDictionary, _geographyFunctionNameToInstancePropertyNameDictionary, _geographyRenamedInstanceMethodFunctionDictionary);
|
|
}
|
|
else
|
|
{
|
|
return HandleSpatialCanonicalFunction(sqlgen, functionExpression, _geometryFunctionNameToStaticMethodHandlerDictionary, _geometryFunctionNameToInstancePropertyNameDictionary, _geometryRenamedInstanceMethodFunctionDictionary);
|
|
}
|
|
}
|
|
|
|
private static ISqlFragment HandleSpatialCanonicalFunction(SqlGenerator sqlgen,
|
|
DbFunctionExpression functionExpression,
|
|
Dictionary<string, FunctionHandler> staticMethodsMap,
|
|
Dictionary<string, string> instancePropertiesMap,
|
|
Dictionary<string, string> renamedInstanceMethodsMap)
|
|
{
|
|
FunctionHandler staticFunctionHandler;
|
|
string instancePropertyName;
|
|
if (staticMethodsMap.TryGetValue(functionExpression.Function.Name, out staticFunctionHandler))
|
|
{
|
|
return staticFunctionHandler(sqlgen, functionExpression);
|
|
}
|
|
else if (instancePropertiesMap.TryGetValue(functionExpression.Function.Name, out instancePropertyName))
|
|
{
|
|
Debug.Assert(functionExpression.Function.Parameters.Count > 0 && Helper.IsSpatialType(functionExpression.Function.Parameters[0].TypeUsage), "Instance property function does not have instance parameter?");
|
|
return WriteInstanceFunctionCall(sqlgen, instancePropertyName, functionExpression, isPropertyAccess: true, castReturnTypeTo: null);
|
|
}
|
|
else
|
|
{
|
|
// Default translation pattern is instance method; the instance method name may differ from that of the spatial canonical function
|
|
Debug.Assert(functionExpression.Function.Parameters.Count > 0 && Helper.IsSpatialType(functionExpression.Function.Parameters[0].TypeUsage), "Instance method function does not have instance parameter?");
|
|
string effectiveFunctionName;
|
|
if (!renamedInstanceMethodsMap.TryGetValue(functionExpression.Function.Name, out effectiveFunctionName))
|
|
{
|
|
effectiveFunctionName = functionExpression.Function.Name;
|
|
}
|
|
|
|
// For AsGml() calls, the XML result must be cast to string to match the declared function result type.
|
|
string castResultType = null;
|
|
if (effectiveFunctionName == "AsGml")
|
|
{
|
|
castResultType = sqlgen.DefaultStringTypeName;
|
|
}
|
|
return WriteInstanceFunctionCall(sqlgen, effectiveFunctionName, functionExpression, isPropertyAccess: false, castReturnTypeTo: castResultType);
|
|
}
|
|
}
|
|
|
|
private static ISqlFragment WriteInstanceFunctionCall(SqlGenerator sqlgen, string functionName, DbFunctionExpression functionExpression, bool isPropertyAccess)
|
|
{
|
|
return WriteInstanceFunctionCall(sqlgen, functionName, functionExpression, isPropertyAccess, null);
|
|
}
|
|
|
|
private static ISqlFragment WriteInstanceFunctionCall(SqlGenerator sqlgen, string functionName, DbFunctionExpression functionExpression, bool isPropertyAccess, string castReturnTypeTo)
|
|
{
|
|
Debug.Assert(!isPropertyAccess || functionExpression.Arguments.Count == 1, "Property accessor instance functions should have only the single instance argument");
|
|
|
|
return WrapWithCast(castReturnTypeTo, result =>
|
|
{
|
|
DbExpression instanceExpression = functionExpression.Arguments[0];
|
|
|
|
// Write the instance - if this is another function call, it need not be enclosed in parentheses.
|
|
if (instanceExpression.ExpressionKind != DbExpressionKind.Function)
|
|
{
|
|
sqlgen.ParenthesizeExpressionIfNeeded(instanceExpression, result);
|
|
}
|
|
else
|
|
{
|
|
result.Append(instanceExpression.Accept(sqlgen));
|
|
}
|
|
result.Append(".");
|
|
result.Append(functionName);
|
|
|
|
if (!isPropertyAccess)
|
|
{
|
|
WriteFunctionArguments(sqlgen, functionExpression.Arguments.Skip(1), result);
|
|
}
|
|
|
|
});
|
|
}
|
|
|
|
/// <summary>
|
|
/// Handles functions that are translated into TSQL operators.
|
|
/// The given function should have one or two arguments.
|
|
/// Functions with one arguemnt are translated into
|
|
/// op arg
|
|
/// Functions with two arguments are translated into
|
|
/// arg0 op arg1
|
|
/// Also, the arguments can be optionaly enclosed in parethesis
|
|
/// </summary>
|
|
/// <param name="e"></param>
|
|
/// <param name="parenthesiseArguments">Whether the arguments should be enclosed in parethesis</param>
|
|
/// <returns></returns>
|
|
private static ISqlFragment HandleSpecialFunctionToOperator(SqlGenerator sqlgen, DbFunctionExpression e, bool parenthesiseArguments)
|
|
{
|
|
SqlBuilder result = new SqlBuilder();
|
|
Debug.Assert(e.Arguments.Count > 0 && e.Arguments.Count <= 2, "There should be 1 or 2 arguments for operator");
|
|
|
|
if (e.Arguments.Count > 1)
|
|
{
|
|
if (parenthesiseArguments)
|
|
{
|
|
result.Append("(");
|
|
}
|
|
result.Append(e.Arguments[0].Accept(sqlgen));
|
|
if (parenthesiseArguments)
|
|
{
|
|
result.Append(")");
|
|
}
|
|
}
|
|
result.Append(" ");
|
|
Debug.Assert(_functionNameToOperatorDictionary.ContainsKey(e.Function.Name), "The function can not be mapped to an operator");
|
|
result.Append(_functionNameToOperatorDictionary[e.Function.Name]);
|
|
result.Append(" ");
|
|
|
|
if (parenthesiseArguments)
|
|
{
|
|
result.Append("(");
|
|
}
|
|
result.Append(e.Arguments[e.Arguments.Count - 1].Accept(sqlgen));
|
|
if (parenthesiseArguments)
|
|
{
|
|
result.Append(")");
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// <see cref="HandleSpecialFunctionToOperator"></see>
|
|
/// </summary>
|
|
/// <param name="sqlgen"></param>
|
|
/// <param name="e"></param>
|
|
/// <returns></returns>
|
|
private static ISqlFragment HandleConcatFunction(SqlGenerator sqlgen, DbFunctionExpression e)
|
|
{
|
|
return HandleSpecialFunctionToOperator(sqlgen, e, false);
|
|
}
|
|
|
|
/// <summary>
|
|
/// <see cref="HandleSpecialFunctionToOperator"></see>
|
|
/// </summary>
|
|
/// <param name="sqlgen"></param>
|
|
/// <param name="e"></param>
|
|
/// <returns></returns>
|
|
private static ISqlFragment HandleCanonicalFunctionBitwise(SqlGenerator sqlgen, DbFunctionExpression e)
|
|
{
|
|
return HandleSpecialFunctionToOperator(sqlgen, e, true);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Handles special case in which datapart 'type' parameter is present. all the functions
|
|
/// handles here have *only* the 1st parameter as datepart. datepart value is passed along
|
|
/// the QP as string and has to be expanded as TSQL keyword.
|
|
/// </summary>
|
|
/// <param name="sqlgen"></param>
|
|
/// <param name="e"></param>
|
|
/// <returns></returns>
|
|
private static ISqlFragment HandleDatepartDateFunction(SqlGenerator sqlgen, DbFunctionExpression e)
|
|
{
|
|
Debug.Assert(e.Arguments.Count > 0, "e.Arguments.Count > 0");
|
|
|
|
DbConstantExpression constExpr = e.Arguments[0] as DbConstantExpression;
|
|
if (null == constExpr)
|
|
{
|
|
throw EntityUtil.InvalidOperation(System.Data.Entity.Strings.SqlGen_InvalidDatePartArgumentExpression(e.Function.NamespaceName, e.Function.Name));
|
|
}
|
|
|
|
string datepart = constExpr.Value as string;
|
|
if (null == datepart)
|
|
{
|
|
throw EntityUtil.InvalidOperation(System.Data.Entity.Strings.SqlGen_InvalidDatePartArgumentExpression(e.Function.NamespaceName, e.Function.Name));
|
|
}
|
|
|
|
SqlBuilder result = new SqlBuilder();
|
|
|
|
//
|
|
// check if datepart value is valid
|
|
//
|
|
if (!_datepartKeywords.Contains(datepart))
|
|
{
|
|
throw EntityUtil.InvalidOperation(System.Data.Entity.Strings.SqlGen_InvalidDatePartArgumentValue(datepart, e.Function.NamespaceName, e.Function.Name));
|
|
}
|
|
|
|
//
|
|
// finaly, expand the function name
|
|
//
|
|
WriteFunctionName(result, e.Function);
|
|
result.Append("(");
|
|
|
|
// expand the datepart literal as tsql kword
|
|
result.Append(datepart);
|
|
string separator = ", ";
|
|
|
|
// expand remaining arguments
|
|
for (int i = 1; i < e.Arguments.Count; i++)
|
|
{
|
|
result.Append(separator);
|
|
result.Append(e.Arguments[i].Accept(sqlgen));
|
|
}
|
|
|
|
result.Append(")");
|
|
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Handler for canonical functions for extracting date parts.
|
|
/// For example:
|
|
/// Year(date) -> DATEPART( year, date)
|
|
/// </summary>
|
|
/// <param name="sqlgen"></param>
|
|
/// <param name="e"></param>
|
|
/// <returns></returns>
|
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase")]
|
|
private static ISqlFragment HandleCanonicalFunctionDatepart(SqlGenerator sqlgen, DbFunctionExpression e)
|
|
{
|
|
return HandleCanonicalFunctionDatepart(sqlgen, e.Function.Name.ToLowerInvariant(), e);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Handler for canonical funcitons for GetTotalOffsetMinutes.
|
|
/// GetTotalOffsetMinutes(e) --> Datepart(tzoffset, e)
|
|
/// </summary>
|
|
/// <param name="sqlgen"></param>
|
|
/// <param name="e"></param>
|
|
/// <returns></returns>
|
|
private static ISqlFragment HandleCanonicalFunctionGetTotalOffsetMinutes(SqlGenerator sqlgen, DbFunctionExpression e)
|
|
{
|
|
return HandleCanonicalFunctionDatepart(sqlgen, "tzoffset", e);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Handler for turning a canonical function into DATEPART
|
|
/// Results in DATEPART(datepart, e)
|
|
/// </summary>
|
|
/// <param name="datepart"></param>
|
|
/// <param name="e"></param>
|
|
/// <returns></returns>
|
|
private static ISqlFragment HandleCanonicalFunctionDatepart(SqlGenerator sqlgen, string datepart, DbFunctionExpression e)
|
|
{
|
|
SqlBuilder result = new SqlBuilder();
|
|
result.Append("DATEPART (");
|
|
result.Append(datepart);
|
|
result.Append(", ");
|
|
|
|
Debug.Assert(e.Arguments.Count == 1, "Canonical datepart functions should have exactly one argument");
|
|
result.Append(e.Arguments[0].Accept(sqlgen));
|
|
|
|
result.Append(")");
|
|
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Handler for the canonical function CurrentDateTime
|
|
/// For Sql8 and Sql9: CurrentDateTime() -> GetDate()
|
|
/// For Sql10: CurrentDateTime() -> SysDateTime()
|
|
/// </summary>
|
|
/// <param name="sqlgen"></param>
|
|
/// <param name="e"></param>
|
|
/// <returns></returns>
|
|
private static ISqlFragment HandleCanonicalFunctionCurrentDateTime(SqlGenerator sqlgen, DbFunctionExpression e)
|
|
{
|
|
return HandleFunctionGivenNameBasedOnVersion(sqlgen, e, "GetDate", "SysDateTime");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Handler for the canonical function CurrentUtcDateTime
|
|
/// For Sql8 and Sql9: CurrentUtcDateTime() -> GetUtcDate()
|
|
/// For Sql10: CurrentUtcDateTime() -> SysUtcDateTime()
|
|
/// </summary>
|
|
/// <param name="sqlgen"></param>
|
|
/// <param name="e"></param>
|
|
/// <returns></returns>
|
|
private static ISqlFragment HandleCanonicalFunctionCurrentUtcDateTime(SqlGenerator sqlgen, DbFunctionExpression e)
|
|
{
|
|
return HandleFunctionGivenNameBasedOnVersion(sqlgen, e, "GetUtcDate", "SysUtcDateTime");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Handler for the canonical function CurrentDateTimeOffset
|
|
/// For Sql8 and Sql9: throw
|
|
/// For Sql10: CurrentDateTimeOffset() -> SysDateTimeOffset()
|
|
/// </summary>
|
|
/// <param name="sqlgen"></param>
|
|
/// <param name="e"></param>
|
|
/// <returns></returns>
|
|
private static ISqlFragment HandleCanonicalFunctionCurrentDateTimeOffset(SqlGenerator sqlgen, DbFunctionExpression e)
|
|
{
|
|
sqlgen.AssertKatmaiOrNewer(e);
|
|
return HandleFunctionDefaultGivenName(sqlgen, e, "SysDateTimeOffset");
|
|
}
|
|
|
|
/// <summary>
|
|
/// See <see cref="HandleCanonicalFunctionDateTimeTypeCreation"/> for exact translation
|
|
/// Pre Katmai creates datetime.
|
|
/// On Katmai creates datetime2.
|
|
/// </summary>
|
|
/// <param name="sqlgen"></param>
|
|
/// <param name="e"></param>
|
|
/// <returns></returns>
|
|
private static ISqlFragment HandleCanonicalFunctionCreateDateTime(SqlGenerator sqlgen, DbFunctionExpression e)
|
|
{
|
|
string typeName = (sqlgen.IsPreKatmai) ? "datetime" : "datetime2";
|
|
return HandleCanonicalFunctionDateTimeTypeCreation(sqlgen, typeName, e.Arguments, true, false);
|
|
}
|
|
|
|
/// <summary>
|
|
/// See <see cref="HandleCanonicalFunctionDateTimeTypeCreation"/> for exact translation
|
|
/// Pre Katmai not supported.
|
|
/// On Katmai creates datetimeoffset.
|
|
/// </summary>
|
|
/// <param name="sqlgen"></param>
|
|
/// <param name="e"></param>
|
|
/// <returns></returns>
|
|
private static ISqlFragment HandleCanonicalFunctionCreateDateTimeOffset(SqlGenerator sqlgen, DbFunctionExpression e)
|
|
{
|
|
sqlgen.AssertKatmaiOrNewer(e);
|
|
return HandleCanonicalFunctionDateTimeTypeCreation(sqlgen, "datetimeoffset", e.Arguments, true, true);
|
|
}
|
|
|
|
/// <summary>
|
|
/// See <see cref="HandleCanonicalFunctionDateTimeTypeCreation"/> for exact translation
|
|
/// Pre Katmai not supported.
|
|
/// On Katmai creates time.
|
|
/// </summary>
|
|
/// <param name="sqlgen"></param>
|
|
/// <param name="e"></param>
|
|
/// <returns></returns>
|
|
private static ISqlFragment HandleCanonicalFunctionCreateTime(SqlGenerator sqlgen, DbFunctionExpression e)
|
|
{
|
|
sqlgen.AssertKatmaiOrNewer(e);
|
|
return HandleCanonicalFunctionDateTimeTypeCreation(sqlgen, "time", e.Arguments, false, false);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Helper for all date and time types creating functions.
|
|
///
|
|
/// The given expression is in general trainslated into:
|
|
///
|
|
/// CONVERT(@typename, [datePart] + [timePart] + [timeZonePart], 121), where the datePart and the timeZonePart are optional
|
|
///
|
|
/// Only on Katmai, if a date part is present it is wrapped with a call for adding years as shown below.
|
|
/// The individual parts are translated as:
|
|
///
|
|
/// Date part:
|
|
/// PRE KATMAI: convert(varchar(255), @year) + '-' + convert(varchar(255), @month) + '-' + convert(varchar(255), @day)
|
|
/// KATMAI: DateAdd(year, @year-1, covert(@typename, '0001' + '-' + convert(varchar(255), @month) + '-' + convert(varchar(255), @day) + [possibly time ], 121)
|
|
///
|
|
/// Time part:
|
|
/// PRE KATMAI: convert(varchar(255), @hour)+ ':' + convert(varchar(255), @minute)+ ':' + str(@second, 6, 3)
|
|
/// KATMAI: convert(varchar(255), @hour)+ ':' + convert(varchar(255), @minute)+ ':' + str(@second, 10, 7)
|
|
///
|
|
/// Time zone part:
|
|
/// (case when @tzoffset >= 0 then '+' else '-' end) + convert(varchar(255), ABS(@tzoffset)/60) + ':' + convert(varchar(255), ABS(@tzoffset)%60)
|
|
///
|
|
/// </summary>
|
|
/// <param name="typeName"></param>
|
|
/// <param name="args"></param>
|
|
/// <param name="hasDatePart"></param>
|
|
/// <param name="hasTimeZonePart"></param>
|
|
/// <returns></returns>
|
|
private static ISqlFragment HandleCanonicalFunctionDateTimeTypeCreation(SqlGenerator sqlgen, string typeName, IList<DbExpression> args, bool hasDatePart, bool hasTimeZonePart)
|
|
{
|
|
Debug.Assert(args.Count == (hasDatePart ? 3 : 0) + 3 + (hasTimeZonePart ? 1 : 0), "Invalid number of parameters for a date time creating function");
|
|
|
|
SqlBuilder result = new SqlBuilder();
|
|
int currentArgumentIndex = 0;
|
|
|
|
if (!sqlgen.IsPreKatmai && hasDatePart)
|
|
{
|
|
result.Append("DATEADD(year, ");
|
|
sqlgen.ParenthesizeExpressionIfNeeded(args[currentArgumentIndex++], result);
|
|
result.Append(" - 1, ");
|
|
}
|
|
|
|
result.Append("convert (");
|
|
result.Append(typeName);
|
|
result.Append(",");
|
|
|
|
//Building the string representation
|
|
if (hasDatePart)
|
|
{
|
|
// YEAR: PREKATMAI: CONVERT(VARCHAR, @YEAR)
|
|
// KATMAI : '0001'
|
|
if (!sqlgen.IsPreKatmai)
|
|
{
|
|
result.Append("'0001'");
|
|
}
|
|
else
|
|
{
|
|
AppendConvertToVarchar(sqlgen, result, args[currentArgumentIndex++]);
|
|
}
|
|
|
|
// MONTH
|
|
result.Append(" + '-' + ");
|
|
AppendConvertToVarchar(sqlgen, result, args[currentArgumentIndex++]);
|
|
|
|
// DAY
|
|
result.Append(" + '-' + ");
|
|
AppendConvertToVarchar(sqlgen, result, args[currentArgumentIndex++]);
|
|
result.Append(" + ' ' + ");
|
|
}
|
|
|
|
// HOUR
|
|
AppendConvertToVarchar(sqlgen, result, args[currentArgumentIndex++]);
|
|
|
|
// MINUTE
|
|
result.Append(" + ':' + ");
|
|
AppendConvertToVarchar(sqlgen, result, args[currentArgumentIndex++]);
|
|
|
|
// SECOND
|
|
result.Append(" + ':' + str(");
|
|
result.Append(args[currentArgumentIndex++].Accept(sqlgen));
|
|
|
|
if (sqlgen.IsPreKatmai)
|
|
{
|
|
result.Append(", 6, 3)");
|
|
}
|
|
else
|
|
{
|
|
result.Append(", 10, 7)");
|
|
}
|
|
|
|
// TZOFFSET
|
|
if (hasTimeZonePart)
|
|
{
|
|
result.Append(" + (CASE WHEN ");
|
|
sqlgen.ParenthesizeExpressionIfNeeded(args[currentArgumentIndex], result);
|
|
result.Append(" >= 0 THEN '+' ELSE '-' END) + convert(varchar(255), ABS(");
|
|
sqlgen.ParenthesizeExpressionIfNeeded(args[currentArgumentIndex], result);
|
|
result.Append("/60)) + ':' + convert(varchar(255), ABS(");
|
|
sqlgen.ParenthesizeExpressionIfNeeded(args[currentArgumentIndex], result);
|
|
result.Append("%60))");
|
|
}
|
|
|
|
result.Append(", 121)");
|
|
|
|
if (!sqlgen.IsPreKatmai && hasDatePart)
|
|
{
|
|
result.Append(")");
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Helper method that wrapps the given expession with a conver to varchar(255)
|
|
/// </summary>
|
|
/// <param name="result"></param>
|
|
/// <param name="e"></param>
|
|
private static void AppendConvertToVarchar(SqlGenerator sqlgen, SqlBuilder result, DbExpression e)
|
|
{
|
|
result.Append("convert(varchar(255), ");
|
|
result.Append(e.Accept(sqlgen));
|
|
result.Append(")");
|
|
}
|
|
|
|
/// <summary>
|
|
/// TruncateTime(DateTime X)
|
|
/// PreKatmai: TRUNCATETIME(X) => CONVERT(DATETIME, CONVERT(VARCHAR(255), expression, 102), 102)
|
|
/// Katmai: TRUNCATETIME(X) => CONVERT(DATETIME2, CONVERT(VARCHAR(255), expression, 102), 102)
|
|
///
|
|
/// TruncateTime(DateTimeOffset X)
|
|
/// TRUNCATETIME(X) => CONVERT(datetimeoffset, CONVERT(VARCHAR(255), expression, 102)
|
|
/// + ' 00:00:00 ' + Right(convert(varchar(255), @arg, 121), 6), 102)
|
|
///
|
|
/// </summary>
|
|
/// <param name="sqlgen"></param>
|
|
/// <param name="e"></param>
|
|
/// <returns></returns>
|
|
private static ISqlFragment HandleCanonicalFunctionTruncateTime(SqlGenerator sqlgen, DbFunctionExpression e)
|
|
{
|
|
//The type that we need to return is based on the argument type.
|
|
string typeName = null;
|
|
bool isDateTimeOffset = false;
|
|
|
|
PrimitiveTypeKind typeKind;
|
|
bool isPrimitiveType = TypeHelpers.TryGetPrimitiveTypeKind(e.Arguments[0].ResultType, out typeKind);
|
|
Debug.Assert(isPrimitiveType, "Expecting primitive type as input parameter to TruncateTime");
|
|
|
|
if (typeKind == PrimitiveTypeKind.DateTime)
|
|
{
|
|
typeName = sqlgen.IsPreKatmai ? "datetime" : "datetime2";
|
|
}
|
|
else if (typeKind == PrimitiveTypeKind.DateTimeOffset)
|
|
{
|
|
typeName = "datetimeoffset";
|
|
isDateTimeOffset = true;
|
|
}
|
|
else
|
|
{
|
|
Debug.Assert(true, "Unexpected type to TruncateTime" + typeKind.ToString());
|
|
}
|
|
|
|
SqlBuilder result = new SqlBuilder();
|
|
result.Append("convert (");
|
|
result.Append(typeName);
|
|
result.Append(", convert(varchar(255), ");
|
|
result.Append(e.Arguments[0].Accept(sqlgen));
|
|
result.Append(", 102) ");
|
|
|
|
if (isDateTimeOffset)
|
|
{
|
|
result.Append("+ ' 00:00:00 ' + Right(convert(varchar(255), ");
|
|
result.Append(e.Arguments[0].Accept(sqlgen));
|
|
result.Append(", 121), 6) ");
|
|
}
|
|
|
|
result.Append(", 102)");
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Handler for date addition functions supported only starting from Katmai
|
|
/// </summary>
|
|
/// <param name="sqlgen"></param>
|
|
/// <param name="e"></param>
|
|
/// <returns></returns>
|
|
private static ISqlFragment HandleCanonicalFunctionDateAddKatmaiOrNewer(SqlGenerator sqlgen, DbFunctionExpression e)
|
|
{
|
|
sqlgen.AssertKatmaiOrNewer(e);
|
|
return HandleCanonicalFunctionDateAdd(sqlgen, e);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Handler for all date/time addition canonical functions.
|
|
/// Translation, e.g.
|
|
/// AddYears(datetime, number) => DATEADD(year, number, datetime)
|
|
/// </summary>
|
|
/// <param name="sqlgen"></param>
|
|
/// <param name="e"></param>
|
|
/// <returns></returns>
|
|
private static ISqlFragment HandleCanonicalFunctionDateAdd(SqlGenerator sqlgen, DbFunctionExpression e)
|
|
{
|
|
SqlBuilder result = new SqlBuilder();
|
|
|
|
result.Append("DATEADD (");
|
|
result.Append(_dateAddFunctionNameToDatepartDictionary[e.Function.Name]);
|
|
result.Append(", ");
|
|
result.Append(e.Arguments[1].Accept(sqlgen));
|
|
result.Append(", ");
|
|
result.Append(e.Arguments[0].Accept(sqlgen));
|
|
result.Append(")");
|
|
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Hanndler for date differencing functions supported only starting from Katmai
|
|
/// </summary>
|
|
/// <param name="sqlgen"></param>
|
|
/// <param name="e"></param>
|
|
/// <returns></returns>
|
|
private static ISqlFragment HandleCanonicalFunctionDateDiffKatmaiOrNewer(SqlGenerator sqlgen, DbFunctionExpression e)
|
|
{
|
|
sqlgen.AssertKatmaiOrNewer(e);
|
|
return HandleCanonicalFunctionDateDiff(sqlgen, e);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Handler for all date/time addition canonical functions.
|
|
/// Translation, e.g.
|
|
/// DiffYears(datetime, number) => DATEDIFF(year, number, datetime)
|
|
/// </summary>
|
|
/// <param name="sqlgen"></param>
|
|
/// <param name="e"></param>
|
|
/// <returns></returns>
|
|
private static ISqlFragment HandleCanonicalFunctionDateDiff(SqlGenerator sqlgen, DbFunctionExpression e)
|
|
{
|
|
SqlBuilder result = new SqlBuilder();
|
|
|
|
result.Append("DATEDIFF (");
|
|
result.Append(_dateDiffFunctionNameToDatepartDictionary[e.Function.Name]);
|
|
result.Append(", ");
|
|
result.Append(e.Arguments[0].Accept(sqlgen));
|
|
result.Append(", ");
|
|
result.Append(e.Arguments[1].Accept(sqlgen));
|
|
result.Append(")");
|
|
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Function rename IndexOf -> CHARINDEX
|
|
/// </summary>
|
|
/// <param name="sqlgen"></param>
|
|
/// <param name="e"></param>
|
|
/// <returns></returns>
|
|
private static ISqlFragment HandleCanonicalFunctionIndexOf(SqlGenerator sqlgen, DbFunctionExpression e)
|
|
{
|
|
return HandleFunctionDefaultGivenName(sqlgen, e, "CHARINDEX");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Function rename NewGuid -> NEWID
|
|
/// </summary>
|
|
/// <param name="sqlgen"></param>
|
|
/// <param name="e"></param>
|
|
/// <returns></returns>
|
|
private static ISqlFragment HandleCanonicalFunctionNewGuid(SqlGenerator sqlgen, DbFunctionExpression e)
|
|
{
|
|
return HandleFunctionDefaultGivenName(sqlgen, e, "NEWID");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Function rename Length -> LEN
|
|
/// </summary>
|
|
/// <param name="sqlgen"></param>
|
|
/// <param name="e"></param>
|
|
/// <returns></returns>
|
|
private static ISqlFragment HandleCanonicalFunctionLength(SqlGenerator sqlgen, DbFunctionExpression e)
|
|
{
|
|
// We are aware of SQL Server's trimming of trailing spaces. We disclaim that behavior in general.
|
|
// It's up to the user to decide whether to trim them explicitly or to append a non-blank space char explicitly.
|
|
// Once SQL Server implements a function that computes Length correctly, we'll use it here instead of LEN,
|
|
// and we'll drop the disclaimer.
|
|
return HandleFunctionDefaultGivenName(sqlgen, e, "LEN");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Round(numericExpression) -> Round(numericExpression, 0);
|
|
/// Round(numericExpression, digits) -> Round(numericExpression, digits);
|
|
/// </summary>
|
|
/// <param name="sqlgen"></param>
|
|
/// <param name="e"></param>
|
|
/// <returns></returns>
|
|
private static ISqlFragment HandleCanonicalFunctionRound(SqlGenerator sqlgen, DbFunctionExpression e)
|
|
{
|
|
return HandleCanonicalFunctionRoundOrTruncate(sqlgen, e, true);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Truncate(numericExpression) -> Round(numericExpression, 0, 1); (does not exist as canonical function yet)
|
|
/// Truncate(numericExpression, digits) -> Round(numericExpression, digits, 1);
|
|
/// </summary>
|
|
/// <param name="sqlgen"></param>
|
|
/// <param name="e"></param>
|
|
/// <returns></returns>
|
|
private static ISqlFragment HandleCanonicalFunctionTruncate(SqlGenerator sqlgen, DbFunctionExpression e)
|
|
{
|
|
return HandleCanonicalFunctionRoundOrTruncate(sqlgen, e, false);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Common handler for the canonical functions ROUND and TRUNCATE
|
|
/// </summary>
|
|
/// <param name="e"></param>
|
|
/// <param name="round"></param>
|
|
/// <returns></returns>
|
|
private static ISqlFragment HandleCanonicalFunctionRoundOrTruncate(SqlGenerator sqlgen, DbFunctionExpression e, bool round)
|
|
{
|
|
SqlBuilder result = new SqlBuilder();
|
|
|
|
// Do not add the cast for the Round() overload having two arguments.
|
|
// Round(Single,Int32) maps to Round(Double,Int32)due to implicit casting.
|
|
// We don't need to cast in that case, since the server returned type is same
|
|
// as the expected type. Cast is only required for the overload - Round(Single)
|
|
bool requiresCastToSingle = false;
|
|
if (e.Arguments.Count == 1)
|
|
{
|
|
requiresCastToSingle = CastReturnTypeToSingle(e);
|
|
if (requiresCastToSingle)
|
|
{
|
|
result.Append(" CAST(");
|
|
}
|
|
}
|
|
result.Append("ROUND(");
|
|
|
|
Debug.Assert(e.Arguments.Count <= 2, "Round or truncate should have at most 2 arguments");
|
|
result.Append(e.Arguments[0].Accept(sqlgen));
|
|
result.Append(", ");
|
|
|
|
if (e.Arguments.Count > 1)
|
|
{
|
|
result.Append(e.Arguments[1].Accept(sqlgen));
|
|
}
|
|
else
|
|
{
|
|
result.Append("0");
|
|
}
|
|
|
|
if (!round)
|
|
{
|
|
result.Append(", 1");
|
|
}
|
|
|
|
result.Append(")");
|
|
|
|
if (requiresCastToSingle)
|
|
{
|
|
result.Append(" AS real)");
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Handle the canonical function Abs().
|
|
/// </summary>
|
|
/// <param name="sqlgen"></param>
|
|
/// <param name="e"></param>
|
|
/// <returns></returns>
|
|
private static ISqlFragment HandleCanonicalFunctionAbs(SqlGenerator sqlgen, DbFunctionExpression e)
|
|
{
|
|
// Convert the call to Abs(Byte) to a no-op, since Byte is an unsigned type.
|
|
if (TypeSemantics.IsPrimitiveType(e.Arguments[0].ResultType, PrimitiveTypeKind.Byte))
|
|
{
|
|
SqlBuilder result = new SqlBuilder();
|
|
result.Append(e.Arguments[0].Accept(sqlgen));
|
|
return result;
|
|
}
|
|
else
|
|
{
|
|
return HandleFunctionDefault(sqlgen, e);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// TRIM(string) -> LTRIM(RTRIM(string))
|
|
/// </summary>
|
|
/// <param name="sqlgen"></param>
|
|
/// <param name="e"></param>
|
|
/// <returns></returns>
|
|
private static ISqlFragment HandleCanonicalFunctionTrim(SqlGenerator sqlgen, DbFunctionExpression e)
|
|
{
|
|
SqlBuilder result = new SqlBuilder();
|
|
|
|
result.Append("LTRIM(RTRIM(");
|
|
|
|
Debug.Assert(e.Arguments.Count == 1, "Trim should have one argument");
|
|
result.Append(e.Arguments[0].Accept(sqlgen));
|
|
|
|
result.Append("))");
|
|
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Function rename ToLower -> LOWER
|
|
/// </summary>
|
|
/// <param name="sqlgen"></param>
|
|
/// <param name="e"></param>
|
|
/// <returns></returns>
|
|
private static ISqlFragment HandleCanonicalFunctionToLower(SqlGenerator sqlgen, DbFunctionExpression e)
|
|
{
|
|
return HandleFunctionDefaultGivenName(sqlgen, e, "LOWER");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Function rename ToUpper -> UPPER
|
|
/// </summary>
|
|
/// <param name="sqlgen"></param>
|
|
/// <param name="e"></param>
|
|
/// <returns></returns>
|
|
private static ISqlFragment HandleCanonicalFunctionToUpper(SqlGenerator sqlgen, DbFunctionExpression e)
|
|
{
|
|
return HandleFunctionDefaultGivenName(sqlgen, e, "UPPER");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Function to translate the StartsWith, EndsWith and Contains canonical functions to LIKE expression in T-SQL
|
|
/// and also add the trailing ESCAPE '~' when escaping of the search string for the LIKE expression has occurred
|
|
/// </summary>
|
|
/// <param name="sqlgen"></param>
|
|
/// <param name="targetExpression"></param>
|
|
/// <param name="constSearchParamExpression"></param>
|
|
/// <param name="result"></param>
|
|
/// <param name="insertPercentStart"></param>
|
|
/// <param name="insertPercentEnd"></param>
|
|
private static void TranslateConstantParameterForLike(SqlGenerator sqlgen, DbExpression targetExpression, DbConstantExpression constSearchParamExpression, SqlBuilder result, bool insertPercentStart, bool insertPercentEnd)
|
|
{
|
|
result.Append(targetExpression.Accept(sqlgen));
|
|
result.Append(" LIKE ");
|
|
|
|
// If it's a DbConstantExpression then escape the search parameter if necessary.
|
|
bool escapingOccurred;
|
|
|
|
StringBuilder searchParamBuilder = new StringBuilder();
|
|
if (insertPercentStart == true)
|
|
searchParamBuilder.Append("%");
|
|
searchParamBuilder.Append(SqlProviderManifest.EscapeLikeText(constSearchParamExpression.Value as string, false, out escapingOccurred));
|
|
if (insertPercentEnd == true)
|
|
searchParamBuilder.Append("%");
|
|
|
|
DbConstantExpression escapedSearchParamExpression = new DbConstantExpression(constSearchParamExpression.ResultType, searchParamBuilder.ToString());
|
|
result.Append(escapedSearchParamExpression.Accept(sqlgen));
|
|
|
|
// If escaping did occur (special characters were found), then append the escape character used.
|
|
if (escapingOccurred)
|
|
result.Append(" ESCAPE '" + SqlProviderManifest.LikeEscapeChar + "'");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Handler for Contains. Wraps the normal translation with a case statement
|
|
/// </summary>
|
|
/// <param name="sqlgen"></param>
|
|
/// <param name="e"></param>
|
|
/// <returns></returns>
|
|
private static ISqlFragment HandleCanonicalFunctionContains(SqlGenerator sqlgen, DbFunctionExpression e)
|
|
{
|
|
return WrapPredicate( HandleCanonicalFunctionContains, sqlgen, e);
|
|
}
|
|
|
|
/// <summary>
|
|
/// CONTAINS(arg0, arg1) => arg0 LIKE '%arg1%'
|
|
/// </summary>
|
|
/// <param name="sqlgen"></param>
|
|
/// <param name="args"></param>
|
|
/// <param name="result"></param>
|
|
/// <returns></returns>
|
|
private static SqlBuilder HandleCanonicalFunctionContains(SqlGenerator sqlgen, IList<DbExpression> args, SqlBuilder result)
|
|
{
|
|
Debug.Assert(args.Count == 2, "Contains should have two arguments");
|
|
// Check if args[1] is a DbConstantExpression
|
|
DbConstantExpression constSearchParamExpression = args[1] as DbConstantExpression;
|
|
if ((constSearchParamExpression != null) && (string.IsNullOrEmpty(constSearchParamExpression.Value as string) == false))
|
|
{
|
|
TranslateConstantParameterForLike(sqlgen, args[0], constSearchParamExpression, result, true, true);
|
|
}
|
|
else
|
|
{
|
|
// We use CHARINDEX when the search param is a DbNullExpression because all of SQL Server 2008, 2005 and 2000
|
|
// consistently return NULL as the result.
|
|
// However, if instead we use the optimized LIKE translation when the search param is a DbNullExpression,
|
|
// only SQL Server 2005 yields a True instead of a DbNull as compared to SQL Server 2008 and 2000. This is
|
|
// tracked in SQLBUDT #32315 in LIKE in SQL Server 2005.
|
|
result.Append("CHARINDEX( ");
|
|
result.Append(args[1].Accept(sqlgen));
|
|
result.Append(", ");
|
|
result.Append(args[0].Accept(sqlgen));
|
|
result.Append(") > 0");
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Handler for StartsWith. Wraps the normal translation with a case statement
|
|
/// </summary>
|
|
/// <param name="sqlgen"></param>
|
|
/// <param name="e"></param>
|
|
/// <returns></returns>
|
|
private static ISqlFragment HandleCanonicalFunctionStartsWith(SqlGenerator sqlgen, DbFunctionExpression e)
|
|
{
|
|
return WrapPredicate(HandleCanonicalFunctionStartsWith, sqlgen, e);
|
|
}
|
|
|
|
/// <summary>
|
|
/// STARTSWITH(arg0, arg1) => arg0 LIKE 'arg1%'
|
|
/// </summary>
|
|
/// <param name="sqlgen"></param>
|
|
/// <param name="args"></param>
|
|
/// <param name="result"></param>
|
|
/// <returns></returns>
|
|
private static SqlBuilder HandleCanonicalFunctionStartsWith(SqlGenerator sqlgen, IList<DbExpression> args, SqlBuilder result)
|
|
{
|
|
Debug.Assert(args.Count == 2, "StartsWith should have two arguments");
|
|
// Check if args[1] is a DbConstantExpression
|
|
DbConstantExpression constSearchParamExpression = args[1] as DbConstantExpression;
|
|
if ((constSearchParamExpression != null) && (string.IsNullOrEmpty(constSearchParamExpression.Value as string) == false))
|
|
{
|
|
TranslateConstantParameterForLike(sqlgen, args[0], constSearchParamExpression, result, false, true);
|
|
}
|
|
else
|
|
{
|
|
// We use CHARINDEX when the search param is a DbNullExpression because all of SQL Server 2008, 2005 and 2000
|
|
// consistently return NULL as the result.
|
|
// However, if instead we use the optimized LIKE translation when the search param is a DbNullExpression,
|
|
// only SQL Server 2005 yields a True instead of a DbNull as compared to SQL Server 2008 and 2000. This is
|
|
// bug 32315 in LIKE in SQL Server 2005.
|
|
result.Append("CHARINDEX( ");
|
|
result.Append(args[1].Accept(sqlgen));
|
|
result.Append(", ");
|
|
result.Append(args[0].Accept(sqlgen));
|
|
result.Append(") = 1");
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Handler for EndsWith. Wraps the normal translation with a case statement
|
|
/// </summary>
|
|
/// <param name="sqlgen"></param>
|
|
/// <param name="e"></param>
|
|
/// <returns></returns>
|
|
private static ISqlFragment HandleCanonicalFunctionEndsWith(SqlGenerator sqlgen, DbFunctionExpression e)
|
|
{
|
|
return WrapPredicate(HandleCanonicalFunctionEndsWith, sqlgen, e);
|
|
}
|
|
|
|
/// <summary>
|
|
/// ENDSWITH(arg0, arg1) => arg0 LIKE '%arg1'
|
|
/// </summary>
|
|
/// <param name="sqlgen"></param>
|
|
/// <param name="args"></param>
|
|
/// <param name="result"></param>
|
|
/// <returns></returns>
|
|
private static SqlBuilder HandleCanonicalFunctionEndsWith(SqlGenerator sqlgen, IList<DbExpression> args, SqlBuilder result)
|
|
{
|
|
Debug.Assert(args.Count == 2, "EndsWith should have two arguments");
|
|
|
|
// Check if args[1] is a DbConstantExpression and if args [0] is a DbPropertyExpression
|
|
DbConstantExpression constSearchParamExpression = args[1] as DbConstantExpression;
|
|
DbPropertyExpression targetParamExpression = args[0] as DbPropertyExpression;
|
|
if ((constSearchParamExpression != null) && (targetParamExpression != null) && (string.IsNullOrEmpty(constSearchParamExpression.Value as string) == false))
|
|
{
|
|
// The LIKE optimization for EndsWith can only be used when the target is a column in table and
|
|
// the search string is a constant. This is because SQL Server ignores a trailing space in a query like:
|
|
// EndsWith('abcd ', 'cd'), which translates to:
|
|
// SELECT
|
|
// CASE WHEN ('abcd ' LIKE '%cd') THEN cast(1 as bit) WHEN ( NOT ('abcd ' LIKE '%cd')) THEN cast(0 as bit) END AS [C1]
|
|
// FROM ( SELECT 1 AS X ) AS [SingleRowTable1]
|
|
// and "incorrectly" returns 1 (true), but the CLR would expect a 0 (false) back.
|
|
|
|
TranslateConstantParameterForLike(sqlgen, args[0], constSearchParamExpression, result, true, false);
|
|
}
|
|
else
|
|
{
|
|
result.Append("CHARINDEX( REVERSE(");
|
|
result.Append(args[1].Accept(sqlgen));
|
|
result.Append("), REVERSE(");
|
|
result.Append(args[0].Accept(sqlgen));
|
|
result.Append(")) = 1");
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Turns a predicate into a statement returning a bit
|
|
/// PREDICATE => CASE WHEN (PREDICATE) THEN CAST(1 AS BIT) WHEN (NOT (PREDICATE)) CAST (O AS BIT) END
|
|
/// The predicate is produced by the given predicateTranslator.
|
|
/// </summary>
|
|
/// <param name="predicateTranslator"></param>
|
|
/// <param name="e"></param>
|
|
/// <returns></returns>
|
|
private static ISqlFragment WrapPredicate(Func<SqlGenerator, IList<DbExpression>, SqlBuilder, SqlBuilder> predicateTranslator, SqlGenerator sqlgen, DbFunctionExpression e)
|
|
{
|
|
SqlBuilder result = new SqlBuilder();
|
|
result.Append("CASE WHEN (");
|
|
predicateTranslator(sqlgen, e.Arguments, result);
|
|
result.Append(") THEN cast(1 as bit) WHEN ( NOT (");
|
|
predicateTranslator(sqlgen, e.Arguments, result);
|
|
result.Append(")) THEN cast(0 as bit) END");
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Writes the function name to the given SqlBuilder.
|
|
/// </summary>
|
|
/// <param name="function"></param>
|
|
/// <param name="result"></param>
|
|
internal static void WriteFunctionName(SqlBuilder result, EdmFunction function)
|
|
{
|
|
string storeFunctionName;
|
|
|
|
if (null != function.StoreFunctionNameAttribute)
|
|
{
|
|
storeFunctionName = function.StoreFunctionNameAttribute;
|
|
}
|
|
else
|
|
{
|
|
storeFunctionName = function.Name;
|
|
}
|
|
|
|
// If the function is a builtin (i.e. the BuiltIn attribute has been
|
|
// specified, both store and canonical functions have this attribute),
|
|
// then the function name should not be quoted;
|
|
// additionally, no namespace should be used.
|
|
if (TypeHelpers.IsCanonicalFunction(function))
|
|
{
|
|
result.Append(storeFunctionName.ToUpperInvariant());
|
|
}
|
|
else if (IsStoreFunction(function))
|
|
{
|
|
result.Append(storeFunctionName);
|
|
}
|
|
else
|
|
{
|
|
// Should we actually support this?
|
|
if (String.IsNullOrEmpty(function.Schema))
|
|
{
|
|
result.Append(SqlGenerator.QuoteIdentifier(function.NamespaceName));
|
|
}
|
|
else
|
|
{
|
|
result.Append(SqlGenerator.QuoteIdentifier(function.Schema));
|
|
}
|
|
result.Append(".");
|
|
result.Append(SqlGenerator.QuoteIdentifier(storeFunctionName));
|
|
}
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Is this a Store function (ie) does it have the builtinAttribute specified and it is not a canonical function?
|
|
/// </summary>
|
|
/// <param name="function"></param>
|
|
/// <returns></returns>
|
|
internal static bool IsStoreFunction(EdmFunction function)
|
|
{
|
|
return function.BuiltInAttribute && !TypeHelpers.IsCanonicalFunction(function);
|
|
}
|
|
|
|
/// <summary>
|
|
/// determines if the function requires the return type be enforeced by use of a cast expression
|
|
/// </summary>
|
|
/// <param name="e"></param>
|
|
/// <returns></returns>
|
|
private static bool CastReturnTypeToInt64(DbFunctionExpression e)
|
|
{
|
|
return CastReturnTypeToGivenType(e, _functionRequiresReturnTypeCastToInt64, PrimitiveTypeKind.Int64);
|
|
}
|
|
|
|
/// <summary>
|
|
/// determines if the function requires the return type be enforeced by use of a cast expression
|
|
/// </summary>
|
|
/// <param name="e"></param>
|
|
/// <returns></returns>
|
|
private static bool CastReturnTypeToInt32(SqlGenerator sqlgen, DbFunctionExpression e)
|
|
{
|
|
if (!_functionRequiresReturnTypeCastToInt32.Contains(e.Function.FullName))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
for (int i = 0; i < e.Arguments.Count; i++)
|
|
{
|
|
TypeUsage storeType = sqlgen.StoreItemCollection.StoreProviderManifest.GetStoreType(e.Arguments[i].ResultType);
|
|
if (_maxTypeNames.Contains(storeType.EdmType.Name))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// determines if the function requires the return type be enforeced by use of a cast expression
|
|
/// </summary>
|
|
/// <param name="e"></param>
|
|
/// <returns></returns>
|
|
private static bool CastReturnTypeToInt16(DbFunctionExpression e)
|
|
{
|
|
return CastReturnTypeToGivenType(e, _functionRequiresReturnTypeCastToInt16, PrimitiveTypeKind.Int16);
|
|
}
|
|
|
|
/// <summary>
|
|
/// determines if the function requires the return type be enforeced by use of a cast expression
|
|
/// </summary>
|
|
/// <param name="e"></param>
|
|
/// <returns></returns>
|
|
private static bool CastReturnTypeToSingle(DbFunctionExpression e)
|
|
{
|
|
//Do not add the cast for the Round() overload having 2 arguments.
|
|
//Round(Single,Int32) maps to Round(Double,Int32)due to implicit casting.
|
|
//We don't need to cast in that case, since we expect a Double as return type there anyways.
|
|
return CastReturnTypeToGivenType(e, _functionRequiresReturnTypeCastToSingle, PrimitiveTypeKind.Single);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Determines if the function requires the return type be enforced by use of a cast expression
|
|
/// </summary>
|
|
/// <param name="e"></param>
|
|
/// <param name="functionsRequiringReturnTypeCast"></param>
|
|
/// <param name="type"></param>
|
|
/// <returns></returns>
|
|
private static bool CastReturnTypeToGivenType(DbFunctionExpression e, Set<string> functionsRequiringReturnTypeCast, PrimitiveTypeKind type)
|
|
{
|
|
if (!functionsRequiringReturnTypeCast.Contains(e.Function.FullName))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
for (int i = 0; i < e.Arguments.Count; i++)
|
|
{
|
|
if (TypeSemantics.IsPrimitiveType(e.Arguments[i].ResultType, type))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|