1 //---------------------------------------------------------------------
2 // <copyright file="SqlFunctionCallHandler.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
7 // @backupOwner Microsoft
8 //---------------------------------------------------------------------
10 namespace System.Data.SqlClient.SqlGen
13 using System.Collections.Generic;
14 using System.Data.Common;
15 using System.Data.Common.CommandTrees;
16 using System.Data.Common.CommandTrees.ExpressionBuilder;
17 using System.Data.Common.Utils;
18 using System.Data.Metadata.Edm;
19 using System.Data.Spatial;
20 using System.Data.SqlClient;
21 using System.Diagnostics;
26 /// Enacapsulates the logic required to translate function calls represented as instances of DbFunctionExpression into SQL.
27 /// There are several special cases that modify how the translation should proceed. These include:
28 /// - 'Special' canonical functions, for which the function name or arguments differ between the EDM canonical function and the SQL function
29 /// - 'Special' server functions, which are similar to the 'special' canonical functions but sourced by the SQL Server provider manifest
30 /// - Niladic functions, which require the parentheses that would usually follow the function name to be omitted
31 /// - Spatial canonical functions, which must translate to a static method call, instance method call, or instance property access against
32 /// one of the built-in spatial CLR UDTs (geography/geometry).
34 internal static class SqlFunctionCallHandler
36 #region Static fields, include dictionaries used to dispatch function handling
38 static private readonly Dictionary<string, FunctionHandler> _storeFunctionHandlers = InitializeStoreFunctionHandlers();
39 static private readonly Dictionary<string, FunctionHandler> _canonicalFunctionHandlers = InitializeCanonicalFunctionHandlers();
40 static private readonly Dictionary<string, string> _functionNameToOperatorDictionary = InitializeFunctionNameToOperatorDictionary();
41 static private readonly Dictionary<string, string> _dateAddFunctionNameToDatepartDictionary = InitializeDateAddFunctionNameToDatepartDictionary();
42 static private readonly Dictionary<string, string> _dateDiffFunctionNameToDatepartDictionary = InitializeDateDiffFunctionNameToDatepartDictionary();
43 static private readonly Dictionary<string, FunctionHandler> _geographyFunctionNameToStaticMethodHandlerDictionary = InitializeGeographyStaticMethodFunctionsDictionary();
44 static private readonly Dictionary<string, string> _geographyFunctionNameToInstancePropertyNameDictionary = InitializeGeographyInstancePropertyFunctionsDictionary();
45 static private readonly Dictionary<string, string> _geographyRenamedInstanceMethodFunctionDictionary = InitializeRenamedGeographyInstanceMethodFunctions();
46 static private readonly Dictionary<string, FunctionHandler> _geometryFunctionNameToStaticMethodHandlerDictionary = InitializeGeometryStaticMethodFunctionsDictionary();
47 static private readonly Dictionary<string, string> _geometryFunctionNameToInstancePropertyNameDictionary = InitializeGeometryInstancePropertyFunctionsDictionary();
48 static private readonly Dictionary<string, string> _geometryRenamedInstanceMethodFunctionDictionary = InitializeRenamedGeometryInstanceMethodFunctions();
49 static private readonly Set<string> _datepartKeywords = new Set<string>(new string[] { "year", "yy", "yyyy",
52 "dayofyear", "dy", "y",
63 "iso_week", "isoww", "isowk"},
64 StringComparer.OrdinalIgnoreCase).MakeReadOnly();
65 static private readonly Set<string> _functionRequiresReturnTypeCastToInt64 = new Set<string>(new string[] { "SqlServer.CHARINDEX" },
66 StringComparer.Ordinal).MakeReadOnly();
67 static private readonly Set<string> _functionRequiresReturnTypeCastToInt32 = new Set<string>(new string[] { "SqlServer.LEN" ,
68 "SqlServer.PATINDEX" ,
69 "SqlServer.DATALENGTH" ,
70 "SqlServer.CHARINDEX" ,
73 StringComparer.Ordinal).MakeReadOnly();
74 static private readonly Set<string> _functionRequiresReturnTypeCastToInt16 = new Set<string>(new string[] { "Edm.Abs" },
75 StringComparer.Ordinal).MakeReadOnly();
76 static private readonly Set<string> _functionRequiresReturnTypeCastToSingle = new Set<string>(new string[] { "Edm.Abs" ,
80 StringComparer.Ordinal).MakeReadOnly();
81 static private readonly Set<string> _maxTypeNames = new Set<string>(new string[] { "varchar(max)" ,
88 StringComparer.Ordinal).MakeReadOnly();
92 #region Static dictionary initialization
94 private delegate ISqlFragment FunctionHandler(SqlGenerator sqlgen, DbFunctionExpression functionExpr);
97 /// All special store functions and their handlers
99 /// <returns></returns>
100 private static Dictionary<string, FunctionHandler> InitializeStoreFunctionHandlers()
102 Dictionary<string, FunctionHandler> functionHandlers = new Dictionary<string, FunctionHandler>(15, StringComparer.Ordinal);
103 functionHandlers.Add("CONCAT", HandleConcatFunction);
104 functionHandlers.Add("DATEADD", HandleDatepartDateFunction);
105 functionHandlers.Add("DATEDIFF", HandleDatepartDateFunction);
106 functionHandlers.Add("DATENAME", HandleDatepartDateFunction);
107 functionHandlers.Add("DATEPART", HandleDatepartDateFunction);
109 // Spatial functions are mapped to static or instance members of geography/geometry
110 // Geography Static functions
111 functionHandlers.Add("POINTGEOGRAPHY", (sqlgen, functionExpression) => HandleFunctionDefaultGivenName(sqlgen, functionExpression, "geography::Point"));
113 // Geometry Static functions
114 functionHandlers.Add("POINTGEOMETRY", (sqlgen, functionExpression) => HandleFunctionDefaultGivenName(sqlgen, functionExpression, "geometry::Point"));
116 // Spatial Instance functions (shared)
117 functionHandlers.Add("ASTEXTZM", (sqlgen, functionExpression) => WriteInstanceFunctionCall(sqlgen, "AsTextZM", functionExpression, isPropertyAccess: false));
118 functionHandlers.Add("BUFFERWITHTOLERANCE", (sqlgen, functionExpression) => WriteInstanceFunctionCall(sqlgen, "BufferWithTolerance", functionExpression, isPropertyAccess: false));
119 functionHandlers.Add("ENVELOPEANGLE", (sqlgen, functionExpression) => WriteInstanceFunctionCall(sqlgen, "EnvelopeAngle", functionExpression, isPropertyAccess: false));
120 functionHandlers.Add("ENVELOPECENTER", (sqlgen, functionExpression) => WriteInstanceFunctionCall(sqlgen, "EnvelopeCenter", functionExpression, isPropertyAccess: false));
121 functionHandlers.Add("INSTANCEOF", (sqlgen, functionExpression) => WriteInstanceFunctionCall(sqlgen, "InstanceOf", functionExpression, isPropertyAccess: false));
122 functionHandlers.Add("FILTER", (sqlgen, functionExpression) => WriteInstanceFunctionCall(sqlgen, "Filter", functionExpression, isPropertyAccess: false));
123 functionHandlers.Add("MAKEVALID", (sqlgen, functionExpression) => WriteInstanceFunctionCall(sqlgen, "MakeValid", functionExpression, isPropertyAccess: false));
124 functionHandlers.Add("REDUCE", (sqlgen, functionExpression) => WriteInstanceFunctionCall(sqlgen, "Reduce", functionExpression, isPropertyAccess: false));
125 functionHandlers.Add("NUMRINGS", (sqlgen, functionExpression) => WriteInstanceFunctionCall(sqlgen, "NumRings", functionExpression, isPropertyAccess: false));
126 functionHandlers.Add("RINGN", (sqlgen, functionExpression) => WriteInstanceFunctionCall(sqlgen, "RingN", functionExpression, isPropertyAccess: false));
128 return functionHandlers;
132 /// All special non-aggregate canonical functions and their handlers
134 /// <returns></returns>
135 private static Dictionary<string, FunctionHandler> InitializeCanonicalFunctionHandlers()
137 Dictionary<string, FunctionHandler> functionHandlers = new Dictionary<string, FunctionHandler>(16, StringComparer.Ordinal);
138 functionHandlers.Add("IndexOf", HandleCanonicalFunctionIndexOf);
139 functionHandlers.Add("Length", HandleCanonicalFunctionLength);
140 functionHandlers.Add("NewGuid", HandleCanonicalFunctionNewGuid);
141 functionHandlers.Add("Round", HandleCanonicalFunctionRound);
142 functionHandlers.Add("Truncate", HandleCanonicalFunctionTruncate);
143 functionHandlers.Add("Abs", HandleCanonicalFunctionAbs);
144 functionHandlers.Add("ToLower", HandleCanonicalFunctionToLower);
145 functionHandlers.Add("ToUpper", HandleCanonicalFunctionToUpper);
146 functionHandlers.Add("Trim", HandleCanonicalFunctionTrim);
147 functionHandlers.Add("Contains", HandleCanonicalFunctionContains);
148 functionHandlers.Add("StartsWith", HandleCanonicalFunctionStartsWith);
149 functionHandlers.Add("EndsWith", HandleCanonicalFunctionEndsWith);
152 functionHandlers.Add("Year", HandleCanonicalFunctionDatepart);
153 functionHandlers.Add("Month", HandleCanonicalFunctionDatepart);
154 functionHandlers.Add("Day", HandleCanonicalFunctionDatepart);
155 functionHandlers.Add("Hour", HandleCanonicalFunctionDatepart);
156 functionHandlers.Add("Minute", HandleCanonicalFunctionDatepart);
157 functionHandlers.Add("Second", HandleCanonicalFunctionDatepart);
158 functionHandlers.Add("Millisecond", HandleCanonicalFunctionDatepart);
159 functionHandlers.Add("DayOfYear", HandleCanonicalFunctionDatepart);
160 functionHandlers.Add("CurrentDateTime", HandleCanonicalFunctionCurrentDateTime);
161 functionHandlers.Add("CurrentUtcDateTime", HandleCanonicalFunctionCurrentUtcDateTime);
162 functionHandlers.Add("CurrentDateTimeOffset", HandleCanonicalFunctionCurrentDateTimeOffset);
163 functionHandlers.Add("GetTotalOffsetMinutes", HandleCanonicalFunctionGetTotalOffsetMinutes);
164 functionHandlers.Add("TruncateTime", HandleCanonicalFunctionTruncateTime);
165 functionHandlers.Add("CreateDateTime", HandleCanonicalFunctionCreateDateTime);
166 functionHandlers.Add("CreateDateTimeOffset", HandleCanonicalFunctionCreateDateTimeOffset);
167 functionHandlers.Add("CreateTime", HandleCanonicalFunctionCreateTime);
168 functionHandlers.Add("AddYears", HandleCanonicalFunctionDateAdd);
169 functionHandlers.Add("AddMonths", HandleCanonicalFunctionDateAdd);
170 functionHandlers.Add("AddDays", HandleCanonicalFunctionDateAdd);
171 functionHandlers.Add("AddHours", HandleCanonicalFunctionDateAdd);
172 functionHandlers.Add("AddMinutes", HandleCanonicalFunctionDateAdd);
173 functionHandlers.Add("AddSeconds", HandleCanonicalFunctionDateAdd);
174 functionHandlers.Add("AddMilliseconds", HandleCanonicalFunctionDateAdd);
175 functionHandlers.Add("AddMicroseconds", HandleCanonicalFunctionDateAddKatmaiOrNewer);
176 functionHandlers.Add("AddNanoseconds", HandleCanonicalFunctionDateAddKatmaiOrNewer);
177 functionHandlers.Add("DiffYears", HandleCanonicalFunctionDateDiff);
178 functionHandlers.Add("DiffMonths", HandleCanonicalFunctionDateDiff);
179 functionHandlers.Add("DiffDays", HandleCanonicalFunctionDateDiff);
180 functionHandlers.Add("DiffHours", HandleCanonicalFunctionDateDiff);
181 functionHandlers.Add("DiffMinutes", HandleCanonicalFunctionDateDiff);
182 functionHandlers.Add("DiffSeconds", HandleCanonicalFunctionDateDiff);
183 functionHandlers.Add("DiffMilliseconds", HandleCanonicalFunctionDateDiff);
184 functionHandlers.Add("DiffMicroseconds", HandleCanonicalFunctionDateDiffKatmaiOrNewer);
185 functionHandlers.Add("DiffNanoseconds", HandleCanonicalFunctionDateDiffKatmaiOrNewer);
187 //Functions that translate to operators
188 functionHandlers.Add("Concat", HandleConcatFunction);
189 functionHandlers.Add("BitwiseAnd", HandleCanonicalFunctionBitwise);
190 functionHandlers.Add("BitwiseNot", HandleCanonicalFunctionBitwise);
191 functionHandlers.Add("BitwiseOr", HandleCanonicalFunctionBitwise);
192 functionHandlers.Add("BitwiseXor", HandleCanonicalFunctionBitwise);
194 return functionHandlers;
198 /// Initalizes the mapping from functions to TSql operators
199 /// for all functions that translate to TSql operators
201 /// <returns></returns>
202 private static Dictionary<string, string> InitializeFunctionNameToOperatorDictionary()
204 Dictionary<string, string> functionNameToOperatorDictionary = new Dictionary<string, string>(5, StringComparer.Ordinal);
205 functionNameToOperatorDictionary.Add("Concat", "+"); //canonical
206 functionNameToOperatorDictionary.Add("CONCAT", "+"); //store
207 functionNameToOperatorDictionary.Add("BitwiseAnd", "&");
208 functionNameToOperatorDictionary.Add("BitwiseNot", "~");
209 functionNameToOperatorDictionary.Add("BitwiseOr", "|");
210 functionNameToOperatorDictionary.Add("BitwiseXor", "^");
211 return functionNameToOperatorDictionary;
215 /// Initalizes the mapping from names of canonical function for date/time addition
216 /// to corresponding dateparts
218 /// <returns></returns>
219 private static Dictionary<string, string> InitializeDateAddFunctionNameToDatepartDictionary()
221 Dictionary<string, string> dateAddFunctionNameToDatepartDictionary = new Dictionary<string, string>(5, StringComparer.Ordinal);
222 dateAddFunctionNameToDatepartDictionary.Add("AddYears", "year");
223 dateAddFunctionNameToDatepartDictionary.Add("AddMonths", "month");
224 dateAddFunctionNameToDatepartDictionary.Add("AddDays", "day");
225 dateAddFunctionNameToDatepartDictionary.Add("AddHours", "hour");
226 dateAddFunctionNameToDatepartDictionary.Add("AddMinutes", "minute");
227 dateAddFunctionNameToDatepartDictionary.Add("AddSeconds", "second");
228 dateAddFunctionNameToDatepartDictionary.Add("AddMilliseconds", "millisecond");
229 dateAddFunctionNameToDatepartDictionary.Add("AddMicroseconds", "microsecond");
230 dateAddFunctionNameToDatepartDictionary.Add("AddNanoseconds", "nanosecond");
231 return dateAddFunctionNameToDatepartDictionary;
235 /// Initalizes the mapping from names of canonical function for date/time difference
236 /// to corresponding dateparts
238 /// <returns></returns>
239 private static Dictionary<string, string> InitializeDateDiffFunctionNameToDatepartDictionary()
241 Dictionary<string, string> dateDiffFunctionNameToDatepartDictionary = new Dictionary<string, string>(5, StringComparer.Ordinal);
242 dateDiffFunctionNameToDatepartDictionary.Add("DiffYears", "year");
243 dateDiffFunctionNameToDatepartDictionary.Add("DiffMonths", "month");
244 dateDiffFunctionNameToDatepartDictionary.Add("DiffDays", "day");
245 dateDiffFunctionNameToDatepartDictionary.Add("DiffHours", "hour");
246 dateDiffFunctionNameToDatepartDictionary.Add("DiffMinutes", "minute");
247 dateDiffFunctionNameToDatepartDictionary.Add("DiffSeconds", "second");
248 dateDiffFunctionNameToDatepartDictionary.Add("DiffMilliseconds", "millisecond");
249 dateDiffFunctionNameToDatepartDictionary.Add("DiffMicroseconds", "microsecond");
250 dateDiffFunctionNameToDatepartDictionary.Add("DiffNanoseconds", "nanosecond");
251 return dateDiffFunctionNameToDatepartDictionary;
255 /// Initalizes the mapping from names of canonical function that represent static geography methods to their corresponding
256 /// static method name, qualified with the 'geography::' prefix.
258 /// <returns></returns>
259 private static Dictionary<string, FunctionHandler> InitializeGeographyStaticMethodFunctionsDictionary()
261 Dictionary<string, FunctionHandler> staticGeographyFunctions = new Dictionary<string, FunctionHandler>();
263 // Well Known Text constructors
264 staticGeographyFunctions.Add("GeographyFromText", HandleSpatialFromTextFunction);
265 staticGeographyFunctions.Add("GeographyPointFromText", (sqlgen, functionExpression) => HandleFunctionDefaultGivenName(sqlgen, functionExpression, "geography::STPointFromText"));
266 staticGeographyFunctions.Add("GeographyLineFromText", (sqlgen, functionExpression) => HandleFunctionDefaultGivenName(sqlgen, functionExpression, "geography::STLineFromText"));
267 staticGeographyFunctions.Add("GeographyPolygonFromText", (sqlgen, functionExpression) => HandleFunctionDefaultGivenName(sqlgen, functionExpression, "geography::STPolyFromText"));
268 staticGeographyFunctions.Add("GeographyMultiPointFromText", (sqlgen, functionExpression) => HandleFunctionDefaultGivenName(sqlgen, functionExpression, "geography::STMPointFromText"));
269 staticGeographyFunctions.Add("GeographyMultiLineFromText", (sqlgen, functionExpression) => HandleFunctionDefaultGivenName(sqlgen, functionExpression, "geography::STMLineFromText"));
270 staticGeographyFunctions.Add("GeographyMultiPolygonFromText", (sqlgen, functionExpression) => HandleFunctionDefaultGivenName(sqlgen, functionExpression, "geography::STMPolyFromText"));
271 staticGeographyFunctions.Add("GeographyCollectionFromText", (sqlgen, functionExpression) => HandleFunctionDefaultGivenName(sqlgen, functionExpression, "geography::STGeomCollFromText"));
273 // Well Known Binary constructors
274 staticGeographyFunctions.Add("GeographyFromBinary", HandleSpatialFromBinaryFunction);
275 staticGeographyFunctions.Add("GeographyPointFromBinary", (sqlgen, functionExpression) => HandleFunctionDefaultGivenName(sqlgen, functionExpression, "geography::STPointFromWKB"));
276 staticGeographyFunctions.Add("GeographyLineFromBinary", (sqlgen, functionExpression) => HandleFunctionDefaultGivenName(sqlgen, functionExpression, "geography::STLineFromWKB"));
277 staticGeographyFunctions.Add("GeographyPolygonFromBinary", (sqlgen, functionExpression) => HandleFunctionDefaultGivenName(sqlgen, functionExpression, "geography::STPolyFromWKB"));
278 staticGeographyFunctions.Add("GeographyMultiPointFromBinary", (sqlgen, functionExpression) => HandleFunctionDefaultGivenName(sqlgen, functionExpression, "geography::STMPointFromWKB"));
279 staticGeographyFunctions.Add("GeographyMultiLineFromBinary", (sqlgen, functionExpression) => HandleFunctionDefaultGivenName(sqlgen, functionExpression, "geography::STMLineFromWKB"));
280 staticGeographyFunctions.Add("GeographyMultiPolygonFromBinary", (sqlgen, functionExpression) => HandleFunctionDefaultGivenName(sqlgen, functionExpression, "geography::STMPolyFromWKB"));
281 staticGeographyFunctions.Add("GeographyCollectionFromBinary", (sqlgen, functionExpression) => HandleFunctionDefaultGivenName(sqlgen, functionExpression, "geography::STGeomCollFromWKB"));
283 // GML constructor (non-OGC)
284 staticGeographyFunctions.Add("GeographyFromGml", HandleSpatialFromGmlFunction);
286 return staticGeographyFunctions;
290 /// Initalizes the mapping from names of canonical function that represent geography instance properties to their corresponding
291 /// store property name.
293 /// <returns></returns>
294 private static Dictionary<string, string> InitializeGeographyInstancePropertyFunctionsDictionary()
296 Dictionary<string, string> instancePropGeographyFunctions = new Dictionary<string, string>();
298 instancePropGeographyFunctions.Add("CoordinateSystemId", "STSrid");
299 instancePropGeographyFunctions.Add("Latitude", "Lat");
300 instancePropGeographyFunctions.Add("Longitude", "Long");
301 instancePropGeographyFunctions.Add("Measure", "M");
302 instancePropGeographyFunctions.Add("Elevation", "Z");
304 return instancePropGeographyFunctions;
308 /// Initalizes the mapping of canonical function name to instance method name for geography instance functions that differ in name from the sql server equivalent.
310 /// <returns></returns>
311 private static Dictionary<string, string> InitializeRenamedGeographyInstanceMethodFunctions()
313 Dictionary<string, string> renamedInstanceMethodFunctions = new Dictionary<string, string>();
315 renamedInstanceMethodFunctions.Add("AsText", "STAsText");
316 renamedInstanceMethodFunctions.Add("AsBinary", "STAsBinary");
317 renamedInstanceMethodFunctions.Add("SpatialTypeName", "STGeometryType");
318 renamedInstanceMethodFunctions.Add("SpatialDimension", "STDimension");
319 renamedInstanceMethodFunctions.Add("IsEmptySpatial", "STIsEmpty");
320 renamedInstanceMethodFunctions.Add("SpatialEquals", "STEquals");
321 renamedInstanceMethodFunctions.Add("SpatialDisjoint", "STDisjoint");
322 renamedInstanceMethodFunctions.Add("SpatialIntersects", "STIntersects");
323 renamedInstanceMethodFunctions.Add("SpatialBuffer", "STBuffer");
324 renamedInstanceMethodFunctions.Add("Distance", "STDistance");
325 renamedInstanceMethodFunctions.Add("SpatialUnion", "STUnion");
326 renamedInstanceMethodFunctions.Add("SpatialIntersection", "STIntersection");
327 renamedInstanceMethodFunctions.Add("SpatialDifference", "STDifference");
328 renamedInstanceMethodFunctions.Add("SpatialSymmetricDifference", "STSymDifference");
329 renamedInstanceMethodFunctions.Add("SpatialElementCount", "STNumGeometries");
330 renamedInstanceMethodFunctions.Add("SpatialElementAt", "STGeometryN");
331 renamedInstanceMethodFunctions.Add("SpatialLength", "STLength");
332 renamedInstanceMethodFunctions.Add("StartPoint", "STStartPoint");
333 renamedInstanceMethodFunctions.Add("EndPoint", "STEndPoint");
334 renamedInstanceMethodFunctions.Add("IsClosedSpatial", "STIsClosed");
335 renamedInstanceMethodFunctions.Add("PointCount", "STNumPoints");
336 renamedInstanceMethodFunctions.Add("PointAt", "STPointN");
337 renamedInstanceMethodFunctions.Add("Area", "STArea");
339 return renamedInstanceMethodFunctions;
343 /// Initalizes the mapping from names of canonical function that represent static geometry methods to their corresponding
344 /// static method name, qualified with the 'geometry::' prefix.
346 /// <returns></returns>
347 private static Dictionary<string, FunctionHandler> InitializeGeometryStaticMethodFunctionsDictionary()
349 Dictionary<string, FunctionHandler> staticGeometryFunctions = new Dictionary<string, FunctionHandler>();
351 // Well Known Text constructors
352 staticGeometryFunctions.Add("GeometryFromText", HandleSpatialFromTextFunction);
353 staticGeometryFunctions.Add("GeometryPointFromText", (sqlgen, functionExpression) => HandleFunctionDefaultGivenName(sqlgen, functionExpression, "geometry::STPointFromText"));
354 staticGeometryFunctions.Add("GeometryLineFromText", (sqlgen, functionExpression) => HandleFunctionDefaultGivenName(sqlgen, functionExpression, "geometry::STLineFromText"));
355 staticGeometryFunctions.Add("GeometryPolygonFromText", (sqlgen, functionExpression) => HandleFunctionDefaultGivenName(sqlgen, functionExpression, "geometry::STPolyFromText"));
356 staticGeometryFunctions.Add("GeometryMultiPointFromText", (sqlgen, functionExpression) => HandleFunctionDefaultGivenName(sqlgen, functionExpression, "geometry::STMPointFromText"));
357 staticGeometryFunctions.Add("GeometryMultiLineFromText", (sqlgen, functionExpression) => HandleFunctionDefaultGivenName(sqlgen, functionExpression, "geometry::STMLineFromText"));
358 staticGeometryFunctions.Add("GeometryMultiPolygonFromText", (sqlgen, functionExpression) => HandleFunctionDefaultGivenName(sqlgen, functionExpression, "geometry::STMPolyFromText"));
359 staticGeometryFunctions.Add("GeometryCollectionFromText", (sqlgen, functionExpression) => HandleFunctionDefaultGivenName(sqlgen, functionExpression, "geometry::STGeomCollFromText"));
361 // Well Known Binary constructors
362 staticGeometryFunctions.Add("GeometryFromBinary", HandleSpatialFromBinaryFunction);
363 staticGeometryFunctions.Add("GeometryPointFromBinary", (sqlgen, functionExpression) => HandleFunctionDefaultGivenName(sqlgen, functionExpression, "geometry::STPointFromWKB"));
364 staticGeometryFunctions.Add("GeometryLineFromBinary", (sqlgen, functionExpression) => HandleFunctionDefaultGivenName(sqlgen, functionExpression, "geometry::STLineFromWKB"));
365 staticGeometryFunctions.Add("GeometryPolygonFromBinary", (sqlgen, functionExpression) => HandleFunctionDefaultGivenName(sqlgen, functionExpression, "geometry::STPolyFromWKB"));
366 staticGeometryFunctions.Add("GeometryMultiPointFromBinary", (sqlgen, functionExpression) => HandleFunctionDefaultGivenName(sqlgen, functionExpression, "geometry::STMPointFromWKB"));
367 staticGeometryFunctions.Add("GeometryMultiLineFromBinary", (sqlgen, functionExpression) => HandleFunctionDefaultGivenName(sqlgen, functionExpression, "geometry::STMLineFromWKB"));
368 staticGeometryFunctions.Add("GeometryMultiPolygonFromBinary", (sqlgen, functionExpression) => HandleFunctionDefaultGivenName(sqlgen, functionExpression, "geometry::STMPolyFromWKB"));
369 staticGeometryFunctions.Add("GeometryCollectionFromBinary", (sqlgen, functionExpression) => HandleFunctionDefaultGivenName(sqlgen, functionExpression, "geometry::STGeomCollFromWKB"));
371 // GML constructor (non-OGC)
372 staticGeometryFunctions.Add("GeometryFromGml", HandleSpatialFromGmlFunction);
374 return staticGeometryFunctions;
378 /// Initalizes the mapping from names of canonical function that represent geometry instance properties to their corresponding
379 /// store property name.
381 /// <returns></returns>
382 private static Dictionary<string, string> InitializeGeometryInstancePropertyFunctionsDictionary()
384 Dictionary<string, string> instancePropGeometryFunctions = new Dictionary<string, string>();
386 instancePropGeometryFunctions.Add("CoordinateSystemId", "STSrid");
387 instancePropGeometryFunctions.Add("Measure", "M");
388 instancePropGeometryFunctions.Add("XCoordinate", "STX");
389 instancePropGeometryFunctions.Add("YCoordinate", "STY");
390 instancePropGeometryFunctions.Add("Elevation", "Z");
392 return instancePropGeometryFunctions;
396 /// Initalizes the mapping of canonical function name to instance method name for geometry instance functions that differ in name from the sql server equivalent.
398 /// <returns></returns>
399 private static Dictionary<string, string> InitializeRenamedGeometryInstanceMethodFunctions()
401 Dictionary<string, string> renamedInstanceMethodFunctions = new Dictionary<string, string>();
403 renamedInstanceMethodFunctions.Add("AsText", "STAsText");
404 renamedInstanceMethodFunctions.Add("AsBinary", "STAsBinary");
405 renamedInstanceMethodFunctions.Add("SpatialTypeName", "STGeometryType");
406 renamedInstanceMethodFunctions.Add("SpatialDimension", "STDimension");
407 renamedInstanceMethodFunctions.Add("IsEmptySpatial", "STIsEmpty");
408 renamedInstanceMethodFunctions.Add("IsSimpleGeometry", "STIsSimple");
409 renamedInstanceMethodFunctions.Add("IsValidGeometry", "STIsValid");
410 renamedInstanceMethodFunctions.Add("SpatialBoundary", "STBoundary");
411 renamedInstanceMethodFunctions.Add("SpatialEnvelope", "STEnvelope");
412 renamedInstanceMethodFunctions.Add("SpatialEquals", "STEquals");
413 renamedInstanceMethodFunctions.Add("SpatialDisjoint", "STDisjoint");
414 renamedInstanceMethodFunctions.Add("SpatialIntersects", "STIntersects");
415 renamedInstanceMethodFunctions.Add("SpatialTouches", "STTouches");
416 renamedInstanceMethodFunctions.Add("SpatialCrosses", "STCrosses");
417 renamedInstanceMethodFunctions.Add("SpatialWithin", "STWithin");
418 renamedInstanceMethodFunctions.Add("SpatialContains", "STContains");
419 renamedInstanceMethodFunctions.Add("SpatialOverlaps", "STOverlaps");
420 renamedInstanceMethodFunctions.Add("SpatialRelate", "STRelate");
421 renamedInstanceMethodFunctions.Add("SpatialBuffer", "STBuffer");
422 renamedInstanceMethodFunctions.Add("SpatialConvexHull", "STConvexHull");
423 renamedInstanceMethodFunctions.Add("Distance", "STDistance");
424 renamedInstanceMethodFunctions.Add("SpatialUnion", "STUnion");
425 renamedInstanceMethodFunctions.Add("SpatialIntersection", "STIntersection");
426 renamedInstanceMethodFunctions.Add("SpatialDifference", "STDifference");
427 renamedInstanceMethodFunctions.Add("SpatialSymmetricDifference", "STSymDifference");
428 renamedInstanceMethodFunctions.Add("SpatialElementCount", "STNumGeometries");
429 renamedInstanceMethodFunctions.Add("SpatialElementAt", "STGeometryN");
430 renamedInstanceMethodFunctions.Add("SpatialLength", "STLength");
431 renamedInstanceMethodFunctions.Add("StartPoint", "STStartPoint");
432 renamedInstanceMethodFunctions.Add("EndPoint", "STEndPoint");
433 renamedInstanceMethodFunctions.Add("IsClosedSpatial", "STIsClosed");
434 renamedInstanceMethodFunctions.Add("IsRing", "STIsRing");
435 renamedInstanceMethodFunctions.Add("PointCount", "STNumPoints");
436 renamedInstanceMethodFunctions.Add("PointAt", "STPointN");
437 renamedInstanceMethodFunctions.Add("Area", "STArea");
438 renamedInstanceMethodFunctions.Add("Centroid", "STCentroid");
439 renamedInstanceMethodFunctions.Add("PointOnSurface", "STPointOnSurface");
440 renamedInstanceMethodFunctions.Add("ExteriorRing", "STExteriorRing");
441 renamedInstanceMethodFunctions.Add("InteriorRingCount", "STNumInteriorRing");
442 renamedInstanceMethodFunctions.Add("InteriorRingAt", "STInteriorRingN");
444 return renamedInstanceMethodFunctions;
447 private static ISqlFragment HandleSpatialFromTextFunction(SqlGenerator sqlgen, DbFunctionExpression functionExpression)
449 string functionNameWithSrid = (TypeSemantics.IsPrimitiveType(functionExpression.ResultType, PrimitiveTypeKind.Geometry) ? "geometry::STGeomFromText" : "geography::STGeomFromText");
450 string functionNameWithoutSrid = (TypeSemantics.IsPrimitiveType(functionExpression.ResultType, PrimitiveTypeKind.Geometry) ? "geometry::Parse" : "geography::Parse");
452 if (functionExpression.Arguments.Count == 2)
454 return HandleFunctionDefaultGivenName(sqlgen, functionExpression, functionNameWithSrid);
458 Debug.Assert(functionExpression.Arguments.Count == 1, "FromText function should have text or text + srid arguments only");
459 return HandleFunctionDefaultGivenName(sqlgen, functionExpression, functionNameWithoutSrid);
464 private static ISqlFragment HandleSpatialFromGmlFunction(SqlGenerator sqlgen, DbFunctionExpression functionExpression)
466 return HandleSpatialStaticMethodFunctionAppendSrid(sqlgen, functionExpression, (TypeSemantics.IsPrimitiveType(functionExpression.ResultType, PrimitiveTypeKind.Geometry) ? "geometry::GeomFromGml" : "geography::GeomFromGml"));
469 private static ISqlFragment HandleSpatialFromBinaryFunction(SqlGenerator sqlgen, DbFunctionExpression functionExpression)
471 return HandleSpatialStaticMethodFunctionAppendSrid(sqlgen, functionExpression, (TypeSemantics.IsPrimitiveType(functionExpression.ResultType, PrimitiveTypeKind.Geometry) ? "geometry::STGeomFromWKB" : "geography::STGeomFromWKB"));
474 private static readonly DbExpression defaultGeographySridExpression = DbExpressionBuilder.Constant(DbGeography.DefaultCoordinateSystemId);
475 private static readonly DbExpression defaultGeometrySridExpression = DbExpressionBuilder.Constant(DbGeometry.DefaultCoordinateSystemId);
477 private static ISqlFragment HandleSpatialStaticMethodFunctionAppendSrid(SqlGenerator sqlgen, DbFunctionExpression functionExpression, string functionName)
479 if (functionExpression.Arguments.Count == 2)
481 return HandleFunctionDefaultGivenName(sqlgen, functionExpression, functionName);
485 DbExpression sridExpression = (TypeSemantics.IsPrimitiveType(functionExpression.ResultType, PrimitiveTypeKind.Geometry) ? defaultGeometrySridExpression : defaultGeographySridExpression);
486 SqlBuilder result = new SqlBuilder();
487 result.Append(functionName);
488 WriteFunctionArguments(sqlgen, functionExpression.Arguments.Concat(new[] { sridExpression }), result);
495 internal static ISqlFragment GenerateFunctionCallSql(SqlGenerator sqlgen, DbFunctionExpression functionExpression)
498 // check if function requires special case processing, if so, delegates to it
500 if (IsSpecialCanonicalFunction(functionExpression))
502 return HandleSpecialCanonicalFunction(sqlgen, functionExpression);
505 if (IsSpecialStoreFunction(functionExpression))
507 return HandleSpecialStoreFunction(sqlgen, functionExpression);
510 PrimitiveTypeKind spatialTypeKind;
511 if(IsSpatialCanonicalFunction(functionExpression, out spatialTypeKind))
513 return HandleSpatialCanonicalFunction(sqlgen, functionExpression, spatialTypeKind);
516 return HandleFunctionDefault(sqlgen, functionExpression);
520 /// Determines whether the given function is a store function that
521 /// requires special handling
523 /// <param name="e"></param>
524 /// <returns></returns>
525 private static bool IsSpecialStoreFunction(DbFunctionExpression e)
527 return IsStoreFunction(e.Function)
528 && _storeFunctionHandlers.ContainsKey(e.Function.Name);
532 /// Determines whether the given function is a canonical function that
533 /// requires special handling
535 /// <param name="e"></param>
536 /// <returns></returns>
537 private static bool IsSpecialCanonicalFunction(DbFunctionExpression e)
539 return TypeHelpers.IsCanonicalFunction(e.Function)
540 && _canonicalFunctionHandlers.ContainsKey(e.Function.Name);
544 /// Determines whether the given function is a canonical function the translates
545 /// to a spatial (geography/geometry) property access or method call.
547 /// <param name="e"></param>
548 /// <returns></returns>
549 private static bool IsSpatialCanonicalFunction(DbFunctionExpression e, out PrimitiveTypeKind spatialTypeKind)
551 if (TypeHelpers.IsCanonicalFunction(e.Function))
553 if (Helper.IsSpatialType(e.ResultType, out spatialTypeKind))
558 foreach (FunctionParameter functionParameter in e.Function.Parameters)
560 if (Helper.IsSpatialType(functionParameter.TypeUsage, out spatialTypeKind))
567 spatialTypeKind = default(PrimitiveTypeKind);
572 /// Default handling for functions.
573 /// Translates them to FunctionName(arg1, arg2, ..., argn)
575 /// <param name="e"></param>
576 /// <returns></returns>
577 private static ISqlFragment HandleFunctionDefault(SqlGenerator sqlgen, DbFunctionExpression e)
579 return HandleFunctionDefaultGivenName(sqlgen, e, null);
583 /// Default handling for functions with a given name.
584 /// Translates them to FunctionName(arg1, arg2, ..., argn)
586 /// <param name="e"></param>
587 /// <param name="functionName"></param>
588 /// <returns></returns>
589 private static ISqlFragment HandleFunctionDefaultGivenName(SqlGenerator sqlgen, DbFunctionExpression e, string functionName)
591 // NOTE: The order of checks is important in case of CHARINDEX.
592 if (CastReturnTypeToInt64(e))
594 return HandleFunctionDefaultCastReturnValue(sqlgen, e, functionName, "bigint");
596 else if (CastReturnTypeToInt32(sqlgen, e))
598 return HandleFunctionDefaultCastReturnValue(sqlgen, e, functionName, "int");
600 else if (CastReturnTypeToInt16(e))
602 return HandleFunctionDefaultCastReturnValue(sqlgen, e, functionName, "smallint");
604 else if (CastReturnTypeToSingle(e))
606 return HandleFunctionDefaultCastReturnValue(sqlgen, e, functionName, "real");
610 return HandleFunctionDefaultCastReturnValue(sqlgen, e, functionName, null);
615 /// Default handling for functions with a given name and given return value cast.
616 /// Translates them to CAST(FunctionName(arg1, arg2, ..., argn) AS returnType)
618 /// <param name="e"></param>
619 /// <param name="functionName"></param>
620 /// <param name="returnType"></param>
621 /// <returns></returns>
622 private static ISqlFragment HandleFunctionDefaultCastReturnValue(SqlGenerator sqlgen, DbFunctionExpression e, string functionName, string returnType)
624 return WrapWithCast(returnType, result =>
626 if (functionName == null)
628 WriteFunctionName(result, e.Function);
632 result.Append(functionName);
635 HandleFunctionArgumentsDefault(sqlgen, e, result);
639 private static ISqlFragment WrapWithCast(string returnType, Action<SqlBuilder> toWrap)
641 SqlBuilder result = new SqlBuilder();
642 if (returnType != null)
644 result.Append(" CAST(");
649 if (returnType != null)
651 result.Append(" AS ");
652 result.Append(returnType);
659 /// Default handling on function arguments.
660 /// Appends the list of arguments to the given result
661 /// If the function is niladic it does not append anything,
662 /// otherwise it appends (arg1, arg2, .., argn)
664 /// <param name="e"></param>
665 /// <param name="result"></param>
666 private static void HandleFunctionArgumentsDefault(SqlGenerator sqlgen, DbFunctionExpression e, SqlBuilder result)
668 bool isNiladicFunction = e.Function.NiladicFunctionAttribute;
669 Debug.Assert(!(isNiladicFunction && (0 < e.Arguments.Count)), "function attributed as NiladicFunction='true' in the provider manifest cannot have arguments");
670 if (isNiladicFunction && e.Arguments.Count > 0)
672 EntityUtil.Metadata(System.Data.Entity.Strings.SqlGen_NiladicFunctionsCannotHaveParameters);
675 if (!isNiladicFunction)
677 WriteFunctionArguments(sqlgen, e.Arguments, result);
681 private static void WriteFunctionArguments(SqlGenerator sqlgen, IEnumerable<DbExpression> functionArguments, SqlBuilder result)
684 string separator = "";
685 foreach (DbExpression arg in functionArguments)
687 result.Append(separator);
688 result.Append(arg.Accept(sqlgen));
695 /// Handler for functions that need to be translated to different store function based on version
697 /// <param name="e"></param>
698 /// <param name="preKatmaiName"></param>
699 /// <param name="katmaiName"></param>
700 /// <returns></returns>
701 private static ISqlFragment HandleFunctionGivenNameBasedOnVersion(SqlGenerator sqlgen, DbFunctionExpression e, string preKatmaiName, string katmaiName)
703 if (sqlgen.IsPreKatmai)
705 return HandleFunctionDefaultGivenName(sqlgen, e, preKatmaiName);
707 return HandleFunctionDefaultGivenName(sqlgen, e, katmaiName);
711 /// Handler for special build in functions
713 /// <param name="e"></param>
714 /// <returns></returns>
715 private static ISqlFragment HandleSpecialStoreFunction(SqlGenerator sqlgen, DbFunctionExpression e)
717 return HandleSpecialFunction(_storeFunctionHandlers, sqlgen, e);
721 /// Handler for special canonical functions
723 /// <param name="e"></param>
724 /// <returns></returns>
725 private static ISqlFragment HandleSpecialCanonicalFunction(SqlGenerator sqlgen, DbFunctionExpression e)
727 return HandleSpecialFunction(_canonicalFunctionHandlers, sqlgen, e);
731 /// Dispatches the special function processing to the appropriate handler
733 /// <param name="handlers"></param>
734 /// <param name="e"></param>
735 /// <returns></returns>
736 private static ISqlFragment HandleSpecialFunction(Dictionary<string, FunctionHandler> handlers, SqlGenerator sqlgen, DbFunctionExpression e)
738 Debug.Assert(handlers.ContainsKey(e.Function.Name), "Special handling should be called only for functions in the list of special functions");
739 return handlers[e.Function.Name](sqlgen, e);
742 private static ISqlFragment HandleSpatialCanonicalFunction(SqlGenerator sqlgen, DbFunctionExpression functionExpression, PrimitiveTypeKind spatialTypeKind)
744 Debug.Assert(spatialTypeKind == PrimitiveTypeKind.Geography || spatialTypeKind == PrimitiveTypeKind.Geometry, "Spatial function does not refer to a valid spatial primitive type kind?");
745 if (spatialTypeKind == PrimitiveTypeKind.Geography)
747 return HandleSpatialCanonicalFunction(sqlgen, functionExpression, _geographyFunctionNameToStaticMethodHandlerDictionary, _geographyFunctionNameToInstancePropertyNameDictionary, _geographyRenamedInstanceMethodFunctionDictionary);
751 return HandleSpatialCanonicalFunction(sqlgen, functionExpression, _geometryFunctionNameToStaticMethodHandlerDictionary, _geometryFunctionNameToInstancePropertyNameDictionary, _geometryRenamedInstanceMethodFunctionDictionary);
755 private static ISqlFragment HandleSpatialCanonicalFunction(SqlGenerator sqlgen,
756 DbFunctionExpression functionExpression,
757 Dictionary<string, FunctionHandler> staticMethodsMap,
758 Dictionary<string, string> instancePropertiesMap,
759 Dictionary<string, string> renamedInstanceMethodsMap)
761 FunctionHandler staticFunctionHandler;
762 string instancePropertyName;
763 if (staticMethodsMap.TryGetValue(functionExpression.Function.Name, out staticFunctionHandler))
765 return staticFunctionHandler(sqlgen, functionExpression);
767 else if (instancePropertiesMap.TryGetValue(functionExpression.Function.Name, out instancePropertyName))
769 Debug.Assert(functionExpression.Function.Parameters.Count > 0 && Helper.IsSpatialType(functionExpression.Function.Parameters[0].TypeUsage), "Instance property function does not have instance parameter?");
770 return WriteInstanceFunctionCall(sqlgen, instancePropertyName, functionExpression, isPropertyAccess: true, castReturnTypeTo: null);
774 // Default translation pattern is instance method; the instance method name may differ from that of the spatial canonical function
775 Debug.Assert(functionExpression.Function.Parameters.Count > 0 && Helper.IsSpatialType(functionExpression.Function.Parameters[0].TypeUsage), "Instance method function does not have instance parameter?");
776 string effectiveFunctionName;
777 if (!renamedInstanceMethodsMap.TryGetValue(functionExpression.Function.Name, out effectiveFunctionName))
779 effectiveFunctionName = functionExpression.Function.Name;
782 // For AsGml() calls, the XML result must be cast to string to match the declared function result type.
783 string castResultType = null;
784 if (effectiveFunctionName == "AsGml")
786 castResultType = sqlgen.DefaultStringTypeName;
788 return WriteInstanceFunctionCall(sqlgen, effectiveFunctionName, functionExpression, isPropertyAccess: false, castReturnTypeTo: castResultType);
792 private static ISqlFragment WriteInstanceFunctionCall(SqlGenerator sqlgen, string functionName, DbFunctionExpression functionExpression, bool isPropertyAccess)
794 return WriteInstanceFunctionCall(sqlgen, functionName, functionExpression, isPropertyAccess, null);
797 private static ISqlFragment WriteInstanceFunctionCall(SqlGenerator sqlgen, string functionName, DbFunctionExpression functionExpression, bool isPropertyAccess, string castReturnTypeTo)
799 Debug.Assert(!isPropertyAccess || functionExpression.Arguments.Count == 1, "Property accessor instance functions should have only the single instance argument");
801 return WrapWithCast(castReturnTypeTo, result =>
803 DbExpression instanceExpression = functionExpression.Arguments[0];
805 // Write the instance - if this is another function call, it need not be enclosed in parentheses.
806 if (instanceExpression.ExpressionKind != DbExpressionKind.Function)
808 sqlgen.ParenthesizeExpressionIfNeeded(instanceExpression, result);
812 result.Append(instanceExpression.Accept(sqlgen));
815 result.Append(functionName);
817 if (!isPropertyAccess)
819 WriteFunctionArguments(sqlgen, functionExpression.Arguments.Skip(1), result);
826 /// Handles functions that are translated into TSQL operators.
827 /// The given function should have one or two arguments.
828 /// Functions with one arguemnt are translated into
830 /// Functions with two arguments are translated into
832 /// Also, the arguments can be optionaly enclosed in parethesis
834 /// <param name="e"></param>
835 /// <param name="parenthesiseArguments">Whether the arguments should be enclosed in parethesis</param>
836 /// <returns></returns>
837 private static ISqlFragment HandleSpecialFunctionToOperator(SqlGenerator sqlgen, DbFunctionExpression e, bool parenthesiseArguments)
839 SqlBuilder result = new SqlBuilder();
840 Debug.Assert(e.Arguments.Count > 0 && e.Arguments.Count <= 2, "There should be 1 or 2 arguments for operator");
842 if (e.Arguments.Count > 1)
844 if (parenthesiseArguments)
848 result.Append(e.Arguments[0].Accept(sqlgen));
849 if (parenthesiseArguments)
855 Debug.Assert(_functionNameToOperatorDictionary.ContainsKey(e.Function.Name), "The function can not be mapped to an operator");
856 result.Append(_functionNameToOperatorDictionary[e.Function.Name]);
859 if (parenthesiseArguments)
863 result.Append(e.Arguments[e.Arguments.Count - 1].Accept(sqlgen));
864 if (parenthesiseArguments)
872 /// <see cref="HandleSpecialFunctionToOperator"></see>
874 /// <param name="sqlgen"></param>
875 /// <param name="e"></param>
876 /// <returns></returns>
877 private static ISqlFragment HandleConcatFunction(SqlGenerator sqlgen, DbFunctionExpression e)
879 return HandleSpecialFunctionToOperator(sqlgen, e, false);
883 /// <see cref="HandleSpecialFunctionToOperator"></see>
885 /// <param name="sqlgen"></param>
886 /// <param name="e"></param>
887 /// <returns></returns>
888 private static ISqlFragment HandleCanonicalFunctionBitwise(SqlGenerator sqlgen, DbFunctionExpression e)
890 return HandleSpecialFunctionToOperator(sqlgen, e, true);
894 /// Handles special case in which datapart 'type' parameter is present. all the functions
895 /// handles here have *only* the 1st parameter as datepart. datepart value is passed along
896 /// the QP as string and has to be expanded as TSQL keyword.
898 /// <param name="sqlgen"></param>
899 /// <param name="e"></param>
900 /// <returns></returns>
901 private static ISqlFragment HandleDatepartDateFunction(SqlGenerator sqlgen, DbFunctionExpression e)
903 Debug.Assert(e.Arguments.Count > 0, "e.Arguments.Count > 0");
905 DbConstantExpression constExpr = e.Arguments[0] as DbConstantExpression;
906 if (null == constExpr)
908 throw EntityUtil.InvalidOperation(System.Data.Entity.Strings.SqlGen_InvalidDatePartArgumentExpression(e.Function.NamespaceName, e.Function.Name));
911 string datepart = constExpr.Value as string;
912 if (null == datepart)
914 throw EntityUtil.InvalidOperation(System.Data.Entity.Strings.SqlGen_InvalidDatePartArgumentExpression(e.Function.NamespaceName, e.Function.Name));
917 SqlBuilder result = new SqlBuilder();
920 // check if datepart value is valid
922 if (!_datepartKeywords.Contains(datepart))
924 throw EntityUtil.InvalidOperation(System.Data.Entity.Strings.SqlGen_InvalidDatePartArgumentValue(datepart, e.Function.NamespaceName, e.Function.Name));
928 // finaly, expand the function name
930 WriteFunctionName(result, e.Function);
933 // expand the datepart literal as tsql kword
934 result.Append(datepart);
935 string separator = ", ";
937 // expand remaining arguments
938 for (int i = 1; i < e.Arguments.Count; i++)
940 result.Append(separator);
941 result.Append(e.Arguments[i].Accept(sqlgen));
950 /// Handler for canonical functions for extracting date parts.
952 /// Year(date) -> DATEPART( year, date)
954 /// <param name="sqlgen"></param>
955 /// <param name="e"></param>
956 /// <returns></returns>
957 [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase")]
958 private static ISqlFragment HandleCanonicalFunctionDatepart(SqlGenerator sqlgen, DbFunctionExpression e)
960 return HandleCanonicalFunctionDatepart(sqlgen, e.Function.Name.ToLowerInvariant(), e);
964 /// Handler for canonical funcitons for GetTotalOffsetMinutes.
965 /// GetTotalOffsetMinutes(e) --> Datepart(tzoffset, e)
967 /// <param name="sqlgen"></param>
968 /// <param name="e"></param>
969 /// <returns></returns>
970 private static ISqlFragment HandleCanonicalFunctionGetTotalOffsetMinutes(SqlGenerator sqlgen, DbFunctionExpression e)
972 return HandleCanonicalFunctionDatepart(sqlgen, "tzoffset", e);
976 /// Handler for turning a canonical function into DATEPART
977 /// Results in DATEPART(datepart, e)
979 /// <param name="datepart"></param>
980 /// <param name="e"></param>
981 /// <returns></returns>
982 private static ISqlFragment HandleCanonicalFunctionDatepart(SqlGenerator sqlgen, string datepart, DbFunctionExpression e)
984 SqlBuilder result = new SqlBuilder();
985 result.Append("DATEPART (");
986 result.Append(datepart);
989 Debug.Assert(e.Arguments.Count == 1, "Canonical datepart functions should have exactly one argument");
990 result.Append(e.Arguments[0].Accept(sqlgen));
998 /// Handler for the canonical function CurrentDateTime
999 /// For Sql8 and Sql9: CurrentDateTime() -> GetDate()
1000 /// For Sql10: CurrentDateTime() -> SysDateTime()
1002 /// <param name="sqlgen"></param>
1003 /// <param name="e"></param>
1004 /// <returns></returns>
1005 private static ISqlFragment HandleCanonicalFunctionCurrentDateTime(SqlGenerator sqlgen, DbFunctionExpression e)
1007 return HandleFunctionGivenNameBasedOnVersion(sqlgen, e, "GetDate", "SysDateTime");
1011 /// Handler for the canonical function CurrentUtcDateTime
1012 /// For Sql8 and Sql9: CurrentUtcDateTime() -> GetUtcDate()
1013 /// For Sql10: CurrentUtcDateTime() -> SysUtcDateTime()
1015 /// <param name="sqlgen"></param>
1016 /// <param name="e"></param>
1017 /// <returns></returns>
1018 private static ISqlFragment HandleCanonicalFunctionCurrentUtcDateTime(SqlGenerator sqlgen, DbFunctionExpression e)
1020 return HandleFunctionGivenNameBasedOnVersion(sqlgen, e, "GetUtcDate", "SysUtcDateTime");
1024 /// Handler for the canonical function CurrentDateTimeOffset
1025 /// For Sql8 and Sql9: throw
1026 /// For Sql10: CurrentDateTimeOffset() -> SysDateTimeOffset()
1028 /// <param name="sqlgen"></param>
1029 /// <param name="e"></param>
1030 /// <returns></returns>
1031 private static ISqlFragment HandleCanonicalFunctionCurrentDateTimeOffset(SqlGenerator sqlgen, DbFunctionExpression e)
1033 sqlgen.AssertKatmaiOrNewer(e);
1034 return HandleFunctionDefaultGivenName(sqlgen, e, "SysDateTimeOffset");
1038 /// See <see cref="HandleCanonicalFunctionDateTimeTypeCreation"/> for exact translation
1039 /// Pre Katmai creates datetime.
1040 /// On Katmai creates datetime2.
1042 /// <param name="sqlgen"></param>
1043 /// <param name="e"></param>
1044 /// <returns></returns>
1045 private static ISqlFragment HandleCanonicalFunctionCreateDateTime(SqlGenerator sqlgen, DbFunctionExpression e)
1047 string typeName = (sqlgen.IsPreKatmai) ? "datetime" : "datetime2";
1048 return HandleCanonicalFunctionDateTimeTypeCreation(sqlgen, typeName, e.Arguments, true, false);
1052 /// See <see cref="HandleCanonicalFunctionDateTimeTypeCreation"/> for exact translation
1053 /// Pre Katmai not supported.
1054 /// On Katmai creates datetimeoffset.
1056 /// <param name="sqlgen"></param>
1057 /// <param name="e"></param>
1058 /// <returns></returns>
1059 private static ISqlFragment HandleCanonicalFunctionCreateDateTimeOffset(SqlGenerator sqlgen, DbFunctionExpression e)
1061 sqlgen.AssertKatmaiOrNewer(e);
1062 return HandleCanonicalFunctionDateTimeTypeCreation(sqlgen, "datetimeoffset", e.Arguments, true, true);
1066 /// See <see cref="HandleCanonicalFunctionDateTimeTypeCreation"/> for exact translation
1067 /// Pre Katmai not supported.
1068 /// On Katmai creates time.
1070 /// <param name="sqlgen"></param>
1071 /// <param name="e"></param>
1072 /// <returns></returns>
1073 private static ISqlFragment HandleCanonicalFunctionCreateTime(SqlGenerator sqlgen, DbFunctionExpression e)
1075 sqlgen.AssertKatmaiOrNewer(e);
1076 return HandleCanonicalFunctionDateTimeTypeCreation(sqlgen, "time", e.Arguments, false, false);
1080 /// Helper for all date and time types creating functions.
1082 /// The given expression is in general trainslated into:
1084 /// CONVERT(@typename, [datePart] + [timePart] + [timeZonePart], 121), where the datePart and the timeZonePart are optional
1086 /// Only on Katmai, if a date part is present it is wrapped with a call for adding years as shown below.
1087 /// The individual parts are translated as:
1090 /// PRE KATMAI: convert(varchar(255), @year) + '-' + convert(varchar(255), @month) + '-' + convert(varchar(255), @day)
1091 /// KATMAI: DateAdd(year, @year-1, covert(@typename, '0001' + '-' + convert(varchar(255), @month) + '-' + convert(varchar(255), @day) + [possibly time ], 121)
1094 /// PRE KATMAI: convert(varchar(255), @hour)+ ':' + convert(varchar(255), @minute)+ ':' + str(@second, 6, 3)
1095 /// KATMAI: convert(varchar(255), @hour)+ ':' + convert(varchar(255), @minute)+ ':' + str(@second, 10, 7)
1098 /// (case when @tzoffset >= 0 then '+' else '-' end) + convert(varchar(255), ABS(@tzoffset)/60) + ':' + convert(varchar(255), ABS(@tzoffset)%60)
1101 /// <param name="typeName"></param>
1102 /// <param name="args"></param>
1103 /// <param name="hasDatePart"></param>
1104 /// <param name="hasTimeZonePart"></param>
1105 /// <returns></returns>
1106 private static ISqlFragment HandleCanonicalFunctionDateTimeTypeCreation(SqlGenerator sqlgen, string typeName, IList<DbExpression> args, bool hasDatePart, bool hasTimeZonePart)
1108 Debug.Assert(args.Count == (hasDatePart ? 3 : 0) + 3 + (hasTimeZonePart ? 1 : 0), "Invalid number of parameters for a date time creating function");
1110 SqlBuilder result = new SqlBuilder();
1111 int currentArgumentIndex = 0;
1113 if (!sqlgen.IsPreKatmai && hasDatePart)
1115 result.Append("DATEADD(year, ");
1116 sqlgen.ParenthesizeExpressionIfNeeded(args[currentArgumentIndex++], result);
1117 result.Append(" - 1, ");
1120 result.Append("convert (");
1121 result.Append(typeName);
1124 //Building the string representation
1127 // YEAR: PREKATMAI: CONVERT(VARCHAR, @YEAR)
1129 if (!sqlgen.IsPreKatmai)
1131 result.Append("'0001'");
1135 AppendConvertToVarchar(sqlgen, result, args[currentArgumentIndex++]);
1139 result.Append(" + '-' + ");
1140 AppendConvertToVarchar(sqlgen, result, args[currentArgumentIndex++]);
1143 result.Append(" + '-' + ");
1144 AppendConvertToVarchar(sqlgen, result, args[currentArgumentIndex++]);
1145 result.Append(" + ' ' + ");
1149 AppendConvertToVarchar(sqlgen, result, args[currentArgumentIndex++]);
1152 result.Append(" + ':' + ");
1153 AppendConvertToVarchar(sqlgen, result, args[currentArgumentIndex++]);
1156 result.Append(" + ':' + str(");
1157 result.Append(args[currentArgumentIndex++].Accept(sqlgen));
1159 if (sqlgen.IsPreKatmai)
1161 result.Append(", 6, 3)");
1165 result.Append(", 10, 7)");
1169 if (hasTimeZonePart)
1171 result.Append(" + (CASE WHEN ");
1172 sqlgen.ParenthesizeExpressionIfNeeded(args[currentArgumentIndex], result);
1173 result.Append(" >= 0 THEN '+' ELSE '-' END) + convert(varchar(255), ABS(");
1174 sqlgen.ParenthesizeExpressionIfNeeded(args[currentArgumentIndex], result);
1175 result.Append("/60)) + ':' + convert(varchar(255), ABS(");
1176 sqlgen.ParenthesizeExpressionIfNeeded(args[currentArgumentIndex], result);
1177 result.Append("%60))");
1180 result.Append(", 121)");
1182 if (!sqlgen.IsPreKatmai && hasDatePart)
1190 /// Helper method that wrapps the given expession with a conver to varchar(255)
1192 /// <param name="result"></param>
1193 /// <param name="e"></param>
1194 private static void AppendConvertToVarchar(SqlGenerator sqlgen, SqlBuilder result, DbExpression e)
1196 result.Append("convert(varchar(255), ");
1197 result.Append(e.Accept(sqlgen));
1202 /// TruncateTime(DateTime X)
1203 /// PreKatmai: TRUNCATETIME(X) => CONVERT(DATETIME, CONVERT(VARCHAR(255), expression, 102), 102)
1204 /// Katmai: TRUNCATETIME(X) => CONVERT(DATETIME2, CONVERT(VARCHAR(255), expression, 102), 102)
1206 /// TruncateTime(DateTimeOffset X)
1207 /// TRUNCATETIME(X) => CONVERT(datetimeoffset, CONVERT(VARCHAR(255), expression, 102)
1208 /// + ' 00:00:00 ' + Right(convert(varchar(255), @arg, 121), 6), 102)
1211 /// <param name="sqlgen"></param>
1212 /// <param name="e"></param>
1213 /// <returns></returns>
1214 private static ISqlFragment HandleCanonicalFunctionTruncateTime(SqlGenerator sqlgen, DbFunctionExpression e)
1216 //The type that we need to return is based on the argument type.
1217 string typeName = null;
1218 bool isDateTimeOffset = false;
1220 PrimitiveTypeKind typeKind;
1221 bool isPrimitiveType = TypeHelpers.TryGetPrimitiveTypeKind(e.Arguments[0].ResultType, out typeKind);
1222 Debug.Assert(isPrimitiveType, "Expecting primitive type as input parameter to TruncateTime");
1224 if (typeKind == PrimitiveTypeKind.DateTime)
1226 typeName = sqlgen.IsPreKatmai ? "datetime" : "datetime2";
1228 else if (typeKind == PrimitiveTypeKind.DateTimeOffset)
1230 typeName = "datetimeoffset";
1231 isDateTimeOffset = true;
1235 Debug.Assert(true, "Unexpected type to TruncateTime" + typeKind.ToString());
1238 SqlBuilder result = new SqlBuilder();
1239 result.Append("convert (");
1240 result.Append(typeName);
1241 result.Append(", convert(varchar(255), ");
1242 result.Append(e.Arguments[0].Accept(sqlgen));
1243 result.Append(", 102) ");
1245 if (isDateTimeOffset)
1247 result.Append("+ ' 00:00:00 ' + Right(convert(varchar(255), ");
1248 result.Append(e.Arguments[0].Accept(sqlgen));
1249 result.Append(", 121), 6) ");
1252 result.Append(", 102)");
1257 /// Handler for date addition functions supported only starting from Katmai
1259 /// <param name="sqlgen"></param>
1260 /// <param name="e"></param>
1261 /// <returns></returns>
1262 private static ISqlFragment HandleCanonicalFunctionDateAddKatmaiOrNewer(SqlGenerator sqlgen, DbFunctionExpression e)
1264 sqlgen.AssertKatmaiOrNewer(e);
1265 return HandleCanonicalFunctionDateAdd(sqlgen, e);
1269 /// Handler for all date/time addition canonical functions.
1270 /// Translation, e.g.
1271 /// AddYears(datetime, number) => DATEADD(year, number, datetime)
1273 /// <param name="sqlgen"></param>
1274 /// <param name="e"></param>
1275 /// <returns></returns>
1276 private static ISqlFragment HandleCanonicalFunctionDateAdd(SqlGenerator sqlgen, DbFunctionExpression e)
1278 SqlBuilder result = new SqlBuilder();
1280 result.Append("DATEADD (");
1281 result.Append(_dateAddFunctionNameToDatepartDictionary[e.Function.Name]);
1282 result.Append(", ");
1283 result.Append(e.Arguments[1].Accept(sqlgen));
1284 result.Append(", ");
1285 result.Append(e.Arguments[0].Accept(sqlgen));
1292 /// Hanndler for date differencing functions supported only starting from Katmai
1294 /// <param name="sqlgen"></param>
1295 /// <param name="e"></param>
1296 /// <returns></returns>
1297 private static ISqlFragment HandleCanonicalFunctionDateDiffKatmaiOrNewer(SqlGenerator sqlgen, DbFunctionExpression e)
1299 sqlgen.AssertKatmaiOrNewer(e);
1300 return HandleCanonicalFunctionDateDiff(sqlgen, e);
1304 /// Handler for all date/time addition canonical functions.
1305 /// Translation, e.g.
1306 /// DiffYears(datetime, number) => DATEDIFF(year, number, datetime)
1308 /// <param name="sqlgen"></param>
1309 /// <param name="e"></param>
1310 /// <returns></returns>
1311 private static ISqlFragment HandleCanonicalFunctionDateDiff(SqlGenerator sqlgen, DbFunctionExpression e)
1313 SqlBuilder result = new SqlBuilder();
1315 result.Append("DATEDIFF (");
1316 result.Append(_dateDiffFunctionNameToDatepartDictionary[e.Function.Name]);
1317 result.Append(", ");
1318 result.Append(e.Arguments[0].Accept(sqlgen));
1319 result.Append(", ");
1320 result.Append(e.Arguments[1].Accept(sqlgen));
1327 /// Function rename IndexOf -> CHARINDEX
1329 /// <param name="sqlgen"></param>
1330 /// <param name="e"></param>
1331 /// <returns></returns>
1332 private static ISqlFragment HandleCanonicalFunctionIndexOf(SqlGenerator sqlgen, DbFunctionExpression e)
1334 return HandleFunctionDefaultGivenName(sqlgen, e, "CHARINDEX");
1338 /// Function rename NewGuid -> NEWID
1340 /// <param name="sqlgen"></param>
1341 /// <param name="e"></param>
1342 /// <returns></returns>
1343 private static ISqlFragment HandleCanonicalFunctionNewGuid(SqlGenerator sqlgen, DbFunctionExpression e)
1345 return HandleFunctionDefaultGivenName(sqlgen, e, "NEWID");
1349 /// Function rename Length -> LEN
1351 /// <param name="sqlgen"></param>
1352 /// <param name="e"></param>
1353 /// <returns></returns>
1354 private static ISqlFragment HandleCanonicalFunctionLength(SqlGenerator sqlgen, DbFunctionExpression e)
1356 // We are aware of SQL Server's trimming of trailing spaces. We disclaim that behavior in general.
1357 // It's up to the user to decide whether to trim them explicitly or to append a non-blank space char explicitly.
1358 // Once SQL Server implements a function that computes Length correctly, we'll use it here instead of LEN,
1359 // and we'll drop the disclaimer.
1360 return HandleFunctionDefaultGivenName(sqlgen, e, "LEN");
1364 /// Round(numericExpression) -> Round(numericExpression, 0);
1365 /// Round(numericExpression, digits) -> Round(numericExpression, digits);
1367 /// <param name="sqlgen"></param>
1368 /// <param name="e"></param>
1369 /// <returns></returns>
1370 private static ISqlFragment HandleCanonicalFunctionRound(SqlGenerator sqlgen, DbFunctionExpression e)
1372 return HandleCanonicalFunctionRoundOrTruncate(sqlgen, e, true);
1376 /// Truncate(numericExpression) -> Round(numericExpression, 0, 1); (does not exist as canonical function yet)
1377 /// Truncate(numericExpression, digits) -> Round(numericExpression, digits, 1);
1379 /// <param name="sqlgen"></param>
1380 /// <param name="e"></param>
1381 /// <returns></returns>
1382 private static ISqlFragment HandleCanonicalFunctionTruncate(SqlGenerator sqlgen, DbFunctionExpression e)
1384 return HandleCanonicalFunctionRoundOrTruncate(sqlgen, e, false);
1388 /// Common handler for the canonical functions ROUND and TRUNCATE
1390 /// <param name="e"></param>
1391 /// <param name="round"></param>
1392 /// <returns></returns>
1393 private static ISqlFragment HandleCanonicalFunctionRoundOrTruncate(SqlGenerator sqlgen, DbFunctionExpression e, bool round)
1395 SqlBuilder result = new SqlBuilder();
1397 // Do not add the cast for the Round() overload having two arguments.
1398 // Round(Single,Int32) maps to Round(Double,Int32)due to implicit casting.
1399 // We don't need to cast in that case, since the server returned type is same
1400 // as the expected type. Cast is only required for the overload - Round(Single)
1401 bool requiresCastToSingle = false;
1402 if (e.Arguments.Count == 1)
1404 requiresCastToSingle = CastReturnTypeToSingle(e);
1405 if (requiresCastToSingle)
1407 result.Append(" CAST(");
1410 result.Append("ROUND(");
1412 Debug.Assert(e.Arguments.Count <= 2, "Round or truncate should have at most 2 arguments");
1413 result.Append(e.Arguments[0].Accept(sqlgen));
1414 result.Append(", ");
1416 if (e.Arguments.Count > 1)
1418 result.Append(e.Arguments[1].Accept(sqlgen));
1427 result.Append(", 1");
1432 if (requiresCastToSingle)
1434 result.Append(" AS real)");
1440 /// Handle the canonical function Abs().
1442 /// <param name="sqlgen"></param>
1443 /// <param name="e"></param>
1444 /// <returns></returns>
1445 private static ISqlFragment HandleCanonicalFunctionAbs(SqlGenerator sqlgen, DbFunctionExpression e)
1447 // Convert the call to Abs(Byte) to a no-op, since Byte is an unsigned type.
1448 if (TypeSemantics.IsPrimitiveType(e.Arguments[0].ResultType, PrimitiveTypeKind.Byte))
1450 SqlBuilder result = new SqlBuilder();
1451 result.Append(e.Arguments[0].Accept(sqlgen));
1456 return HandleFunctionDefault(sqlgen, e);
1461 /// TRIM(string) -> LTRIM(RTRIM(string))
1463 /// <param name="sqlgen"></param>
1464 /// <param name="e"></param>
1465 /// <returns></returns>
1466 private static ISqlFragment HandleCanonicalFunctionTrim(SqlGenerator sqlgen, DbFunctionExpression e)
1468 SqlBuilder result = new SqlBuilder();
1470 result.Append("LTRIM(RTRIM(");
1472 Debug.Assert(e.Arguments.Count == 1, "Trim should have one argument");
1473 result.Append(e.Arguments[0].Accept(sqlgen));
1475 result.Append("))");
1481 /// Function rename ToLower -> LOWER
1483 /// <param name="sqlgen"></param>
1484 /// <param name="e"></param>
1485 /// <returns></returns>
1486 private static ISqlFragment HandleCanonicalFunctionToLower(SqlGenerator sqlgen, DbFunctionExpression e)
1488 return HandleFunctionDefaultGivenName(sqlgen, e, "LOWER");
1492 /// Function rename ToUpper -> UPPER
1494 /// <param name="sqlgen"></param>
1495 /// <param name="e"></param>
1496 /// <returns></returns>
1497 private static ISqlFragment HandleCanonicalFunctionToUpper(SqlGenerator sqlgen, DbFunctionExpression e)
1499 return HandleFunctionDefaultGivenName(sqlgen, e, "UPPER");
1503 /// Function to translate the StartsWith, EndsWith and Contains canonical functions to LIKE expression in T-SQL
1504 /// and also add the trailing ESCAPE '~' when escaping of the search string for the LIKE expression has occurred
1506 /// <param name="sqlgen"></param>
1507 /// <param name="targetExpression"></param>
1508 /// <param name="constSearchParamExpression"></param>
1509 /// <param name="result"></param>
1510 /// <param name="insertPercentStart"></param>
1511 /// <param name="insertPercentEnd"></param>
1512 private static void TranslateConstantParameterForLike(SqlGenerator sqlgen, DbExpression targetExpression, DbConstantExpression constSearchParamExpression, SqlBuilder result, bool insertPercentStart, bool insertPercentEnd)
1514 result.Append(targetExpression.Accept(sqlgen));
1515 result.Append(" LIKE ");
1517 // If it's a DbConstantExpression then escape the search parameter if necessary.
1518 bool escapingOccurred;
1520 StringBuilder searchParamBuilder = new StringBuilder();
1521 if (insertPercentStart == true)
1522 searchParamBuilder.Append("%");
1523 searchParamBuilder.Append(SqlProviderManifest.EscapeLikeText(constSearchParamExpression.Value as string, false, out escapingOccurred));
1524 if (insertPercentEnd == true)
1525 searchParamBuilder.Append("%");
1527 DbConstantExpression escapedSearchParamExpression = new DbConstantExpression(constSearchParamExpression.ResultType, searchParamBuilder.ToString());
1528 result.Append(escapedSearchParamExpression.Accept(sqlgen));
1530 // If escaping did occur (special characters were found), then append the escape character used.
1531 if (escapingOccurred)
1532 result.Append(" ESCAPE '" + SqlProviderManifest.LikeEscapeChar + "'");
1536 /// Handler for Contains. Wraps the normal translation with a case statement
1538 /// <param name="sqlgen"></param>
1539 /// <param name="e"></param>
1540 /// <returns></returns>
1541 private static ISqlFragment HandleCanonicalFunctionContains(SqlGenerator sqlgen, DbFunctionExpression e)
1543 return WrapPredicate( HandleCanonicalFunctionContains, sqlgen, e);
1547 /// CONTAINS(arg0, arg1) => arg0 LIKE '%arg1%'
1549 /// <param name="sqlgen"></param>
1550 /// <param name="args"></param>
1551 /// <param name="result"></param>
1552 /// <returns></returns>
1553 private static SqlBuilder HandleCanonicalFunctionContains(SqlGenerator sqlgen, IList<DbExpression> args, SqlBuilder result)
1555 Debug.Assert(args.Count == 2, "Contains should have two arguments");
1556 // Check if args[1] is a DbConstantExpression
1557 DbConstantExpression constSearchParamExpression = args[1] as DbConstantExpression;
1558 if ((constSearchParamExpression != null) && (string.IsNullOrEmpty(constSearchParamExpression.Value as string) == false))
1560 TranslateConstantParameterForLike(sqlgen, args[0], constSearchParamExpression, result, true, true);
1564 // We use CHARINDEX when the search param is a DbNullExpression because all of SQL Server 2008, 2005 and 2000
1565 // consistently return NULL as the result.
1566 // However, if instead we use the optimized LIKE translation when the search param is a DbNullExpression,
1567 // only SQL Server 2005 yields a True instead of a DbNull as compared to SQL Server 2008 and 2000. This is
1568 // tracked in SQLBUDT #32315 in LIKE in SQL Server 2005.
1569 result.Append("CHARINDEX( ");
1570 result.Append(args[1].Accept(sqlgen));
1571 result.Append(", ");
1572 result.Append(args[0].Accept(sqlgen));
1573 result.Append(") > 0");
1579 /// Handler for StartsWith. Wraps the normal translation with a case statement
1581 /// <param name="sqlgen"></param>
1582 /// <param name="e"></param>
1583 /// <returns></returns>
1584 private static ISqlFragment HandleCanonicalFunctionStartsWith(SqlGenerator sqlgen, DbFunctionExpression e)
1586 return WrapPredicate(HandleCanonicalFunctionStartsWith, sqlgen, e);
1590 /// STARTSWITH(arg0, arg1) => arg0 LIKE 'arg1%'
1592 /// <param name="sqlgen"></param>
1593 /// <param name="args"></param>
1594 /// <param name="result"></param>
1595 /// <returns></returns>
1596 private static SqlBuilder HandleCanonicalFunctionStartsWith(SqlGenerator sqlgen, IList<DbExpression> args, SqlBuilder result)
1598 Debug.Assert(args.Count == 2, "StartsWith should have two arguments");
1599 // Check if args[1] is a DbConstantExpression
1600 DbConstantExpression constSearchParamExpression = args[1] as DbConstantExpression;
1601 if ((constSearchParamExpression != null) && (string.IsNullOrEmpty(constSearchParamExpression.Value as string) == false))
1603 TranslateConstantParameterForLike(sqlgen, args[0], constSearchParamExpression, result, false, true);
1607 // We use CHARINDEX when the search param is a DbNullExpression because all of SQL Server 2008, 2005 and 2000
1608 // consistently return NULL as the result.
1609 // However, if instead we use the optimized LIKE translation when the search param is a DbNullExpression,
1610 // only SQL Server 2005 yields a True instead of a DbNull as compared to SQL Server 2008 and 2000. This is
1612 result.Append("CHARINDEX( ");
1613 result.Append(args[1].Accept(sqlgen));
1614 result.Append(", ");
1615 result.Append(args[0].Accept(sqlgen));
1616 result.Append(") = 1");
1623 /// Handler for EndsWith. Wraps the normal translation with a case statement
1625 /// <param name="sqlgen"></param>
1626 /// <param name="e"></param>
1627 /// <returns></returns>
1628 private static ISqlFragment HandleCanonicalFunctionEndsWith(SqlGenerator sqlgen, DbFunctionExpression e)
1630 return WrapPredicate(HandleCanonicalFunctionEndsWith, sqlgen, e);
1634 /// ENDSWITH(arg0, arg1) => arg0 LIKE '%arg1'
1636 /// <param name="sqlgen"></param>
1637 /// <param name="args"></param>
1638 /// <param name="result"></param>
1639 /// <returns></returns>
1640 private static SqlBuilder HandleCanonicalFunctionEndsWith(SqlGenerator sqlgen, IList<DbExpression> args, SqlBuilder result)
1642 Debug.Assert(args.Count == 2, "EndsWith should have two arguments");
1644 // Check if args[1] is a DbConstantExpression and if args [0] is a DbPropertyExpression
1645 DbConstantExpression constSearchParamExpression = args[1] as DbConstantExpression;
1646 DbPropertyExpression targetParamExpression = args[0] as DbPropertyExpression;
1647 if ((constSearchParamExpression != null) && (targetParamExpression != null) && (string.IsNullOrEmpty(constSearchParamExpression.Value as string) == false))
1649 // The LIKE optimization for EndsWith can only be used when the target is a column in table and
1650 // the search string is a constant. This is because SQL Server ignores a trailing space in a query like:
1651 // EndsWith('abcd ', 'cd'), which translates to:
1653 // CASE WHEN ('abcd ' LIKE '%cd') THEN cast(1 as bit) WHEN ( NOT ('abcd ' LIKE '%cd')) THEN cast(0 as bit) END AS [C1]
1654 // FROM ( SELECT 1 AS X ) AS [SingleRowTable1]
1655 // and "incorrectly" returns 1 (true), but the CLR would expect a 0 (false) back.
1657 TranslateConstantParameterForLike(sqlgen, args[0], constSearchParamExpression, result, true, false);
1661 result.Append("CHARINDEX( REVERSE(");
1662 result.Append(args[1].Accept(sqlgen));
1663 result.Append("), REVERSE(");
1664 result.Append(args[0].Accept(sqlgen));
1665 result.Append(")) = 1");
1671 /// Turns a predicate into a statement returning a bit
1672 /// PREDICATE => CASE WHEN (PREDICATE) THEN CAST(1 AS BIT) WHEN (NOT (PREDICATE)) CAST (O AS BIT) END
1673 /// The predicate is produced by the given predicateTranslator.
1675 /// <param name="predicateTranslator"></param>
1676 /// <param name="e"></param>
1677 /// <returns></returns>
1678 private static ISqlFragment WrapPredicate(Func<SqlGenerator, IList<DbExpression>, SqlBuilder, SqlBuilder> predicateTranslator, SqlGenerator sqlgen, DbFunctionExpression e)
1680 SqlBuilder result = new SqlBuilder();
1681 result.Append("CASE WHEN (");
1682 predicateTranslator(sqlgen, e.Arguments, result);
1683 result.Append(") THEN cast(1 as bit) WHEN ( NOT (");
1684 predicateTranslator(sqlgen, e.Arguments, result);
1685 result.Append(")) THEN cast(0 as bit) END");
1690 /// Writes the function name to the given SqlBuilder.
1692 /// <param name="function"></param>
1693 /// <param name="result"></param>
1694 internal static void WriteFunctionName(SqlBuilder result, EdmFunction function)
1696 string storeFunctionName;
1698 if (null != function.StoreFunctionNameAttribute)
1700 storeFunctionName = function.StoreFunctionNameAttribute;
1704 storeFunctionName = function.Name;
1707 // If the function is a builtin (i.e. the BuiltIn attribute has been
1708 // specified, both store and canonical functions have this attribute),
1709 // then the function name should not be quoted;
1710 // additionally, no namespace should be used.
1711 if (TypeHelpers.IsCanonicalFunction(function))
1713 result.Append(storeFunctionName.ToUpperInvariant());
1715 else if (IsStoreFunction(function))
1717 result.Append(storeFunctionName);
1721 // Should we actually support this?
1722 if (String.IsNullOrEmpty(function.Schema))
1724 result.Append(SqlGenerator.QuoteIdentifier(function.NamespaceName));
1728 result.Append(SqlGenerator.QuoteIdentifier(function.Schema));
1731 result.Append(SqlGenerator.QuoteIdentifier(storeFunctionName));
1737 /// Is this a Store function (ie) does it have the builtinAttribute specified and it is not a canonical function?
1739 /// <param name="function"></param>
1740 /// <returns></returns>
1741 internal static bool IsStoreFunction(EdmFunction function)
1743 return function.BuiltInAttribute && !TypeHelpers.IsCanonicalFunction(function);
1747 /// determines if the function requires the return type be enforeced by use of a cast expression
1749 /// <param name="e"></param>
1750 /// <returns></returns>
1751 private static bool CastReturnTypeToInt64(DbFunctionExpression e)
1753 return CastReturnTypeToGivenType(e, _functionRequiresReturnTypeCastToInt64, PrimitiveTypeKind.Int64);
1757 /// determines if the function requires the return type be enforeced by use of a cast expression
1759 /// <param name="e"></param>
1760 /// <returns></returns>
1761 private static bool CastReturnTypeToInt32(SqlGenerator sqlgen, DbFunctionExpression e)
1763 if (!_functionRequiresReturnTypeCastToInt32.Contains(e.Function.FullName))
1768 for (int i = 0; i < e.Arguments.Count; i++)
1770 TypeUsage storeType = sqlgen.StoreItemCollection.StoreProviderManifest.GetStoreType(e.Arguments[i].ResultType);
1771 if (_maxTypeNames.Contains(storeType.EdmType.Name))
1780 /// determines if the function requires the return type be enforeced by use of a cast expression
1782 /// <param name="e"></param>
1783 /// <returns></returns>
1784 private static bool CastReturnTypeToInt16(DbFunctionExpression e)
1786 return CastReturnTypeToGivenType(e, _functionRequiresReturnTypeCastToInt16, PrimitiveTypeKind.Int16);
1790 /// determines if the function requires the return type be enforeced by use of a cast expression
1792 /// <param name="e"></param>
1793 /// <returns></returns>
1794 private static bool CastReturnTypeToSingle(DbFunctionExpression e)
1796 //Do not add the cast for the Round() overload having 2 arguments.
1797 //Round(Single,Int32) maps to Round(Double,Int32)due to implicit casting.
1798 //We don't need to cast in that case, since we expect a Double as return type there anyways.
1799 return CastReturnTypeToGivenType(e, _functionRequiresReturnTypeCastToSingle, PrimitiveTypeKind.Single);
1803 /// Determines if the function requires the return type be enforced by use of a cast expression
1805 /// <param name="e"></param>
1806 /// <param name="functionsRequiringReturnTypeCast"></param>
1807 /// <param name="type"></param>
1808 /// <returns></returns>
1809 private static bool CastReturnTypeToGivenType(DbFunctionExpression e, Set<string> functionsRequiringReturnTypeCast, PrimitiveTypeKind type)
1811 if (!functionsRequiringReturnTypeCast.Contains(e.Function.FullName))
1816 for (int i = 0; i < e.Arguments.Count; i++)
1818 if (TypeSemantics.IsPrimitiveType(e.Arguments[i].ResultType, type))