// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System;
using System.Data;
using System.Data.Common;
using System.Data.Metadata.Edm;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Text;
using System.Xml;
namespace SampleEntityFrameworkProvider
{
///
/// The Provider Manifest for SQL Server
///
internal class SampleProviderManifest : DbXmlEnabledProviderManifest
{
internal const string TokenSql8 = "2000";
internal const string TokenSql9 = "2005";
internal const string TokenSql10 = "2008";
internal const char LikeEscapeChar = '~';
internal const string LikeEscapeCharToString = "~";
#region Private Fields
private StoreVersion _version = StoreVersion.Sql9;
private string _token = TokenSql9;
///
/// maximum size of sql server unicode
///
private const int varcharMaxSize = 8000;
private const int nvarcharMaxSize = 4000;
private const int binaryMaxSize = 8000;
private System.Collections.ObjectModel.ReadOnlyCollection _primitiveTypes = null;
private System.Collections.ObjectModel.ReadOnlyCollection _functions = null;
#endregion
#region Constructors
///
/// Constructor
///
/// A token used to infer the capabilities of the store
public SampleProviderManifest(string manifestToken)
: base(SampleProviderManifest.GetProviderManifest())
{
// GetStoreVersion will throw ArgumentException if manifestToken is null, empty, or not recognized.
_version = StoreVersionUtils.GetStoreVersion(manifestToken);
_token = manifestToken;
}
#endregion
#region Properties
internal StoreVersion Version
{
get { return _version; }
}
#endregion
#region Private Methods
private static XmlReader GetProviderManifest()
{
return GetXmlResource("SampleEntityFrameworkProvider.Resources.SampleProviderServices.ProviderManifest.xml");
}
private XmlReader GetStoreSchemaMapping()
{
return GetXmlResource("SampleEntityFrameworkProvider.Resources.SampleProviderServices.StoreSchemaMapping.msl");
}
private XmlReader GetStoreSchemaDescription()
{
return GetXmlResource("SampleEntityFrameworkProvider.Resources.SampleProviderServices.StoreSchemaDefinition.ssdl");
}
private static XmlReader GetXmlResource(string resourceName)
{
Assembly executingAssembly = Assembly.GetExecutingAssembly();
Stream stream = executingAssembly.GetManifestResourceStream(resourceName);
return XmlReader.Create(stream);
}
#endregion
#region Internal Methods
///
/// Function to detect wildcard characters %, _, [ and ^ and escape them with a preceding ~
/// This escaping is used when StartsWith, EndsWith and Contains canonical and CLR functions
/// are translated to their equivalent LIKE expression
///
/// Original input as specified by the user
/// escape the escape character ~ regardless whether wildcard
/// characters were encountered
/// true if the escaping was performed, false if no escaping was required
/// The escaped string that can be used as pattern in a LIKE expression
internal static string EscapeLikeText(string text, bool alwaysEscapeEscapeChar, out bool usedEscapeChar)
{
usedEscapeChar = false;
if (!(text.Contains("%") || text.Contains("_") || text.Contains("[")
|| text.Contains("^") || alwaysEscapeEscapeChar && text.Contains(LikeEscapeCharToString)))
{
return text;
}
StringBuilder sb = new StringBuilder(text.Length);
foreach (char c in text)
{
if (c == '%' || c == '_' || c == '[' || c == '^' || c == LikeEscapeChar)
{
sb.Append(LikeEscapeChar);
usedEscapeChar = true;
}
sb.Append(c);
}
return sb.ToString();
}
#endregion
#region Overrides
///
/// Providers should override this to return information specific to their provider.
///
/// This method should never return null.
///
/// The name of the information to be retrieved.
/// An XmlReader at the begining of the information requested.
protected override XmlReader GetDbInformation(string informationType)
{
if (informationType == DbProviderManifest.StoreSchemaDefinitionVersion3)
{
return GetStoreSchemaDescription();
}
if (informationType == DbProviderManifest.StoreSchemaMappingVersion3)
{
return GetStoreSchemaMapping();
}
throw new ProviderIncompatibleException(String.Format("The provider returned null for the informationType '{0}'.", informationType));
}
public override System.Collections.ObjectModel.ReadOnlyCollection GetStoreTypes()
{
if (this._primitiveTypes == null)
{
if (this._version == StoreVersion.Sql9 || this._version == StoreVersion.Sql10)
{
this._primitiveTypes = base.GetStoreTypes();
}
else
{
throw new ArgumentException("Store version not supported via sample provider.");
}
}
return this._primitiveTypes;
}
public override System.Collections.ObjectModel.ReadOnlyCollection GetStoreFunctions()
{
if (this._functions == null)
{
if (this._version == StoreVersion.Sql9 || this._version == StoreVersion.Sql10)
{
this._functions = base.GetStoreFunctions();
}
else
{
throw new ArgumentException("Store version not supported via sample provider.");
}
}
return this._functions;
}
///
/// This method takes a type and a set of facets and returns the best mapped equivalent type
/// in EDM.
///
/// A TypeUsage encapsulating a store type and a set of facets
/// A TypeUsage encapsulating an EDM type and a set of facets
public override TypeUsage GetEdmType(TypeUsage storeType)
{
if (storeType == null)
{
throw new ArgumentNullException("storeType");
}
string storeTypeName = storeType.EdmType.Name.ToLowerInvariant();
if (!base.StoreTypeNameToEdmPrimitiveType.ContainsKey(storeTypeName))
{
throw new ArgumentException(String.Format("The underlying provider does not support the type '{0}'.", storeTypeName));
}
PrimitiveType edmPrimitiveType = base.StoreTypeNameToEdmPrimitiveType[storeTypeName];
int maxLength = 0;
bool isUnicode = true;
bool isFixedLen = false;
bool isUnbounded = true;
PrimitiveTypeKind newPrimitiveTypeKind;
switch (storeTypeName)
{
// for some types we just go with simple type usage with no facets
case "tinyint":
case "smallint":
case "bigint":
case "bit":
case "uniqueidentifier":
case "int":
case "geography":
case "geometry":
return TypeUsage.CreateDefaultTypeUsage(edmPrimitiveType);
case "varchar":
newPrimitiveTypeKind = PrimitiveTypeKind.String;
isUnbounded = !TypeHelpers.TryGetMaxLength(storeType, out maxLength);
isUnicode = false;
isFixedLen = false;
break;
case "char":
newPrimitiveTypeKind = PrimitiveTypeKind.String;
isUnbounded = !TypeHelpers.TryGetMaxLength(storeType, out maxLength);
isUnicode = false;
isFixedLen = true;
break;
case "nvarchar":
newPrimitiveTypeKind = PrimitiveTypeKind.String;
isUnbounded = !TypeHelpers.TryGetMaxLength(storeType, out maxLength);
isUnicode = true;
isFixedLen = false;
break;
case "nchar":
newPrimitiveTypeKind = PrimitiveTypeKind.String;
isUnbounded = !TypeHelpers.TryGetMaxLength(storeType, out maxLength);
isUnicode = true;
isFixedLen = true;
break;
case "varchar(max)":
case "text":
newPrimitiveTypeKind = PrimitiveTypeKind.String;
isUnbounded = true;
isUnicode = false;
isFixedLen = false;
break;
case "nvarchar(max)":
case "ntext":
case "xml":
newPrimitiveTypeKind = PrimitiveTypeKind.String;
isUnbounded = true;
isUnicode = true;
isFixedLen = false;
break;
case "binary":
newPrimitiveTypeKind = PrimitiveTypeKind.Binary;
isUnbounded = !TypeHelpers.TryGetMaxLength(storeType, out maxLength);
isFixedLen = true;
break;
case "varbinary":
newPrimitiveTypeKind = PrimitiveTypeKind.Binary;
isUnbounded = !TypeHelpers.TryGetMaxLength(storeType, out maxLength);
isFixedLen = false;
break;
case "varbinary(max)":
case "image":
newPrimitiveTypeKind = PrimitiveTypeKind.Binary;
isUnbounded = true;
isFixedLen = false;
break;
case "timestamp":
case "rowversion":
return TypeUsage.CreateBinaryTypeUsage(edmPrimitiveType, true, 8);
case "float":
case "real":
return TypeUsage.CreateDefaultTypeUsage(edmPrimitiveType);
case "decimal":
case "numeric":
{
byte precision;
byte scale;
if (TypeHelpers.TryGetPrecision(storeType, out precision) && TypeHelpers.TryGetScale(storeType, out scale))
{
return TypeUsage.CreateDecimalTypeUsage(edmPrimitiveType, precision, scale);
}
else
{
return TypeUsage.CreateDecimalTypeUsage(edmPrimitiveType);
}
}
case "money":
return TypeUsage.CreateDecimalTypeUsage(edmPrimitiveType, 19, 4);
case "smallmoney":
return TypeUsage.CreateDecimalTypeUsage(edmPrimitiveType, 10, 4);
case "datetime":
return TypeUsage.CreateDateTimeTypeUsage(edmPrimitiveType, null);
case "smalldatetime":
return TypeUsage.CreateDateTimeTypeUsage(edmPrimitiveType, null);
default:
throw new NotSupportedException(String.Format("The underlying provider does not support the type '{0}'.", storeTypeName));
}
Debug.Assert(newPrimitiveTypeKind == PrimitiveTypeKind.String || newPrimitiveTypeKind == PrimitiveTypeKind.Binary, "at this point only string and binary types should be present");
switch (newPrimitiveTypeKind)
{
case PrimitiveTypeKind.String:
if (!isUnbounded)
{
return TypeUsage.CreateStringTypeUsage(edmPrimitiveType, isUnicode, isFixedLen, maxLength);
}
else
{
return TypeUsage.CreateStringTypeUsage(edmPrimitiveType, isUnicode, isFixedLen);
}
case PrimitiveTypeKind.Binary:
if (!isUnbounded)
{
return TypeUsage.CreateBinaryTypeUsage(edmPrimitiveType, isFixedLen, maxLength);
}
else
{
return TypeUsage.CreateBinaryTypeUsage(edmPrimitiveType, isFixedLen);
}
default:
throw new NotSupportedException(String.Format("The underlying provider does not support the type '{0}'.", storeTypeName));
}
}
///
/// This method takes a type and a set of facets and returns the best mapped equivalent type
/// in SQL Server, taking the store version into consideration.
///
/// A TypeUsage encapsulating an EDM type and a set of facets
/// A TypeUsage encapsulating a store type and a set of facets
public override TypeUsage GetStoreType(TypeUsage edmType)
{
if(edmType == null)
{
throw new ArgumentNullException("edmType");
}
System.Diagnostics.Debug.Assert(edmType.EdmType.BuiltInTypeKind == BuiltInTypeKind.PrimitiveType);
PrimitiveType primitiveType = edmType.EdmType as PrimitiveType;
if (primitiveType == null)
{
throw new ArgumentException(String.Format("The underlying provider does not support the type '{0}'.", edmType));
}
ReadOnlyMetadataCollection facets = edmType.Facets;
switch (primitiveType.PrimitiveTypeKind)
{
case PrimitiveTypeKind.Boolean:
return TypeUsage.CreateDefaultTypeUsage(StoreTypeNameToStorePrimitiveType["bit"]);
case PrimitiveTypeKind.Byte:
return TypeUsage.CreateDefaultTypeUsage(StoreTypeNameToStorePrimitiveType["tinyint"]);
case PrimitiveTypeKind.Int16:
return TypeUsage.CreateDefaultTypeUsage(StoreTypeNameToStorePrimitiveType["smallint"]);
case PrimitiveTypeKind.Int32:
return TypeUsage.CreateDefaultTypeUsage(StoreTypeNameToStorePrimitiveType["int"]);
case PrimitiveTypeKind.Int64:
return TypeUsage.CreateDefaultTypeUsage(StoreTypeNameToStorePrimitiveType["bigint"]);
case PrimitiveTypeKind.Guid:
return TypeUsage.CreateDefaultTypeUsage(StoreTypeNameToStorePrimitiveType["uniqueidentifier"]);
case PrimitiveTypeKind.Double:
return TypeUsage.CreateDefaultTypeUsage(StoreTypeNameToStorePrimitiveType["float"]);
case PrimitiveTypeKind.Single:
return TypeUsage.CreateDefaultTypeUsage(StoreTypeNameToStorePrimitiveType["real"]);
case PrimitiveTypeKind.Decimal: // decimal, numeric, smallmoney, money
{
byte precision;
if (!TypeHelpers.TryGetPrecision(edmType, out precision))
{
precision = 18;
}
byte scale;
if (!TypeHelpers.TryGetScale(edmType, out scale))
{
scale = 0;
}
return TypeUsage.CreateDecimalTypeUsage(StoreTypeNameToStorePrimitiveType["decimal"], precision, scale);
}
case PrimitiveTypeKind.Binary: // binary, varbinary, varbinary(max), image, timestamp, rowversion
{
bool isFixedLength = null != facets["FixedLength"].Value && (bool)facets["FixedLength"].Value;
Facet f = facets["MaxLength"];
bool isMaxLength = f.IsUnbounded || null == f.Value || (int)f.Value > binaryMaxSize;
int maxLength = !isMaxLength ? (int)f.Value : Int32.MinValue;
TypeUsage tu;
if (isFixedLength)
{
tu = TypeUsage.CreateBinaryTypeUsage(StoreTypeNameToStorePrimitiveType["binary"], true, maxLength);
}
else
{
if (isMaxLength)
{
tu = TypeUsage.CreateBinaryTypeUsage(StoreTypeNameToStorePrimitiveType["varbinary(max)"], false);
System.Diagnostics.Debug.Assert(tu.Facets["MaxLength"].Description.IsConstant, "varbinary(max) is not constant!");
}
else
{
tu = TypeUsage.CreateBinaryTypeUsage(StoreTypeNameToStorePrimitiveType["varbinary"], false, maxLength);
}
}
return tu;
}
case PrimitiveTypeKind.String:
// char, nchar, varchar, nvarchar, varchar(max), nvarchar(max), ntext, text, xml
{
bool isUnicode = null == facets["Unicode"].Value || (bool)facets["Unicode"].Value;
bool isFixedLength = null != facets["FixedLength"].Value && (bool)facets["FixedLength"].Value;
Facet f = facets["MaxLength"];
// maxlen is true if facet value is unbounded, the value is bigger than the limited string sizes *or* the facet
// value is null. this is needed since functions still have maxlength facet value as null
bool isMaxLength = f.IsUnbounded || null == f.Value || (int)f.Value > (isUnicode ? nvarcharMaxSize : varcharMaxSize);
int maxLength = !isMaxLength ? (int)f.Value : Int32.MinValue;
TypeUsage tu;
if (isUnicode)
{
if (isFixedLength)
{
tu = TypeUsage.CreateStringTypeUsage(StoreTypeNameToStorePrimitiveType["nchar"], true, true, maxLength);
}
else
{
if (isMaxLength)
{
tu = TypeUsage.CreateStringTypeUsage(StoreTypeNameToStorePrimitiveType["nvarchar(max)"], true, false);
}
else
{
tu = TypeUsage.CreateStringTypeUsage(StoreTypeNameToStorePrimitiveType["nvarchar"], true, false, maxLength);
}
}
}
else
{
if (isFixedLength)
{
tu = TypeUsage.CreateStringTypeUsage(StoreTypeNameToStorePrimitiveType["char"], false, true, maxLength);
}
else
{
if (isMaxLength)
{
tu = TypeUsage.CreateStringTypeUsage(StoreTypeNameToStorePrimitiveType["varchar(max)"], false, false);
}
else
{
tu = TypeUsage.CreateStringTypeUsage(StoreTypeNameToStorePrimitiveType["varchar"], false, false, maxLength);
}
}
}
return tu;
}
case PrimitiveTypeKind.DateTime: // datetime, smalldatetime
Facet preserveSecondsFacet;
bool preserveSeconds;
if (edmType.Facets.TryGetValue("PreserveSeconds", true, out preserveSecondsFacet) && null != preserveSecondsFacet.Value)
{
preserveSeconds = (bool)preserveSecondsFacet.Value;
}
else
{
preserveSeconds = true;
}
return TypeUsage.CreateDefaultTypeUsage(preserveSeconds ? StoreTypeNameToStorePrimitiveType["datetime"] : StoreTypeNameToStorePrimitiveType["smalldatetime"]);
case PrimitiveTypeKind.Geography:
case PrimitiveTypeKind.GeographyPoint:
case PrimitiveTypeKind.GeographyLineString:
case PrimitiveTypeKind.GeographyPolygon:
case PrimitiveTypeKind.GeographyMultiPoint:
case PrimitiveTypeKind.GeographyMultiLineString:
case PrimitiveTypeKind.GeographyMultiPolygon:
case PrimitiveTypeKind.GeographyCollection:
return TypeUsage.CreateDefaultTypeUsage(StoreTypeNameToStorePrimitiveType["geography"]);
case PrimitiveTypeKind.Geometry:
case PrimitiveTypeKind.GeometryPoint:
case PrimitiveTypeKind.GeometryLineString:
case PrimitiveTypeKind.GeometryPolygon:
case PrimitiveTypeKind.GeometryMultiPoint:
case PrimitiveTypeKind.GeometryMultiLineString:
case PrimitiveTypeKind.GeometryMultiPolygon:
case PrimitiveTypeKind.GeometryCollection:
return TypeUsage.CreateDefaultTypeUsage(StoreTypeNameToStorePrimitiveType["geometry"]);
default:
throw new NotSupportedException(String.Format("There is no store type corresponding to the EDM type '{0}' of primitive type '{1}'.", edmType, primitiveType.PrimitiveTypeKind));
}
}
///
/// Returns true, SqlClient supports escaping strings to be used as arguments to like
/// The escape character is '~'
///
/// The character '~'
/// True
public override bool SupportsEscapingLikeArgument(out char escapeCharacter)
{
escapeCharacter = SampleProviderManifest.LikeEscapeChar;
return true;
}
///
/// Escapes the wildcard characters and the escape character in the given argument.
///
///
/// Equivalent to the argument, with the wildcard characters and the escape character escaped
public override string EscapeLikeArgument(string argument)
{
bool usedEscapeCharacter;
return SampleProviderManifest.EscapeLikeText(argument, true, out usedEscapeCharacter);
}
#endregion
#region Helpers
class TypeHelpers
{
public static bool TryGetPrecision(TypeUsage tu, out byte precision)
{
Facet f;
precision = 0;
if (tu.Facets.TryGetValue("Precision", false, out f))
{
if (!f.IsUnbounded && f.Value != null)
{
precision = (byte)f.Value;
return true;
}
}
return false;
}
public static bool TryGetMaxLength(TypeUsage tu, out int maxLength)
{
Facet f;
maxLength = 0;
if (tu.Facets.TryGetValue("MaxLength", false, out f))
{
if (!f.IsUnbounded && f.Value != null)
{
maxLength = (int)f.Value;
return true;
}
}
return false;
}
public static bool TryGetScale(TypeUsage tu, out byte scale)
{
Facet f;
scale = 0;
if (tu.Facets.TryGetValue("Scale", false, out f))
{
if (!f.IsUnbounded && f.Value != null)
{
scale = (byte)f.Value;
return true;
}
}
return false;
}
}
#endregion
}
}