Update Reference Sources to .NET Framework 4.6.1
[mono.git] / mcs / class / referencesource / System.Data.Entity / System / Data / SqlClient / SqlProviderManifest.cs
1 //---------------------------------------------------------------------
2 // <copyright file="SqlProviderManifest.cs" company="Microsoft">
3 //      Copyright (c) Microsoft Corporation.  All rights reserved.
4 // </copyright>
5 //
6 // @owner  [....]
7 // @backupOwner [....]
8 //---------------------------------------------------------------------
9
10 using System.Collections.Generic;
11 using System.Data.Common;
12 using System.Data.Entity;
13 using System.Data.Metadata.Edm;
14 using System.Diagnostics;
15 using System.Linq;
16 using System.Text;
17 using System.Xml;
18
19 namespace System.Data.SqlClient
20 {
21     /// <summary>
22     /// The Provider Manifest for SQL Server
23     /// </summary>
24     internal class SqlProviderManifest : DbXmlEnabledProviderManifest
25     {
26         internal const string TokenSql8 = "2000";
27         internal const string TokenSql9 = "2005";
28         internal const string TokenSql10 = "2008";
29
30         // '~' is the same escape character that L2S uses
31         internal const char LikeEscapeChar = '~';
32         internal const string LikeEscapeCharToString = "~";
33
34         #region Private Fields
35
36         // Default to SQL Server 2005 (9.0)
37         private SqlVersion _version = SqlVersion.Sql9;
38
39         /// <summary>
40         /// maximum size of sql server unicode 
41         /// </summary>
42         private const int varcharMaxSize = 8000;
43         private const int nvarcharMaxSize = 4000;
44         private const int binaryMaxSize = 8000;
45
46         private System.Collections.ObjectModel.ReadOnlyCollection<PrimitiveType> _primitiveTypes = null;
47         private System.Collections.ObjectModel.ReadOnlyCollection<EdmFunction> _functions = null;
48
49         #endregion
50
51         #region Constructors
52                
53         /// <summary>
54         /// Constructor
55         /// </summary>
56         /// <param name="manifestToken">A token used to infer the capabilities of the store</param>
57         public SqlProviderManifest(string manifestToken)
58             : base(SqlProviderManifest.GetProviderManifest())
59         {
60             // GetSqlVersion will throw ArgumentException if manifestToken is null, empty, or not recognized.
61             _version = SqlVersionUtils.GetSqlVersion(manifestToken);
62         }
63
64
65         #endregion
66
67         #region Properties
68
69         internal SqlVersion SqlVersion
70         {
71             get { return this._version; }
72         }
73
74         #endregion
75
76         #region Private Methods
77         private static XmlReader GetProviderManifest()
78         {
79             return DbProviderServices.GetXmlResource("System.Data.Resources.SqlClient.SqlProviderServices.ProviderManifest.xml");
80         }
81
82         private XmlReader GetStoreSchemaMapping(string mslName)
83         {
84             return DbProviderServices.GetXmlResource("System.Data.Resources.SqlClient.SqlProviderServices." + mslName + ".msl");
85         }
86
87         private XmlReader GetStoreSchemaDescription(string ssdlName)
88         {
89             if (this._version == SqlVersion.Sql8)
90             {
91                 return DbProviderServices.GetXmlResource("System.Data.Resources.SqlClient.SqlProviderServices." + ssdlName + "_Sql8.ssdl");
92             }
93
94             return DbProviderServices.GetXmlResource("System.Data.Resources.SqlClient.SqlProviderServices." + ssdlName + ".ssdl");
95         }
96         #endregion 
97
98         #region Internal Methods
99
100         /// <summary>
101         /// Function to detect wildcard characters %, _, [ and ^ and escape them with a preceding ~
102         /// This escaping is used when StartsWith, EndsWith and Contains canonical and CLR functions
103         /// are translated to their equivalent LIKE expression
104         /// NOTE: This code has been copied from LinqToSql
105         /// </summary>
106         /// <param name="text">Original input as specified by the user</param>
107         /// <param name="alwaysEscapeEscapeChar">escape the escape character ~ regardless whether wildcard 
108         /// characters were encountered </param>
109         /// <param name="usedEscapeChar">true if the escaping was performed, false if no escaping was required</param>
110         /// <returns>The escaped string that can be used as pattern in a LIKE expression</returns>
111         internal static string EscapeLikeText(string text, bool alwaysEscapeEscapeChar, out bool usedEscapeChar)
112         {
113             usedEscapeChar = false;
114             if (!(text.Contains("%") || text.Contains("_") || text.Contains("[")
115                 || text.Contains("^") || alwaysEscapeEscapeChar && text.Contains(LikeEscapeCharToString)))
116             {
117                 return text;
118             }
119             StringBuilder sb = new StringBuilder(text.Length);
120             foreach (char c in text)
121             {
122                 if (c == '%' || c == '_' || c == '[' || c == '^' || c == LikeEscapeChar)
123                 {
124                     sb.Append(LikeEscapeChar);
125                     usedEscapeChar = true;
126                 }
127                 sb.Append(c);
128             }
129             return sb.ToString();
130         }
131         #endregion
132
133         #region Overrides
134         /// <summary>
135         /// Providers should override this to return information specific to their provider.  
136         /// 
137         /// This method should never return null.
138         /// </summary>
139         /// <param name="informationType">The name of the information to be retrieved.</param>
140         /// <returns>An XmlReader at the begining of the information requested.</returns>
141         protected override XmlReader GetDbInformation(string informationType)
142         {
143             if (informationType == DbProviderManifest.StoreSchemaDefinitionVersion3 ||
144                 informationType == DbProviderManifest.StoreSchemaDefinition)
145             {
146                 return GetStoreSchemaDescription(informationType);
147             }
148
149             if (informationType == DbProviderManifest.StoreSchemaMappingVersion3 ||
150                 informationType == DbProviderManifest.StoreSchemaMapping)
151             {
152                 return GetStoreSchemaMapping(informationType);
153             }
154
155             // Use default Conceptual Schema Definition
156             if (informationType == DbProviderManifest.ConceptualSchemaDefinitionVersion3 ||
157                 informationType == DbProviderManifest.ConceptualSchemaDefinition)
158             {
159                 return null;
160             }
161
162             throw EntityUtil.ProviderIncompatible(System.Data.Entity.Strings.ProviderReturnedNullForGetDbInformation(informationType));
163         }
164
165         [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase")]
166         public override System.Collections.ObjectModel.ReadOnlyCollection<PrimitiveType> GetStoreTypes()
167         {
168             if (this._primitiveTypes == null)
169             {
170                 if (this._version == SqlVersion.Sql10)
171                 {
172                     this._primitiveTypes = base.GetStoreTypes();
173                 }
174                 else
175                 {
176                     List<PrimitiveType> primitiveTypes = new List<PrimitiveType>(base.GetStoreTypes());
177                     Debug.Assert((this._version == SqlVersion.Sql8) || (this._version == SqlVersion.Sql9), "Found verion other than Sql 8, 9 or 10");
178                     //Remove the Katmai types for both Sql8 and Sql9
179                     primitiveTypes.RemoveAll(new Predicate<PrimitiveType>(
180                                                         delegate(PrimitiveType primitiveType)
181                                                         {
182                                                             string name = primitiveType.Name.ToLowerInvariant();
183                                                             return name.Equals("time", StringComparison.Ordinal) || 
184                                                                    name.Equals("date", StringComparison.Ordinal) || 
185                                                                    name.Equals("datetime2", StringComparison.Ordinal) || 
186                                                                    name.Equals("datetimeoffset", StringComparison.Ordinal) ||
187                                                                    name.Equals("geography", StringComparison.Ordinal) || 
188                                                                    name.Equals("geometry", StringComparison.Ordinal);
189                                                         }
190                                                     )
191                                         );
192                     //Remove the types that won't work in Sql8
193                     if (this._version == SqlVersion.Sql8)                    {
194                         
195                         // SQLBUDT 550667 and 551271: Remove xml and 'max' types for SQL Server 2000
196                         primitiveTypes.RemoveAll(new Predicate<PrimitiveType>(
197                                                             delegate(PrimitiveType primitiveType)
198                                                             {
199                                                                 string name = primitiveType.Name.ToLowerInvariant();
200                                                                 return name.Equals("xml", StringComparison.Ordinal) || name.EndsWith("(max)", StringComparison.Ordinal);
201                                                             }
202                                                         )
203                                             );                        
204                     }
205                     this._primitiveTypes = primitiveTypes.AsReadOnly();
206                 }
207             }
208
209             return this._primitiveTypes;
210         }
211
212         public override System.Collections.ObjectModel.ReadOnlyCollection<EdmFunction> GetStoreFunctions()
213         {
214             if (this._functions == null)
215             {
216                 if (this._version == SqlVersion.Sql10)
217                 {
218                     this._functions = base.GetStoreFunctions();
219                 }
220                 else
221                 {
222                     //Remove the functions over katmai types from both Sql 9 and Sql 8.
223                     IEnumerable<EdmFunction> functions = base.GetStoreFunctions().Where(f => !IsKatmaiOrNewer(f));
224                     if(this._version == SqlVersion.Sql8)
225                     {
226                         // SQLBUDT 550998: Remove unsupported overloads from Provider Manifest on SQL 8.0
227                         functions = functions.Where(f => !IsYukonOrNewer(f));      
228                     }
229                     this._functions = functions.ToList().AsReadOnly();
230                 }
231             }
232
233             return this._functions;
234         }
235
236         private static bool IsKatmaiOrNewer(EdmFunction edmFunction)
237         {
238             // Spatial types are only supported from Katmai onward; any functions using them must therefore also be Katmai or newer.
239             if ((edmFunction.ReturnParameter != null && Helper.IsSpatialType(edmFunction.ReturnParameter.TypeUsage)) ||
240                 edmFunction.Parameters.Any(p => Helper.IsSpatialType(p.TypeUsage)))
241             {
242                 return true;
243             }
244
245             ReadOnlyMetadataCollection<FunctionParameter> funParams = edmFunction.Parameters;
246             switch (edmFunction.Name.ToUpperInvariant())
247             {
248                 case "COUNT":
249                 case "COUNT_BIG":
250                 case "MAX":
251                 case "MIN":
252                     {
253                         string name = ((CollectionType)funParams[0].TypeUsage.EdmType).TypeUsage.EdmType.Name;
254                         return ((name.Equals("DateTimeOffset", StringComparison.OrdinalIgnoreCase)) ||
255                                (name.Equals("Time", StringComparison.OrdinalIgnoreCase)));
256
257                     }
258                 case "DAY":
259                 case "MONTH":
260                 case "YEAR":
261                 case "DATALENGTH":
262                 case "CHECKSUM":
263                     {
264                         string name = funParams[0].TypeUsage.EdmType.Name;
265                         return ((name.Equals("DateTimeOffset", StringComparison.OrdinalIgnoreCase)) ||
266                                (name.Equals("Time", StringComparison.OrdinalIgnoreCase)));
267
268                     }
269                 case "DATEADD":
270                 case "DATEDIFF":
271                     {
272                         string param1Name = funParams[1].TypeUsage.EdmType.Name;
273                         string param2Name = funParams[2].TypeUsage.EdmType.Name;
274                         return ((param1Name.Equals("Time", StringComparison.OrdinalIgnoreCase)) ||
275                                (param2Name.Equals("Time", StringComparison.OrdinalIgnoreCase)) ||
276                                (param1Name.Equals("DateTimeOffset", StringComparison.OrdinalIgnoreCase)) ||
277                                (param2Name.Equals("DateTimeOffset", StringComparison.OrdinalIgnoreCase)));
278                     }
279                 case "DATENAME":
280                 case "DATEPART":
281                     {
282                         string name = funParams[1].TypeUsage.EdmType.Name;
283                         return ((name.Equals("DateTimeOffset", StringComparison.OrdinalIgnoreCase)) ||
284                                (name.Equals("Time", StringComparison.OrdinalIgnoreCase)));
285                     }
286                 case "SYSUTCDATETIME":
287                 case "SYSDATETIME":
288                 case "SYSDATETIMEOFFSET":
289                     return true;
290                 default:
291                     break;
292             }
293
294             return false;
295         }
296
297         private static bool IsYukonOrNewer(EdmFunction edmFunction)
298         {
299             ReadOnlyMetadataCollection<FunctionParameter> funParams = edmFunction.Parameters;
300             if (funParams == null || funParams.Count == 0)
301             {
302                 return false;
303             }
304
305             switch (edmFunction.Name.ToUpperInvariant())
306             {
307                 case "COUNT":
308                 case "COUNT_BIG":
309                     {
310                         string name = ((CollectionType)funParams[0].TypeUsage.EdmType).TypeUsage.EdmType.Name;
311                         return name.Equals("Guid", StringComparison.OrdinalIgnoreCase);
312                     }
313
314                 case "CHARINDEX":
315                     {
316                         foreach (FunctionParameter funParam in funParams)
317                         {
318                             if (funParam.TypeUsage.EdmType.Name.Equals("Int64", StringComparison.OrdinalIgnoreCase))
319                             {
320                                 return true;
321                             }
322                         }
323                     }
324                     break;
325
326                 default:
327                     break;
328             }
329
330             return false;
331         }
332
333         /// <summary>
334         /// This method takes a type and a set of facets and returns the best mapped equivalent type 
335         /// in EDM.
336         /// </summary>
337         /// <param name="storeType">A TypeUsage encapsulating a store type and a set of facets</param>
338         /// <returns>A TypeUsage encapsulating an EDM type and a set of facets</returns>
339         [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase")]
340         public override TypeUsage GetEdmType(TypeUsage storeType)
341         {
342             EntityUtil.CheckArgumentNull<TypeUsage>(storeType, "storeType");
343
344             string storeTypeName = storeType.EdmType.Name.ToLowerInvariant();
345             if (!base.StoreTypeNameToEdmPrimitiveType.ContainsKey(storeTypeName))
346             {
347                 throw EntityUtil.Argument(Strings.ProviderDoesNotSupportType(storeTypeName));
348             }
349
350             PrimitiveType edmPrimitiveType = base.StoreTypeNameToEdmPrimitiveType[storeTypeName];
351
352             int maxLength = 0;
353             bool isUnicode = true;
354             bool isFixedLen = false;
355             bool isUnbounded = true;
356
357             PrimitiveTypeKind newPrimitiveTypeKind;
358
359             switch (storeTypeName)
360             {
361                 // for some types we just go with simple type usage with no facets
362                 case "tinyint":
363                 case "smallint":
364                 case "bigint":
365                 case "bit":
366                 case "uniqueidentifier":
367                 case "int":
368                 case "geography":
369                 case "geometry":
370                     return TypeUsage.CreateDefaultTypeUsage(edmPrimitiveType);
371
372                 case "varchar":
373                     newPrimitiveTypeKind = PrimitiveTypeKind.String;
374                     isUnbounded = !TypeHelpers.TryGetMaxLength(storeType, out maxLength);
375                     isUnicode = false;
376                     isFixedLen = false;
377                     break;
378
379                 case "char":
380                     newPrimitiveTypeKind = PrimitiveTypeKind.String;
381                     isUnbounded = !TypeHelpers.TryGetMaxLength(storeType, out maxLength);
382                     isUnicode = false;
383                     isFixedLen = true;
384                     break;
385
386                 case "nvarchar":
387                     newPrimitiveTypeKind = PrimitiveTypeKind.String;
388                     isUnbounded = !TypeHelpers.TryGetMaxLength(storeType, out maxLength);
389                     isUnicode = true;
390                     isFixedLen = false;
391                     break;
392
393                 case "nchar":
394                     newPrimitiveTypeKind = PrimitiveTypeKind.String;
395                     isUnbounded = !TypeHelpers.TryGetMaxLength(storeType, out maxLength);
396                     isUnicode = true;
397                     isFixedLen = true;
398                     break;
399
400                 case "varchar(max)":
401                 case "text":
402                     newPrimitiveTypeKind = PrimitiveTypeKind.String;
403                     isUnbounded = true;
404                     isUnicode = false;
405                     isFixedLen = false;
406                     break;
407
408                 case "nvarchar(max)":
409                 case "ntext":
410                 case "xml":
411                     newPrimitiveTypeKind = PrimitiveTypeKind.String;
412                     isUnbounded = true;
413                     isUnicode = true;
414                     isFixedLen = false;
415                     break;
416
417                 case "binary":
418                     newPrimitiveTypeKind = PrimitiveTypeKind.Binary;
419                     isUnbounded = !TypeHelpers.TryGetMaxLength(storeType, out maxLength);
420                     isFixedLen = true;
421                     break;
422
423                 case "varbinary":
424                     newPrimitiveTypeKind = PrimitiveTypeKind.Binary;
425                     isUnbounded = !TypeHelpers.TryGetMaxLength(storeType, out maxLength);
426                     isFixedLen = false;
427                     break;
428
429                 case "varbinary(max)":
430                 case "image":
431                     newPrimitiveTypeKind = PrimitiveTypeKind.Binary;
432                     isUnbounded = true;
433                     isFixedLen = false;
434                     break;
435
436                 case "timestamp":
437                 case "rowversion":
438                     return TypeUsage.CreateBinaryTypeUsage(edmPrimitiveType, true, 8);
439
440                 case "float":
441                 case "real":
442                     return TypeUsage.CreateDefaultTypeUsage(edmPrimitiveType);
443
444                 case "decimal":
445                 case "numeric":
446                     {
447                         byte precision;
448                         byte scale;
449                         if (TypeHelpers.TryGetPrecision(storeType, out precision) && TypeHelpers.TryGetScale(storeType, out scale))
450                         {
451                             return TypeUsage.CreateDecimalTypeUsage(edmPrimitiveType, precision, scale);
452                         }
453                         else
454                         {
455                             return TypeUsage.CreateDecimalTypeUsage(edmPrimitiveType);
456                         }
457                     }
458
459                 case "money":
460                     return TypeUsage.CreateDecimalTypeUsage(edmPrimitiveType, 19, 4);
461
462                 case "smallmoney":
463                     return TypeUsage.CreateDecimalTypeUsage(edmPrimitiveType, 10, 4);
464
465                 case "datetime":
466                 case "datetime2":
467                 case "smalldatetime":
468                     return TypeUsage.CreateDateTimeTypeUsage(edmPrimitiveType, null);
469                 case "date":
470                     return TypeUsage.CreateDefaultTypeUsage(edmPrimitiveType);
471                 case "time":
472                     return TypeUsage.CreateTimeTypeUsage(edmPrimitiveType, null);
473                 case "datetimeoffset":
474                     return TypeUsage.CreateDateTimeOffsetTypeUsage(edmPrimitiveType, null);
475
476                 default:
477                     throw EntityUtil.NotSupported(Strings.ProviderDoesNotSupportType(storeTypeName));
478             }
479
480             Debug.Assert(newPrimitiveTypeKind == PrimitiveTypeKind.String || newPrimitiveTypeKind == PrimitiveTypeKind.Binary, "at this point only string and binary types should be present");
481
482             switch(newPrimitiveTypeKind)
483             {
484                 case PrimitiveTypeKind.String:
485                     if (!isUnbounded)
486                     {
487                         return TypeUsage.CreateStringTypeUsage(edmPrimitiveType, isUnicode, isFixedLen, maxLength);
488                     }
489                     else
490                     {
491                         return TypeUsage.CreateStringTypeUsage(edmPrimitiveType, isUnicode, isFixedLen);
492                     }
493                 case PrimitiveTypeKind.Binary:
494                     if (!isUnbounded)
495                     {
496                         return TypeUsage.CreateBinaryTypeUsage(edmPrimitiveType, isFixedLen, maxLength);
497                     }
498                     else
499                     {
500                         return TypeUsage.CreateBinaryTypeUsage(edmPrimitiveType, isFixedLen);
501                     }
502                 default:
503                     throw EntityUtil.NotSupported(Strings.ProviderDoesNotSupportType(storeTypeName));
504             }
505         }
506
507         /// <summary>
508         /// This method takes a type and a set of facets and returns the best mapped equivalent type 
509         /// in SQL Server, taking the store version into consideration.
510         /// </summary>
511         /// <param name="storeType">A TypeUsage encapsulating an EDM type and a set of facets</param>
512         /// <returns>A TypeUsage encapsulating a store type and a set of facets</returns>
513         public override TypeUsage GetStoreType(TypeUsage edmType)
514         {
515             EntityUtil.CheckArgumentNull<TypeUsage>(edmType, "edmType");
516             System.Diagnostics.Debug.Assert(edmType.EdmType.BuiltInTypeKind == BuiltInTypeKind.PrimitiveType);
517
518             PrimitiveType primitiveType = edmType.EdmType as PrimitiveType;
519             if (primitiveType == null)
520             {
521                 throw EntityUtil.Argument(Strings.ProviderDoesNotSupportType(edmType.Identity));
522             }
523
524             ReadOnlyMetadataCollection<Facet> facets = edmType.Facets;
525
526             switch (primitiveType.PrimitiveTypeKind)
527             {
528                 case PrimitiveTypeKind.Boolean:
529                     return TypeUsage.CreateDefaultTypeUsage(StoreTypeNameToStorePrimitiveType["bit"]);
530
531                 case PrimitiveTypeKind.Byte:
532                     return TypeUsage.CreateDefaultTypeUsage(StoreTypeNameToStorePrimitiveType["tinyint"]);
533
534                 case PrimitiveTypeKind.Int16:
535                     return TypeUsage.CreateDefaultTypeUsage(StoreTypeNameToStorePrimitiveType["smallint"]);
536
537                 case PrimitiveTypeKind.Int32:
538                     return TypeUsage.CreateDefaultTypeUsage(StoreTypeNameToStorePrimitiveType["int"]);
539
540                 case PrimitiveTypeKind.Int64:
541                     return TypeUsage.CreateDefaultTypeUsage(StoreTypeNameToStorePrimitiveType["bigint"]);
542
543                 case PrimitiveTypeKind.Geography:
544                 case PrimitiveTypeKind.GeographyPoint:
545                 case PrimitiveTypeKind.GeographyLineString:
546                 case PrimitiveTypeKind.GeographyPolygon:
547                 case PrimitiveTypeKind.GeographyMultiPoint:
548                 case PrimitiveTypeKind.GeographyMultiLineString:
549                 case PrimitiveTypeKind.GeographyMultiPolygon:
550                 case PrimitiveTypeKind.GeographyCollection:
551                     return GetStorePrimitiveTypeIfPostSql9("geography", edmType.Identity, primitiveType.PrimitiveTypeKind);
552
553                 case PrimitiveTypeKind.Geometry:
554                 case PrimitiveTypeKind.GeometryPoint:
555                 case PrimitiveTypeKind.GeometryLineString:
556                 case PrimitiveTypeKind.GeometryPolygon:
557                 case PrimitiveTypeKind.GeometryMultiPoint:
558                 case PrimitiveTypeKind.GeometryMultiLineString:
559                 case PrimitiveTypeKind.GeometryMultiPolygon:
560                 case PrimitiveTypeKind.GeometryCollection:
561                     return GetStorePrimitiveTypeIfPostSql9("geometry", edmType.Identity, primitiveType.PrimitiveTypeKind);
562
563                 case PrimitiveTypeKind.Guid:
564                     return TypeUsage.CreateDefaultTypeUsage(StoreTypeNameToStorePrimitiveType["uniqueidentifier"]);
565
566                 case PrimitiveTypeKind.Double:
567                     return TypeUsage.CreateDefaultTypeUsage(StoreTypeNameToStorePrimitiveType["float"]);
568
569                 case PrimitiveTypeKind.Single:
570                     return TypeUsage.CreateDefaultTypeUsage(StoreTypeNameToStorePrimitiveType["real"]);
571
572                 case PrimitiveTypeKind.Decimal: // decimal, numeric, smallmoney, money
573                     {
574                         byte precision;
575                         if (!TypeHelpers.TryGetPrecision(edmType, out precision))
576                         {
577                             precision = 18;
578                         }
579
580                         byte scale;
581                         if (!TypeHelpers.TryGetScale(edmType, out scale))
582                         {
583                             scale = 0;
584                         }
585                         TypeUsage tu = TypeUsage.CreateDecimalTypeUsage(StoreTypeNameToStorePrimitiveType["decimal"], precision, scale);
586                         return tu;
587                     }
588
589                 case PrimitiveTypeKind.Binary: // binary, varbinary, varbinary(max), image, timestamp, rowversion
590                     {
591                         bool isFixedLength = null != facets[DbProviderManifest.FixedLengthFacetName].Value && (bool)facets[DbProviderManifest.FixedLengthFacetName].Value;                     
592                         Facet f = facets[DbProviderManifest.MaxLengthFacetName];
593                         bool isMaxLength = Helper.IsUnboundedFacetValue(f) || null == f.Value || (int)f.Value > binaryMaxSize;
594                         int maxLength = !isMaxLength ? (int)f.Value : Int32.MinValue;
595
596                         TypeUsage tu;
597                         if (isFixedLength)
598                         {
599                             tu = TypeUsage.CreateBinaryTypeUsage(StoreTypeNameToStorePrimitiveType["binary"], true, (isMaxLength ? binaryMaxSize : maxLength));
600                         }
601                         else
602                         {
603                             if (isMaxLength)
604                             {
605                                 if (_version != SqlVersion.Sql8)
606                                 {
607
608                                     tu = TypeUsage.CreateBinaryTypeUsage(StoreTypeNameToStorePrimitiveType["varbinary(max)"], false);
609                                     Debug.Assert(tu.Facets[DbProviderManifest.MaxLengthFacetName].Description.IsConstant, "varbinary(max) is not constant!");
610                                 }
611                                 else
612                                 {
613                                     tu = TypeUsage.CreateBinaryTypeUsage(StoreTypeNameToStorePrimitiveType["varbinary"], false, binaryMaxSize);
614                                 }
615                             }
616                             else
617                             {
618                                 tu = TypeUsage.CreateBinaryTypeUsage(StoreTypeNameToStorePrimitiveType["varbinary"], false, maxLength);
619                             }
620                         }
621                         return tu;
622                     }
623
624                 case PrimitiveTypeKind.String:
625                     // char, nchar, varchar, nvarchar, varchar(max), nvarchar(max), ntext, text, xml
626                     {
627                         bool isUnicode = null == facets[DbProviderManifest.UnicodeFacetName].Value || (bool)facets[DbProviderManifest.UnicodeFacetName].Value;
628                         bool isFixedLength = null != facets[DbProviderManifest.FixedLengthFacetName].Value && (bool)facets[DbProviderManifest.FixedLengthFacetName].Value;
629                         Facet f = facets[DbProviderManifest.MaxLengthFacetName];
630                         // maxlen is true if facet value is unbounded, the value is bigger than the limited string sizes *or* the facet
631                         // value is null. this is needed since functions still have maxlength facet value as null
632                         bool isMaxLength = Helper.IsUnboundedFacetValue(f) || null == f.Value || (int)f.Value > (isUnicode ? nvarcharMaxSize : varcharMaxSize);
633                         int maxLength = !isMaxLength ? (int)f.Value : Int32.MinValue;
634                         
635                         TypeUsage tu;
636
637                         if (isUnicode)
638                         {
639                             if (isFixedLength)
640                             {
641                                 tu = TypeUsage.CreateStringTypeUsage(StoreTypeNameToStorePrimitiveType["nchar"], true, true, (isMaxLength ? nvarcharMaxSize : maxLength));
642                             }
643                             else
644                             {
645                                 if (isMaxLength)
646                                 {
647                                     // nvarchar(max) (SQL 9) or ntext (SQL 8)
648                                     if (_version != SqlVersion.Sql8)
649                                     {
650                                         tu = TypeUsage.CreateStringTypeUsage(StoreTypeNameToStorePrimitiveType["nvarchar(max)"], true, false);
651                                         Debug.Assert(tu.Facets[DbProviderManifest.MaxLengthFacetName].Description.IsConstant, "NVarchar(max) is not constant!");
652                                     }
653                                     else
654                                     {   
655                                         // if it is unknown, fallback to nvarchar[4000] instead of ntext since it has limited store semantics
656                                         tu = TypeUsage.CreateStringTypeUsage(StoreTypeNameToStorePrimitiveType["nvarchar"], true, false, nvarcharMaxSize);
657                                     }
658                                 }
659                                 else
660                                 {
661                                     tu = TypeUsage.CreateStringTypeUsage(StoreTypeNameToStorePrimitiveType["nvarchar"], true, false, maxLength);
662                                 }
663                             }
664                         }
665                         else    // !isUnicode
666                         {
667                             if (isFixedLength)
668                             {
669                                 tu = TypeUsage.CreateStringTypeUsage(StoreTypeNameToStorePrimitiveType["char"], false, true,
670                                     (isMaxLength ? varcharMaxSize : maxLength));
671                             }
672                             else
673                             {
674                                 if (isMaxLength)
675                                 {
676                                     // nvarchar(max) (SQL 9) or ntext (SQL 8)
677                                     if (_version != SqlVersion.Sql8)
678                                     {
679                                         tu = TypeUsage.CreateStringTypeUsage(StoreTypeNameToStorePrimitiveType["varchar(max)"], false, false);
680                                         Debug.Assert(tu.Facets[DbProviderManifest.MaxLengthFacetName].Description.IsConstant, "varchar(max) is not constant!");
681                                     }
682                                     else
683                                     {
684                                         // if it is unknown, fallback to varchar[8000] instead of text since it has limited store semantics
685                                         tu = TypeUsage.CreateStringTypeUsage(StoreTypeNameToStorePrimitiveType["varchar"], false, false, varcharMaxSize);
686                                     }
687                                 }
688                                 else
689                                 {
690                                     tu = TypeUsage.CreateStringTypeUsage(StoreTypeNameToStorePrimitiveType["varchar"], false, false, maxLength);
691                                 }
692                             }
693                         }
694                         return tu;
695                     }
696
697
698                 case PrimitiveTypeKind.DateTime:
699                     return TypeUsage.CreateDefaultTypeUsage(StoreTypeNameToStorePrimitiveType["datetime"]);
700                 case PrimitiveTypeKind.DateTimeOffset:
701                     return GetStorePrimitiveTypeIfPostSql9("datetimeoffset", edmType.Identity, primitiveType.PrimitiveTypeKind); 
702                 case PrimitiveTypeKind.Time:
703                     return GetStorePrimitiveTypeIfPostSql9("time", edmType.Identity, primitiveType.PrimitiveTypeKind);
704                  
705                 default:
706                     throw EntityUtil.NotSupported(Strings.NoStoreTypeForEdmType(edmType.Identity, primitiveType.PrimitiveTypeKind));
707             }
708         }
709
710         private TypeUsage GetStorePrimitiveTypeIfPostSql9(string storeTypeName, string edmTypeIdentity, PrimitiveTypeKind primitiveTypeKind)
711         {
712             if ((this.SqlVersion != SqlVersion.Sql8) && (this.SqlVersion != SqlVersion.Sql9))
713             {
714                 return TypeUsage.CreateDefaultTypeUsage(StoreTypeNameToStorePrimitiveType[storeTypeName]);
715             }
716             else
717             {
718                 throw EntityUtil.NotSupported(Strings.NoStoreTypeForEdmType(edmTypeIdentity, primitiveTypeKind));
719             }
720         }
721
722         /// <summary>
723         /// Returns true, SqlClient supports escaping strings to be used as arguments to like
724         /// The escape character is '~'
725         /// </summary>
726         /// <param name="escapeCharacter">The character '~'</param>
727         /// <returns>True</returns>
728         public override bool SupportsEscapingLikeArgument(out char escapeCharacter)
729         {
730             escapeCharacter = SqlProviderManifest.LikeEscapeChar;
731             return true;
732         }
733
734         /// <summary>
735         /// Escapes the wildcard characters and the escape character in the given argument.
736         /// </summary>
737         /// <param name="argument"></param>
738         /// <returns>Equivalent to the argument, with the wildcard characters and the escape character escaped</returns>
739         public override string EscapeLikeArgument(string argument)
740         {
741             EntityUtil.CheckArgumentNull(argument, "argument");
742
743             bool usedEscapeCharacter;
744             return SqlProviderManifest.EscapeLikeText(argument, true, out usedEscapeCharacter);
745         }
746         #endregion
747     }
748 }