From 19a914eb5c5619b5b6423b8664e13087ca9b6dcb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Delaporte?= <fredericdelaporte@free.fr> Date: Wed, 4 Oct 2017 18:16:38 +0200 Subject: [PATCH 1/3] NH-4088 - Fix TypeNames.Get for decimal capacity * Compare capacity against precision for precision based types * Fix dialects declarations for max precision * Fix dialects declarations for precision based types which were using length as precision or scale It is a prerequisite to the fix of GetTypeCastName. --- src/NHibernate/Dialect/DB2Dialect.cs | 3 +- src/NHibernate/Dialect/Dialect.cs | 18 +----- src/NHibernate/Dialect/FirebirdDialect.cs | 31 +--------- src/NHibernate/Dialect/GenericDialect.cs | 22 ++++--- src/NHibernate/Dialect/InformixDialect.cs | 9 ++- src/NHibernate/Dialect/IngresDialect.cs | 5 +- src/NHibernate/Dialect/MsSql2000Dialect.cs | 3 +- src/NHibernate/Dialect/MsSqlCeDialect.cs | 3 +- src/NHibernate/Dialect/MySQL5Dialect.cs | 7 ++- src/NHibernate/Dialect/Oracle8iDialect.cs | 5 +- src/NHibernate/Dialect/OracleLiteDialect.cs | 3 +- src/NHibernate/Dialect/PostgreSQLDialect.cs | 3 +- src/NHibernate/Dialect/SybaseASA9Dialect.cs | 5 +- .../Dialect/SybaseSQLAnywhere10Dialect.cs | 3 +- src/NHibernate/Dialect/TypeNames.cs | 58 ++++++++++++++----- 15 files changed, 94 insertions(+), 84 deletions(-) diff --git a/src/NHibernate/Dialect/DB2Dialect.cs b/src/NHibernate/Dialect/DB2Dialect.cs index 26d658f9140..fc578dcc6df 100644 --- a/src/NHibernate/Dialect/DB2Dialect.cs +++ b/src/NHibernate/Dialect/DB2Dialect.cs @@ -39,7 +39,8 @@ public DB2Dialect() RegisterColumnType(DbType.Date, "DATE"); RegisterColumnType(DbType.DateTime, "TIMESTAMP"); RegisterColumnType(DbType.Decimal, "DECIMAL(19,5)"); - RegisterColumnType(DbType.Decimal, 19, "DECIMAL(19, $l)"); + // DB2 max precision is 31, but .Net is 28-29 anyway. + RegisterColumnType(DbType.Decimal, 28, "DECIMAL($p, $s)"); RegisterColumnType(DbType.Double, "DOUBLE"); RegisterColumnType(DbType.Int16, "SMALLINT"); RegisterColumnType(DbType.Int32, "INTEGER"); diff --git a/src/NHibernate/Dialect/Dialect.cs b/src/NHibernate/Dialect/Dialect.cs index a85a330b4c9..ff97bce3220 100644 --- a/src/NHibernate/Dialect/Dialect.cs +++ b/src/NHibernate/Dialect/Dialect.cs @@ -215,17 +215,10 @@ public virtual string GetTypeName(SqlType sqlType) { if (sqlType.LengthDefined || sqlType.PrecisionDefined || sqlType.ScaleDefined) { - string resultWithLength = _typeNames.Get(sqlType.DbType, sqlType.Length, sqlType.Precision, sqlType.Scale); - if (resultWithLength != null) return resultWithLength; + return _typeNames.Get(sqlType.DbType, sqlType.Length, sqlType.Precision, sqlType.Scale); } - string result = _typeNames.Get(sqlType.DbType); - if (result == null) - { - throw new HibernateException(string.Format("No default type mapping for SqlType {0}", sqlType)); - } - - return result; + return _typeNames.Get(sqlType.DbType); } /// <summary> @@ -239,12 +232,7 @@ public virtual string GetTypeName(SqlType sqlType) /// <returns>The database type name used by ddl.</returns> public virtual string GetTypeName(SqlType sqlType, int length, int precision, int scale) { - string result = _typeNames.Get(sqlType.DbType, length, precision, scale); - if (result == null) - { - throw new HibernateException(string.Format("No type mapping for SqlType {0} of length {1}", sqlType, length)); - } - return result; + return _typeNames.Get(sqlType.DbType, length, precision, scale); } /// <summary> diff --git a/src/NHibernate/Dialect/FirebirdDialect.cs b/src/NHibernate/Dialect/FirebirdDialect.cs index 5cd56a4d08a..564ba59ddab 100644 --- a/src/NHibernate/Dialect/FirebirdDialect.cs +++ b/src/NHibernate/Dialect/FirebirdDialect.cs @@ -6,7 +6,6 @@ using NHibernate.Dialect.Schema; using NHibernate.Engine; using NHibernate.SqlCommand; -using NHibernate.SqlTypes; using NHibernate.Type; using Environment = NHibernate.Cfg.Environment; @@ -30,12 +29,6 @@ namespace NHibernate.Dialect /// </remarks> public class FirebirdDialect : Dialect { - #region Fields - - private const int MAX_DECIMAL_PRECISION = 18; - - #endregion - public FirebirdDialect() { RegisterKeywords(); @@ -49,23 +42,6 @@ public override string AddColumnString get { return "add"; } } - public override string GetTypeName(SqlType sqlType) - { - if (IsUnallowedDecimal(sqlType.DbType, sqlType.Precision)) - return base.GetTypeName(new SqlType(sqlType.DbType, MAX_DECIMAL_PRECISION, sqlType.Scale)); - - return base.GetTypeName(sqlType); - } - - public override string GetTypeName(SqlType sqlType, int length, int precision, int scale) - { - var fbDecimalPrecision = precision; - if (IsUnallowedDecimal(sqlType.DbType, precision)) - fbDecimalPrecision = MAX_DECIMAL_PRECISION; - - return base.GetTypeName(sqlType, length, fbDecimalPrecision, scale); - } - public override string GetSelectSequenceNextValString(string sequenceName) { return string.Format("gen_id({0}, 1 )", sequenceName); @@ -544,11 +520,6 @@ private void RegisterTrigonometricFunctions() RegisterFunction("tanh", new StandardSQLFunction("tanh", NHibernateUtil.Double)); } - private static bool IsUnallowedDecimal(DbType dbType, int precision) - { - return dbType == DbType.Decimal && precision > MAX_DECIMAL_PRECISION; - } - #region Informational metadata /// <summary> @@ -562,4 +533,4 @@ private static bool IsUnallowedDecimal(DbType dbType, int precision) #endregion } -} \ No newline at end of file +} diff --git a/src/NHibernate/Dialect/GenericDialect.cs b/src/NHibernate/Dialect/GenericDialect.cs index 545981b6249..a55c638d55e 100644 --- a/src/NHibernate/Dialect/GenericDialect.cs +++ b/src/NHibernate/Dialect/GenericDialect.cs @@ -9,25 +9,31 @@ namespace NHibernate.Dialect public class GenericDialect : Dialect { /// <summary></summary> - public GenericDialect() : base() + public GenericDialect() { - RegisterColumnType(DbType.AnsiStringFixedLength, "CHAR($l)"); - RegisterColumnType(DbType.AnsiString, "VARCHAR($l)"); - RegisterColumnType(DbType.Binary, "VARBINARY($l)"); + RegisterColumnType(DbType.AnsiStringFixedLength, "CHAR(255)"); + RegisterColumnType(DbType.AnsiStringFixedLength, 8000, "CHAR($l)"); + RegisterColumnType(DbType.AnsiString, "VARCHAR(255)"); + RegisterColumnType(DbType.AnsiString, 8000, "VARCHAR($l)"); + RegisterColumnType(DbType.Binary, "VARBINARY(255)"); + RegisterColumnType(DbType.Binary, 8000, "VARBINARY($l)"); RegisterColumnType(DbType.Boolean, "BIT"); RegisterColumnType(DbType.Byte, "TINYINT"); RegisterColumnType(DbType.Currency, "MONEY"); RegisterColumnType(DbType.Date, "DATE"); RegisterColumnType(DbType.DateTime, "DATETIME"); - RegisterColumnType(DbType.Decimal, "DECIMAL(19, $l)"); + RegisterColumnType(DbType.Decimal, "DECIMAL(19, 5)"); + RegisterColumnType(DbType.Decimal, 19, "DECIMAL($p, $s)"); RegisterColumnType(DbType.Double, "DOUBLE PRECISION"); RegisterColumnType(DbType.Guid, "UNIQUEIDENTIFIER"); RegisterColumnType(DbType.Int16, "SMALLINT"); RegisterColumnType(DbType.Int32, "INT"); RegisterColumnType(DbType.Int64, "BIGINT"); RegisterColumnType(DbType.Single, "REAL"); - RegisterColumnType(DbType.StringFixedLength, "NCHAR($l)"); - RegisterColumnType(DbType.String, "NVARCHAR($l)"); + RegisterColumnType(DbType.StringFixedLength, "NCHAR(255)"); + RegisterColumnType(DbType.StringFixedLength, 4000, "NCHAR($l)"); + RegisterColumnType(DbType.String, "NVARCHAR(255)"); + RegisterColumnType(DbType.String, 4000, "NVARCHAR($l)"); RegisterColumnType(DbType.Time, "TIME"); } @@ -37,4 +43,4 @@ public override string AddColumnString get { return "add column"; } } } -} \ No newline at end of file +} diff --git a/src/NHibernate/Dialect/InformixDialect.cs b/src/NHibernate/Dialect/InformixDialect.cs index 4ab647b632b..94b174b5e64 100644 --- a/src/NHibernate/Dialect/InformixDialect.cs +++ b/src/NHibernate/Dialect/InformixDialect.cs @@ -36,7 +36,8 @@ public partial class InformixDialect : Dialect /// <summary></summary> public InformixDialect() { - RegisterColumnType(DbType.AnsiStringFixedLength, "CHAR($l)"); + RegisterColumnType(DbType.AnsiStringFixedLength, "CHAR(255)"); + RegisterColumnType(DbType.AnsiStringFixedLength, 255, "CHAR($l)"); RegisterColumnType(DbType.AnsiString, 255, "VARCHAR($l)"); RegisterColumnType(DbType.AnsiString, 32739, "LVARCHAR($l)"); RegisterColumnType(DbType.AnsiString, 2147483647, "TEXT"); @@ -49,14 +50,16 @@ public InformixDialect() RegisterColumnType(DbType.Date, "DATE"); RegisterColumnType(DbType.DateTime, "datetime year to fraction(5)"); RegisterColumnType(DbType.Decimal, "DECIMAL(19, 5)"); - RegisterColumnType(DbType.Decimal, 19, "DECIMAL($p, $s)"); + // Informix max precision is 32, but .Net is limited to 28-29. + RegisterColumnType(DbType.Decimal, 28, "DECIMAL($p, $s)"); RegisterColumnType(DbType.Double, "DOUBLE"); RegisterColumnType(DbType.Int16, "SMALLINT"); RegisterColumnType(DbType.Int32, "INTEGER"); RegisterColumnType(DbType.Int64, "BIGINT"); RegisterColumnType(DbType.Single, "SmallFloat"); RegisterColumnType(DbType.Time, "datetime hour to second"); - RegisterColumnType(DbType.StringFixedLength, "CHAR($l)"); + RegisterColumnType(DbType.StringFixedLength, "CHAR(255)"); + RegisterColumnType(DbType.StringFixedLength, 255, "CHAR($l)"); RegisterColumnType(DbType.String, 255, "VARCHAR($l)"); RegisterColumnType(DbType.String, 32739, "LVARCHAR($l)"); RegisterColumnType(DbType.String, 2147483647, "TEXT"); diff --git a/src/NHibernate/Dialect/IngresDialect.cs b/src/NHibernate/Dialect/IngresDialect.cs index 616d7cde29b..06395686aba 100644 --- a/src/NHibernate/Dialect/IngresDialect.cs +++ b/src/NHibernate/Dialect/IngresDialect.cs @@ -36,7 +36,8 @@ public IngresDialect() RegisterColumnType(DbType.Date, "date"); RegisterColumnType(DbType.DateTime, "timestamp"); RegisterColumnType(DbType.Decimal, "decimal(19,5)"); - RegisterColumnType(DbType.Decimal, 19, "decimal(18, $l)"); + // Ingres max precision is 31, but .Net is limited to 28-29. + RegisterColumnType(DbType.Decimal, 28, "decimal($p, $s)"); RegisterColumnType(DbType.Double, "float8"); RegisterColumnType(DbType.Int16, "int2"); RegisterColumnType(DbType.Int32, "int4"); @@ -64,4 +65,4 @@ public IngresDialect() #endregion } -} \ No newline at end of file +} diff --git a/src/NHibernate/Dialect/MsSql2000Dialect.cs b/src/NHibernate/Dialect/MsSql2000Dialect.cs index 0e86557107b..1418beccbda 100644 --- a/src/NHibernate/Dialect/MsSql2000Dialect.cs +++ b/src/NHibernate/Dialect/MsSql2000Dialect.cs @@ -373,7 +373,8 @@ protected virtual void RegisterNumericTypeMappings() RegisterColumnType(DbType.Byte, "TINYINT"); RegisterColumnType(DbType.Currency, "MONEY"); RegisterColumnType(DbType.Decimal, "DECIMAL(19,5)"); - RegisterColumnType(DbType.Decimal, 19, "DECIMAL($p, $s)"); + // SQL Server max precision is 38, but .Net is limited to 28-29. + RegisterColumnType(DbType.Decimal, 28, "DECIMAL($p, $s)"); RegisterColumnType(DbType.Double, "FLOAT(53)"); RegisterColumnType(DbType.Int16, "SMALLINT"); RegisterColumnType(DbType.Int32, "INT"); diff --git a/src/NHibernate/Dialect/MsSqlCeDialect.cs b/src/NHibernate/Dialect/MsSqlCeDialect.cs index 4b39f2b87de..257c71186a1 100644 --- a/src/NHibernate/Dialect/MsSqlCeDialect.cs +++ b/src/NHibernate/Dialect/MsSqlCeDialect.cs @@ -150,7 +150,8 @@ protected virtual void RegisterTypeMapping() RegisterColumnType(DbType.Date, "DATETIME"); RegisterColumnType(DbType.DateTime, "DATETIME"); RegisterColumnType(DbType.Decimal, "NUMERIC(19,5)"); - RegisterColumnType(DbType.Decimal, 19, "NUMERIC($p, $s)"); + // SQL Server CE max precision is 38, but .Net is limited to 28-29. + RegisterColumnType(DbType.Decimal, 28, "NUMERIC($p, $s)"); RegisterColumnType(DbType.Double, "FLOAT"); RegisterColumnType(DbType.Guid, "UNIQUEIDENTIFIER"); RegisterColumnType(DbType.Int16, "SMALLINT"); diff --git a/src/NHibernate/Dialect/MySQL5Dialect.cs b/src/NHibernate/Dialect/MySQL5Dialect.cs index aa76e16b3ac..0a7c73b0018 100644 --- a/src/NHibernate/Dialect/MySQL5Dialect.cs +++ b/src/NHibernate/Dialect/MySQL5Dialect.cs @@ -8,7 +8,8 @@ public class MySQL5Dialect : MySQLDialect public MySQL5Dialect() { RegisterColumnType(DbType.Decimal, "DECIMAL(19,5)"); - RegisterColumnType(DbType.Decimal, 19, "DECIMAL($p, $s)"); + // My SQL supports precision up to 65, but .Net is limited to 28-29. + RegisterColumnType(DbType.Decimal, 28, "DECIMAL($p, $s)"); RegisterColumnType(DbType.Guid, "BINARY(16)"); } @@ -17,7 +18,7 @@ protected override void RegisterCastTypes() { // MySql 5 also supports DECIMAL as a cast type target // http://dev.mysql.com/doc/refman/5.0/en/cast-functions.html RegisterCastType(DbType.Decimal, "DECIMAL(19,5)"); - RegisterCastType(DbType.Decimal, 19, "DECIMAL($p, $s)"); + RegisterCastType(DbType.Decimal, 28, "DECIMAL($p, $s)"); RegisterCastType(DbType.Double, "DECIMAL(19,5)"); RegisterCastType(DbType.Single, "DECIMAL(19,5)"); RegisterCastType(DbType.Guid, "BINARY(16)"); @@ -61,4 +62,4 @@ public override bool SupportsInsertSelectIdentity get { return true; } } } -} \ No newline at end of file +} diff --git a/src/NHibernate/Dialect/Oracle8iDialect.cs b/src/NHibernate/Dialect/Oracle8iDialect.cs index fb20ded0cf4..0c1cff94616 100644 --- a/src/NHibernate/Dialect/Oracle8iDialect.cs +++ b/src/NHibernate/Dialect/Oracle8iDialect.cs @@ -185,9 +185,10 @@ protected virtual void RegisterNumericTypeMappings() RegisterColumnType(DbType.Currency, "NUMBER(20,2)"); RegisterColumnType(DbType.Single, "FLOAT(24)"); RegisterColumnType(DbType.Double, "DOUBLE PRECISION"); - RegisterColumnType(DbType.Double, 19, "NUMBER($p,$s)"); + // Oracle max precision is 39-40, but .Net is limited to 28-29. + RegisterColumnType(DbType.Double, 28, "NUMBER($p,$s)"); RegisterColumnType(DbType.Decimal, "NUMBER(19,5)"); - RegisterColumnType(DbType.Decimal, 19, "NUMBER($p,$s)"); + RegisterColumnType(DbType.Decimal, 28, "NUMBER($p,$s)"); } protected virtual void RegisterDateTimeTypeMappings() diff --git a/src/NHibernate/Dialect/OracleLiteDialect.cs b/src/NHibernate/Dialect/OracleLiteDialect.cs index 707736fb166..d0945678fe9 100644 --- a/src/NHibernate/Dialect/OracleLiteDialect.cs +++ b/src/NHibernate/Dialect/OracleLiteDialect.cs @@ -43,7 +43,8 @@ public OracleLiteDialect() RegisterColumnType(DbType.Date, "DATE"); RegisterColumnType(DbType.DateTime, "TIMESTAMP(4)"); RegisterColumnType(DbType.Decimal, "NUMBER(19,5)"); - RegisterColumnType(DbType.Decimal, 19, "NUMBER(19, $l)"); + // Oracle max precision is 39-40, but .Net is limited to 28-29. + RegisterColumnType(DbType.Decimal, 28, "NUMBER($p, $s)"); // having problems with both ODP and OracleClient from MS not being able // to read values out of a field that is DOUBLE PRECISION RegisterColumnType(DbType.Double, "DOUBLE PRECISION"); //"FLOAT(53)" ); diff --git a/src/NHibernate/Dialect/PostgreSQLDialect.cs b/src/NHibernate/Dialect/PostgreSQLDialect.cs index f45ff7040de..cb1f5086ab2 100644 --- a/src/NHibernate/Dialect/PostgreSQLDialect.cs +++ b/src/NHibernate/Dialect/PostgreSQLDialect.cs @@ -42,7 +42,8 @@ public PostgreSQLDialect() RegisterColumnType(DbType.Byte, "int2"); RegisterColumnType(DbType.Currency, "decimal(16,4)"); RegisterColumnType(DbType.Decimal, "decimal(19,5)"); - RegisterColumnType(DbType.Decimal, 19, "decimal($p, $s)"); + // PostgreSQL max precision is unlimited, but .Net is limited to 28-29. + RegisterColumnType(DbType.Decimal, 28, "decimal($p, $s)"); RegisterColumnType(DbType.Double, "float8"); RegisterColumnType(DbType.Int16, "int2"); RegisterColumnType(DbType.Int32, "int4"); diff --git a/src/NHibernate/Dialect/SybaseASA9Dialect.cs b/src/NHibernate/Dialect/SybaseASA9Dialect.cs index 94745a76329..699a5430433 100644 --- a/src/NHibernate/Dialect/SybaseASA9Dialect.cs +++ b/src/NHibernate/Dialect/SybaseASA9Dialect.cs @@ -51,7 +51,8 @@ public SybaseASA9Dialect() RegisterColumnType(DbType.Date, "DATE"); RegisterColumnType(DbType.DateTime, "TIMESTAMP"); RegisterColumnType(DbType.Decimal, "DECIMAL(18,5)"); // NUMERIC(18,5) is equivalent to DECIMAL(18,5) - RegisterColumnType(DbType.Decimal, 18, "DECIMAL(18,$l)"); + // Sybase max precision is 38, but .Net is limited to 28-29. + RegisterColumnType(DbType.Decimal, 28, "DECIMAL($p,$s)"); RegisterColumnType(DbType.Double, "DOUBLE"); RegisterColumnType(DbType.Guid, "CHAR(16)"); RegisterColumnType(DbType.Int16, "SMALLINT"); @@ -185,4 +186,4 @@ private static int GetAfterSelectInsertPoint(SqlString sql) return 0; } } -} \ No newline at end of file +} diff --git a/src/NHibernate/Dialect/SybaseSQLAnywhere10Dialect.cs b/src/NHibernate/Dialect/SybaseSQLAnywhere10Dialect.cs index a45cfda6775..7b40051ef65 100644 --- a/src/NHibernate/Dialect/SybaseSQLAnywhere10Dialect.cs +++ b/src/NHibernate/Dialect/SybaseSQLAnywhere10Dialect.cs @@ -91,7 +91,8 @@ protected virtual void RegisterNumericTypeMappings() RegisterColumnType(DbType.Single, "REAL"); RegisterColumnType(DbType.Double, "DOUBLE"); RegisterColumnType(DbType.Decimal, "NUMERIC(19,5)"); // Precision ranges from 0-127 - RegisterColumnType(DbType.Decimal, 19, "NUMERIC($p, $s)"); // Precision ranges from 0-127 + // Anywhere max precision is 127, but .Net is limited to 28-29. + RegisterColumnType(DbType.Decimal, 38, "NUMERIC($p, $s)"); // Precision ranges from 0-127 } protected virtual void RegisterDateTimeTypeMappings() diff --git a/src/NHibernate/Dialect/TypeNames.cs b/src/NHibernate/Dialect/TypeNames.cs index 4ceb8a49a88..7c25a211461 100644 --- a/src/NHibernate/Dialect/TypeNames.cs +++ b/src/NHibernate/Dialect/TypeNames.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Data; +using System.Linq; using NHibernate.Util; namespace NHibernate.Dialect @@ -56,10 +57,9 @@ public class TypeNames /// <returns>the default type name associated with the specified key</returns> public string Get(DbType typecode) { - string result; - if (!defaults.TryGetValue(typecode, out result)) + if (!defaults.TryGetValue(typecode, out var result)) { - throw new ArgumentException("Dialect does not support DbType." + typecode, "typecode"); + throw new ArgumentException("Dialect does not support DbType." + typecode, nameof(typecode)); } return result; } @@ -72,15 +72,18 @@ public string Get(DbType typecode) /// <param name="scale">the SQL scale </param> /// <param name="precision">the SQL precision </param> /// <returns> - /// The associated name with smallest capacity >= size (or scale for date time types) if available, - /// otherwise the default type name. + /// The associated name with smallest capacity >= size (or precision for decimal, or scale for date time types) + /// if available, otherwise the default type name. /// </returns> public string Get(DbType typecode, int size, int precision, int scale) { weighted.TryGetValue(typecode, out var map); if (map != null && map.Count > 0) { - var requiredCapacity = IsScaleType(typecode) ? scale : size; + var isPrecisionType = IsPrecisionType(typecode); + var requiredCapacity = isPrecisionType + ? precision + : IsScaleType(typecode) ? scale : size; foreach (var entry in map) { if (requiredCapacity <= entry.Key) @@ -88,14 +91,27 @@ public string Get(DbType typecode, int size, int precision, int scale) return Replace(entry.Value, size, precision, scale); } } + if (isPrecisionType && precision != 0) + { + // The default is usually not the max for precision type, fallback to last entry instead. + var maxEntry = map.Last(); + var adjustedPrecision = maxEntry.Key; + // Reduce the scale (most databases restrict scale to be less or equal to precision) + // For a proportionnal reduction, we could use + // Math.Min((int) Math.Round(scale * adjustedPrecision / (double) precision), adjustedPrecision); + // But if the type is used for storing amounts, this may cause losing the ability to store cents... + // So better just reduce as few as possible. + var adjustedScale = Math.Min(scale, adjustedPrecision); + return Replace(maxEntry.Value, size, adjustedPrecision, adjustedScale); + } } //Could not find a specific type for the capacity, using the default - return Replace(Get(typecode), size, precision, scale); + return Get(typecode); } /// <summary> - /// For types with a simple length (or scale for date time types), this method returns the definition - /// for the longest registered type. + /// For types with a simple length (or precision for decimal, or scale for date time types), this method + /// returns the definition for the longest registered type. /// </summary> /// <param name="typecode"></param> /// <returns></returns> @@ -104,18 +120,32 @@ public string GetLongest(DbType typecode) weighted.TryGetValue(typecode, out var map); if (map != null && map.Count > 0) { + var isPrecisionType = IsPrecisionType(typecode); var isScaleType = IsScaleType(typecode); + var isSizeType = !isPrecisionType && !isScaleType; var capacity = map.Keys[map.Count - 1]; return Replace( map.Values[map.Count - 1], - isScaleType ? 0 : capacity, - 0, + isSizeType ? capacity : 0, + isPrecisionType ? capacity : 0, isScaleType ? capacity : 0); } return Get(typecode); } + private static bool IsPrecisionType(DbType typecode) + { + switch (typecode) + { + case DbType.Decimal: + // Oracle dialect defines precision and scale for double, because it uses number instead of binary_double. + case DbType.Double: + return true; + } + return false; + } + private static bool IsScaleType(DbType typecode) { switch (typecode) @@ -140,10 +170,12 @@ private static string Replace(string type, int size, int precision, int scale) /// Set a type name for specified type key and capacity /// </summary> /// <param name="typecode">the type key</param> - /// <param name="capacity">the (maximum) type size/length or scale</param> + /// <param name="capacity">the (maximum) type size/length, precision or scale</param> /// <param name="value">The associated name</param> public void Put(DbType typecode, int capacity, string value) { + if (value == null) + throw new ArgumentNullException(nameof(value)); SortedList<int, string> map; if (!weighted.TryGetValue(typecode, out map)) { @@ -160,7 +192,7 @@ public void Put(DbType typecode, int capacity, string value) /// <param name="value"></param> public void Put(DbType typecode, string value) { - defaults[typecode] = value; + defaults[typecode] = value ?? throw new ArgumentNullException(nameof(value)); } } } From bbd7772cd257b09bfdfb75cb897a7b33e42c0b89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Delaporte?= <fredericdelaporte@free.fr> Date: Wed, 4 Oct 2017 15:55:32 +0200 Subject: [PATCH 2/3] NH-4088 - Fix GetCastTypeName * Use type length/precision/scale when defined * Use maximal capacity types otherwise when it makes sens * Use configurable default length/precision/scale otherwise --- .../Async/Criteria/ProjectionsTest.cs | 55 +++++++++++++++++++ .../Async/DialectTest/DialectFixture.cs | 3 +- .../Criteria/ProjectionsTest.cs | 55 +++++++++++++++++++ .../DialectTest/DialectFixture.cs | 26 ++++++++- .../DriverTest/FirebirdClientDriverFixture.cs | 6 +- src/NHibernate/Cfg/Environment.cs | 18 ++++++ src/NHibernate/Dialect/Dialect.cs | 40 +++++++++++++- src/NHibernate/Dialect/MySQLDialect.cs | 16 ++---- src/NHibernate/Driver/FirebirdClientDriver.cs | 38 +++++++++---- src/NHibernate/nhibernate-configuration.xsd | 24 ++++++++ 10 files changed, 250 insertions(+), 31 deletions(-) diff --git a/src/NHibernate.Test/Async/Criteria/ProjectionsTest.cs b/src/NHibernate.Test/Async/Criteria/ProjectionsTest.cs index 71623f356a0..7b00a0b4bd3 100644 --- a/src/NHibernate.Test/Async/Criteria/ProjectionsTest.cs +++ b/src/NHibernate.Test/Async/Criteria/ProjectionsTest.cs @@ -8,10 +8,12 @@ //------------------------------------------------------------------------------ +using System; using System.Collections; using System.Collections.Generic; using NHibernate.Criterion; using NHibernate.Dialect; +using NHibernate.Type; using NUnit.Framework; namespace NHibernate.Test.Criteria @@ -100,6 +102,59 @@ public async Task UsingSqlFunctions_Concat_WithCastAsync() } } + [Test] + public async Task CastWithLengthAsync() + { + using (var s = OpenSession()) + { + try + { + var shortName = await (s + .CreateCriteria<Student>() + .SetProjection( + Projections.Cast( + TypeFactory.GetStringType(3), + Projections.Property("Name"))) + .UniqueResultAsync<string>()); + Assert.That(shortName, Is.EqualTo("aye")); + } + catch (Exception e) + { + if (!e.Message.Contains("truncation") && + (e.InnerException == null || !e.InnerException.Message.Contains("truncation"))) + throw; + } + } + } + + [Test] + public async Task CastWithPrecisionScaleAsync() + { + if (TestDialect.HasBrokenDecimalType) + Assert.Ignore("Dialect does not correctly handle decimal."); + + using (var s = OpenSession()) + { + var value = await (s + .CreateCriteria<Student>() + .SetProjection( + Projections.Cast( + TypeFactory.Basic("decimal(18,9)"), + Projections.Constant(123456789.123456789m, TypeFactory.Basic("decimal(18,9)")))) + .UniqueResultAsync<decimal>()); + Assert.That(value, Is.EqualTo(123456789.123456789m), "Same type cast"); + + value = await (s + .CreateCriteria<Student>() + .SetProjection( + Projections.Cast( + TypeFactory.Basic("decimal(18,7)"), + Projections.Constant(123456789.987654321m, TypeFactory.Basic("decimal(18,9)")))) + .UniqueResultAsync<decimal>()); + Assert.That(value, Is.EqualTo(123456789.9876543m), "Reduced scale cast"); + } + } + [Test] public async Task CanUseParametersWithProjectionsAsync() { diff --git a/src/NHibernate.Test/Async/DialectTest/DialectFixture.cs b/src/NHibernate.Test/Async/DialectTest/DialectFixture.cs index fed9479e9fd..e822ea63452 100644 --- a/src/NHibernate.Test/Async/DialectTest/DialectFixture.cs +++ b/src/NHibernate.Test/Async/DialectTest/DialectFixture.cs @@ -11,7 +11,6 @@ using System; using System.Collections.Generic; using System.Data; -using System.Data.Common; using NHibernate.Dialect; using NHibernate.Driver; using NHibernate.Engine; @@ -79,4 +78,4 @@ public async Task CurrentTimestampSelectionAsync() } } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/Criteria/ProjectionsTest.cs b/src/NHibernate.Test/Criteria/ProjectionsTest.cs index eb553ce00c2..165c12715cb 100644 --- a/src/NHibernate.Test/Criteria/ProjectionsTest.cs +++ b/src/NHibernate.Test/Criteria/ProjectionsTest.cs @@ -1,7 +1,9 @@ +using System; using System.Collections; using System.Collections.Generic; using NHibernate.Criterion; using NHibernate.Dialect; +using NHibernate.Type; using NUnit.Framework; namespace NHibernate.Test.Criteria @@ -89,6 +91,59 @@ public void UsingSqlFunctions_Concat_WithCast() } } + [Test] + public void CastWithLength() + { + using (var s = OpenSession()) + { + try + { + var shortName = s + .CreateCriteria<Student>() + .SetProjection( + Projections.Cast( + TypeFactory.GetStringType(3), + Projections.Property("Name"))) + .UniqueResult<string>(); + Assert.That(shortName, Is.EqualTo("aye")); + } + catch (Exception e) + { + if (!e.Message.Contains("truncation") && + (e.InnerException == null || !e.InnerException.Message.Contains("truncation"))) + throw; + } + } + } + + [Test] + public void CastWithPrecisionScale() + { + if (TestDialect.HasBrokenDecimalType) + Assert.Ignore("Dialect does not correctly handle decimal."); + + using (var s = OpenSession()) + { + var value = s + .CreateCriteria<Student>() + .SetProjection( + Projections.Cast( + TypeFactory.Basic("decimal(18,9)"), + Projections.Constant(123456789.123456789m, TypeFactory.Basic("decimal(18,9)")))) + .UniqueResult<decimal>(); + Assert.That(value, Is.EqualTo(123456789.123456789m), "Same type cast"); + + value = s + .CreateCriteria<Student>() + .SetProjection( + Projections.Cast( + TypeFactory.Basic("decimal(18,7)"), + Projections.Constant(123456789.987654321m, TypeFactory.Basic("decimal(18,9)")))) + .UniqueResult<decimal>(); + Assert.That(value, Is.EqualTo(123456789.9876543m), "Reduced scale cast"); + } + } + [Test] public void CanUseParametersWithProjections() { diff --git a/src/NHibernate.Test/DialectTest/DialectFixture.cs b/src/NHibernate.Test/DialectTest/DialectFixture.cs index 50a9aa12661..1895f9be13b 100644 --- a/src/NHibernate.Test/DialectTest/DialectFixture.cs +++ b/src/NHibernate.Test/DialectTest/DialectFixture.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Data; -using System.Data.Common; using NHibernate.Dialect; using NHibernate.Driver; using NHibernate.Engine; @@ -171,5 +170,28 @@ public void CurrentTimestampSelection() } } } + + [Test] + public void GetDecimalTypeName() + { + var cfg = TestConfigurationHelper.GetDefaultConfiguration(); + var dialect = Dialect.Dialect.GetDialect(cfg.Properties); + + Assert.That(dialect.GetTypeName(SqlTypeFactory.GetSqlType(DbType.Decimal, 40, 40)), Does.Not.Contain("40"), "oversized decimal"); + Assert.That(dialect.GetTypeName(SqlTypeFactory.GetSqlType(DbType.Decimal, 3, 2)), Does.Match(@"^[^(]*(\(\s*3\s*,\s*2\s*\))?\s*$"), "small decimal"); + } + + [Test] + public void GetTypeCastName() + { + var cfg = TestConfigurationHelper.GetDefaultConfiguration(); + cfg.SetProperty(Environment.QueryDefaultCastLength, "20"); + cfg.SetProperty(Environment.QueryDefaultCastPrecision, "10"); + cfg.SetProperty(Environment.QueryDefaultCastScale, "3"); + var dialect = Dialect.Dialect.GetDialect(cfg.Properties); + + Assert.That(dialect.GetCastTypeName(SqlTypeFactory.Decimal), Does.Match(@"^[^(]*(\(\s*10\s*,\s*3\s*\))?\s*$"), "decimal"); + Assert.That(dialect.GetCastTypeName(new SqlType(DbType.String)), Does.Match(@"^[^(]*(\(\s*20\s*\))?\s*$"), "string"); + } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/DriverTest/FirebirdClientDriverFixture.cs b/src/NHibernate.Test/DriverTest/FirebirdClientDriverFixture.cs index 027fa2e9f77..efaa46b7d2e 100644 --- a/src/NHibernate.Test/DriverTest/FirebirdClientDriverFixture.cs +++ b/src/NHibernate.Test/DriverTest/FirebirdClientDriverFixture.cs @@ -76,7 +76,7 @@ public void ConnectionPooling_OpenThenCloseTwoAtTheSameTime_TowConnectionsArePoo public void AdjustCommand_StringParametersWithinConditionalSelect_ThenParameterIsWrappedByAVarcharCastStatement() { MakeDriver(); - var cmd = BuildSelectCaseCommand(SqlTypeFactory.GetString(0)); + var cmd = BuildSelectCaseCommand(SqlTypeFactory.GetString(255)); _driver.AdjustCommand(cmd); @@ -100,7 +100,7 @@ public void AdjustCommand_IntParametersWithinConditionalSelect_ThenParameterIsWr public void AdjustCommand_ParameterWithinSelectConcat_ParameterIsCasted() { MakeDriver(); - var cmd = BuildSelectConcatCommand(SqlTypeFactory.GetString(0)); + var cmd = BuildSelectConcatCommand(SqlTypeFactory.GetString(255)); _driver.AdjustCommand(cmd); @@ -112,7 +112,7 @@ public void AdjustCommand_ParameterWithinSelectConcat_ParameterIsCasted() public void AdjustCommand_ParameterWithinSelectAddFunction_ParameterIsCasted() { MakeDriver(); - var cmd = BuildSelectAddCommand(SqlTypeFactory.GetString(0)); + var cmd = BuildSelectAddCommand(SqlTypeFactory.GetString(255)); _driver.AdjustCommand(cmd); diff --git a/src/NHibernate/Cfg/Environment.cs b/src/NHibernate/Cfg/Environment.cs index f46e6674cb1..efc0b6dcab4 100644 --- a/src/NHibernate/Cfg/Environment.cs +++ b/src/NHibernate/Cfg/Environment.cs @@ -203,6 +203,24 @@ public static string Version public const string QueryModelRewriterFactory = "query.query_model_rewriter_factory"; + /// <summary> + /// Set the default length used in casting when the target type is length bound and + /// does not specify it. <c>4000</c> by default, automatically trim down according to dialect type registration. + /// </summary> + public const string QueryDefaultCastLength = "query.default_cast_length"; + + /// <summary> + /// Set the default precision used in casting when the target type is decimal and + /// does not specify it. <c>28</c> by default, automatically trim down according to dialect type registration. + /// </summary> + public const string QueryDefaultCastPrecision = "query.default_cast_precision"; + + /// <summary> + /// Set the default scale used in casting when the target type is decimal and + /// does not specify it. <c>10</c> by default, automatically trim down according to dialect type registration. + /// </summary> + public const string QueryDefaultCastScale = "query.default_cast_scale"; + /// <summary> /// This may need to be set to 3 if you are using the OdbcDriver with MS SQL Server 2008+. /// </summary> diff --git a/src/NHibernate/Dialect/Dialect.cs b/src/NHibernate/Dialect/Dialect.cs index ff97bce3220..a485c766107 100644 --- a/src/NHibernate/Dialect/Dialect.cs +++ b/src/NHibernate/Dialect/Dialect.cs @@ -199,6 +199,9 @@ private static Dialect InstantiateDialect(string dialectName, IDictionary<string /// <param name="settings">The configuration settings.</param> public virtual void Configure(IDictionary<string, string> settings) { + DefaultCastLength = PropertiesHelper.GetInt32(Environment.QueryDefaultCastLength, settings, 4000); + DefaultCastPrecision = PropertiesHelper.GetByte(Environment.QueryDefaultCastPrecision, settings, null) ?? 28; + DefaultCastScale = PropertiesHelper.GetByte(Environment.QueryDefaultCastScale, settings, null) ?? 10; } #endregion @@ -245,15 +248,48 @@ public virtual string GetLongestTypeName(DbType dbType) return _typeNames.GetLongest(dbType); } + protected int DefaultCastLength { get; set; } + protected byte DefaultCastPrecision { get; set; } + protected byte DefaultCastScale { get; set; } + /// <summary> /// Get the name of the database type appropriate for casting operations /// (via the CAST() SQL function) for the given <see cref="SqlType"/> typecode. /// </summary> /// <param name="sqlType">The <see cref="SqlType"/> typecode </param> /// <returns> The database type name </returns> - public virtual string GetCastTypeName(SqlType sqlType) + public virtual string GetCastTypeName(SqlType sqlType) => + GetCastTypeName(sqlType, _typeNames); + + /// <summary> + /// Get the name of the database type appropriate for casting operations + /// (via the CAST() SQL function) for the given <see cref="SqlType"/> typecode. + /// </summary> + /// <param name="sqlType">The <see cref="SqlType"/> typecode.</param> + /// <param name="castTypeNames">The source for type names.</param> + /// <returns>The database type name.</returns> + protected virtual string GetCastTypeName(SqlType sqlType, TypeNames castTypeNames) { - return GetTypeName(sqlType, Column.DefaultLength, Column.DefaultPrecision, Column.DefaultScale); + if (sqlType.LengthDefined || sqlType.PrecisionDefined || sqlType.ScaleDefined) + return castTypeNames.Get(sqlType.DbType, sqlType.Length, sqlType.Precision, sqlType.Scale); + switch (sqlType.DbType) + { + case DbType.Decimal: + // We cannot know if the user needs its digit after or before the dot, so use a configurable + // default. + return castTypeNames.Get(DbType.Decimal, 0, DefaultCastPrecision, DefaultCastScale); + case DbType.DateTime: + case DbType.DateTime2: + case DbType.DateTimeOffset: + case DbType.Time: + case DbType.Currency: + // Use default for these, dialects are supposed to map them to max capacity + return castTypeNames.Get(sqlType.DbType); + default: + // Other types are either length bound or not length/precision/scale bound. Otherwise they need to be + // handled previously. + return castTypeNames.Get(sqlType.DbType, DefaultCastLength, 0, 0); + } } /// <summary> diff --git a/src/NHibernate/Dialect/MySQLDialect.cs b/src/NHibernate/Dialect/MySQLDialect.cs index da1954f6601..fb913a92320 100644 --- a/src/NHibernate/Dialect/MySQLDialect.cs +++ b/src/NHibernate/Dialect/MySQLDialect.cs @@ -4,7 +4,6 @@ using System.Text; using NHibernate.Dialect.Function; using NHibernate.Dialect.Schema; -using NHibernate.Mapping; using NHibernate.SqlCommand; using NHibernate.SqlTypes; using NHibernate.Util; @@ -64,7 +63,7 @@ public MySQLDialect() RegisterColumnType(DbType.String, 16777215, "MEDIUMTEXT"); //todo: future: add compatibility with decimal??? //An unpacked fixed-point number. Behaves like a CHAR column; - //�unpacked� means the number is stored as a string, using one character for each digit of the value. + //“unpacked” means the number is stored as a string, using one character for each digit of the value. //M is the total number of digits and D is the number of digits after the decimal point //DECIMAL[(M[,D])] [UNSIGNED] [ZEROFILL] @@ -494,15 +493,8 @@ protected void RegisterCastType(DbType code, int capacity, string name) /// </summary> /// <param name="sqlType">The <see cref="SqlType"/> typecode </param> /// <returns> The database type name </returns> - public override string GetCastTypeName(SqlType sqlType) - { - string result = castTypeNames.Get(sqlType.DbType, Column.DefaultLength, Column.DefaultPrecision, Column.DefaultScale); - if (result == null) - { - throw new HibernateException(string.Format("No CAST() type mapping for SqlType {0}", sqlType)); - } - return result; - } + public override string GetCastTypeName(SqlType sqlType) => + GetCastTypeName(sqlType, castTypeNames); public override long TimestampResolutionInTicks { @@ -552,4 +544,4 @@ public override long TimestampResolutionInTicks #endregion } -} \ No newline at end of file +} diff --git a/src/NHibernate/Driver/FirebirdClientDriver.cs b/src/NHibernate/Driver/FirebirdClientDriver.cs index 32fcb94853e..730d11a388d 100644 --- a/src/NHibernate/Driver/FirebirdClientDriver.cs +++ b/src/NHibernate/Driver/FirebirdClientDriver.cs @@ -40,6 +40,12 @@ public FirebirdClientDriver() } + public override void Configure(IDictionary<string, string> settings) + { + base.Configure(settings); + _fbDialect.Configure(settings); + } + public override bool UseNamedPrefixInSql { get { return true; } @@ -72,12 +78,13 @@ public override DbCommand GenerateCommand(CommandType type, SqlString sqlString, if (!string.IsNullOrWhiteSpace(expWithParams)) { var candidates = GetCastCandidates(expWithParams); - var castParams = from DbParameter p in command.Parameters - where candidates.Contains(p.ParameterName) - select p; - foreach (var param in castParams) + + var index = 0; + foreach (DbParameter p in command.Parameters) { - TypeCastParam(param, command); + if (candidates.Contains(p.ParameterName)) + TypeCastParam(p, command, parameterTypes[index]); + index++; } } @@ -99,15 +106,26 @@ private HashSet<string> GetCastCandidates(string statement) return new HashSet<string>(candidates); } - private void TypeCastParam(DbParameter param, DbCommand command) + private void TypeCastParam(DbParameter param, DbCommand command, SqlType sqlType) { - var castType = GetFbTypeFromDbType(param.DbType); - command.CommandText = command.CommandText.ReplaceWholeWord(param.ParameterName, string.Format("cast({0} as {1})", param.ParameterName, castType)); + var castType = GetFbTypeForParam(sqlType); + command.CommandText = command.CommandText.ReplaceWholeWord( + param.ParameterName, + $"cast({param.ParameterName} as {castType})"); } - private string GetFbTypeFromDbType(DbType dbType) + private string GetFbTypeForParam(SqlType sqlType) { - return _fbDialect.GetCastTypeName(new SqlType(dbType)); + if (sqlType.LengthDefined) + switch (sqlType.DbType) + { + case DbType.AnsiString: + case DbType.String: + // Use default length instead for supporting like expressions requiring longer length. + sqlType = new SqlType(sqlType.DbType); + break; + } + return _fbDialect.GetCastTypeName(sqlType); } private static volatile MethodInfo _clearPool; diff --git a/src/NHibernate/nhibernate-configuration.xsd b/src/NHibernate/nhibernate-configuration.xsd index 7a837e28909..261f29d2a2e 100644 --- a/src/NHibernate/nhibernate-configuration.xsd +++ b/src/NHibernate/nhibernate-configuration.xsd @@ -169,6 +169,30 @@ </xs:documentation> </xs:annotation> </xs:enumeration> + <xs:enumeration value="query.default_cast_length"> + <xs:annotation> + <xs:documentation> + Set the default length used in casting when the target type is length bound and + does not specify it. 4000 by default, automatically trim down according to dialect type registration. + </xs:documentation> + </xs:annotation> + </xs:enumeration> + <xs:enumeration value="query.default_cast_precision"> + <xs:annotation> + <xs:documentation> + Set the default precision used in casting when the target type is decimal and + does not specify it. 28 by default, automatically trim down according to dialect type registration. + </xs:documentation> + </xs:annotation> + </xs:enumeration> + <xs:enumeration value="query.default_cast_scale"> + <xs:annotation> + <xs:documentation> + Set the default scale used in casting when the target type is decimal and + does not specify it. 10 by default, automatically trim down according to dialect type registration. + </xs:documentation> + </xs:annotation> + </xs:enumeration> </xs:restriction> </xs:simpleType> </xs:attribute> From 7b6ff8e57d5ceacc897c355be212d836f8cb3383 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Delaporte?= <fredericdelaporte@free.fr> Date: Thu, 5 Oct 2017 14:59:28 +0200 Subject: [PATCH 3/3] fixup !NH-4088 - Fix GetCastTypeName * Oracle double special case * More tests * Tests fixes --- .../Async/Criteria/ProjectionsTest.cs | 10 +- .../DriverTest/FirebirdClientDriverFixture.cs | 130 +++++------ .../Criteria/ProjectionsTest.cs | 10 +- .../DialectTest/DialectFixture.cs | 4 + .../DriverTest/FirebirdClientDriverFixture.cs | 216 +++++++++--------- src/NHibernate/Dialect/Dialect.cs | 4 +- .../Dialect/SybaseSQLAnywhere10Dialect.cs | 2 +- 7 files changed, 200 insertions(+), 176 deletions(-) diff --git a/src/NHibernate.Test/Async/Criteria/ProjectionsTest.cs b/src/NHibernate.Test/Async/Criteria/ProjectionsTest.cs index 7b00a0b4bd3..baacbc9fbb1 100644 --- a/src/NHibernate.Test/Async/Criteria/ProjectionsTest.cs +++ b/src/NHibernate.Test/Async/Criteria/ProjectionsTest.cs @@ -11,8 +11,10 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Text.RegularExpressions; using NHibernate.Criterion; using NHibernate.Dialect; +using NHibernate.SqlTypes; using NHibernate.Type; using NUnit.Framework; @@ -105,6 +107,11 @@ public async Task UsingSqlFunctions_Concat_WithCastAsync() [Test] public async Task CastWithLengthAsync() { + if (Regex.IsMatch(Dialect.GetCastTypeName(SqlTypeFactory.GetString(3)), @"^[^(]*$")) + { + Assert.Ignore($"Dialect {Dialect} does not seem to handle string length in cast"); + } + using (var s = OpenSession()) { try @@ -120,8 +127,7 @@ public async Task CastWithLengthAsync() } catch (Exception e) { - if (!e.Message.Contains("truncation") && - (e.InnerException == null || !e.InnerException.Message.Contains("truncation"))) + if (e.InnerException == null || !e.InnerException.Message.Contains("truncation")) throw; } } diff --git a/src/NHibernate.Test/Async/DriverTest/FirebirdClientDriverFixture.cs b/src/NHibernate.Test/Async/DriverTest/FirebirdClientDriverFixture.cs index bc2f4157f98..734866bda0c 100644 --- a/src/NHibernate.Test/Async/DriverTest/FirebirdClientDriverFixture.cs +++ b/src/NHibernate.Test/Async/DriverTest/FirebirdClientDriverFixture.cs @@ -26,73 +26,74 @@ public class FirebirdClientDriverFixtureAsync private string _connectionString; private FirebirdClientDriver _driver; + [OneTimeSetUp] + public void OneTimeSetup() + { + var cfg = TestConfigurationHelper.GetDefaultConfiguration(); + + var dlct = cfg.GetProperty("dialect"); + if (!dlct.Contains("Firebird")) + Assert.Ignore("Applies only to Firebird"); + + _driver = new FirebirdClientDriver(); + _driver.Configure(cfg.Properties); + _connectionString = cfg.GetProperty("connection.connection_string"); + } + [Test] public async Task ConnectionPooling_OpenThenCloseThenOpenAnotherOne_OnlyOneConnectionIsPooledAsync() { - MakeDriver(); - _driver.ClearPool(_connectionString); var allreadyEstablished = await (GetEstablishedConnectionsAsync()); - var connection1 = MakeConnection(); - var connection2 = MakeConnection(); - - //open first connection - await (connection1.OpenAsync()); - await (VerifyCountOfEstablishedConnectionsIsAsync(allreadyEstablished + 1, "After first open")); + using (var connection1 = MakeConnection()) + using (var connection2 = MakeConnection()) + { + //open first connection + await (connection1.OpenAsync()); + await (VerifyCountOfEstablishedConnectionsIsAsync(allreadyEstablished + 1, "After first open")); - //return it to the pool - connection1.Close(); - await (VerifyCountOfEstablishedConnectionsIsAsync(allreadyEstablished + 1, "After first close")); + //return it to the pool + connection1.Close(); + await (VerifyCountOfEstablishedConnectionsIsAsync(allreadyEstablished + 1, "After first close")); - //open the second connection - await (connection2.OpenAsync()); - await (VerifyCountOfEstablishedConnectionsIsAsync(allreadyEstablished + 1, "After second open")); + //open the second connection + await (connection2.OpenAsync()); + await (VerifyCountOfEstablishedConnectionsIsAsync(allreadyEstablished + 1, "After second open")); - //return it to the pool - connection2.Close(); - await (VerifyCountOfEstablishedConnectionsIsAsync(allreadyEstablished + 1, "After second close")); + //return it to the pool + connection2.Close(); + await (VerifyCountOfEstablishedConnectionsIsAsync(allreadyEstablished + 1, "After second close")); + } } [Test] public async Task ConnectionPooling_OpenThenCloseTwoAtTheSameTime_TowConnectionsArePooledAsync() { - MakeDriver(); - _driver.ClearPool(_connectionString); var allreadyEstablished = await (GetEstablishedConnectionsAsync()); - var connection1 = MakeConnection(); - var connection2 = MakeConnection(); - - //open first connection - await (connection1.OpenAsync()); - await (VerifyCountOfEstablishedConnectionsIsAsync(allreadyEstablished + 1, "After first open")); - - //open second one - await (connection2.OpenAsync()); - await (VerifyCountOfEstablishedConnectionsIsAsync(allreadyEstablished + 2, "After second open")); - - //return connection1 to the pool - connection1.Close(); - await (VerifyCountOfEstablishedConnectionsIsAsync(allreadyEstablished + 2, "After first close")); + using (var connection1 = MakeConnection()) + using (var connection2 = MakeConnection()) + { + //open first connection + await (connection1.OpenAsync()); + await (VerifyCountOfEstablishedConnectionsIsAsync(allreadyEstablished + 1, "After first open")); - //return connection2 to the pool - connection2.Close(); - await (VerifyCountOfEstablishedConnectionsIsAsync(allreadyEstablished + 2, "After second close")); - } + //open second one + await (connection2.OpenAsync()); + await (VerifyCountOfEstablishedConnectionsIsAsync(allreadyEstablished + 2, "After second open")); - private void MakeDriver() - { - var cfg = TestConfigurationHelper.GetDefaultConfiguration(); - var dlct = cfg.GetProperty("dialect"); - if (!dlct.Contains("Firebird")) - Assert.Ignore("Applies only to Firebird"); + //return connection1 to the pool + connection1.Close(); + await (VerifyCountOfEstablishedConnectionsIsAsync(allreadyEstablished + 2, "After first close")); - _driver = new FirebirdClientDriver(); - _connectionString = cfg.GetProperty("connection.connection_string"); + //return connection2 to the pool + connection2.Close(); + await (VerifyCountOfEstablishedConnectionsIsAsync(allreadyEstablished + 2, "After second close")); + } } private DbConnection MakeConnection() @@ -125,14 +126,14 @@ private DbConnection MakeConnection() private DbCommand BuildSelectCaseCommand(SqlType paramType) { var sqlString = new SqlStringBuilder() - .Add("select (case when col = ") - .AddParameter() - .Add(" then ") - .AddParameter() - .Add(" else ") - .AddParameter() - .Add(" end) from table") - .ToSqlString(); + .Add("select (case when col = ") + .AddParameter() + .Add(" then ") + .AddParameter() + .Add(" else ") + .AddParameter() + .Add(" end) from table") + .ToSqlString(); return _driver.GenerateCommand(CommandType.Text, sqlString, new[] { paramType, paramType, paramType }); } @@ -140,12 +141,12 @@ private DbCommand BuildSelectCaseCommand(SqlType paramType) private DbCommand BuildSelectConcatCommand(SqlType paramType) { var sqlString = new SqlStringBuilder() - .Add("select col || ") - .AddParameter() - .Add(" || ") - .Add("col ") - .Add("from table") - .ToSqlString(); + .Add("select col || ") + .AddParameter() + .Add(" || ") + .Add("col ") + .Add("from table") + .ToSqlString(); return _driver.GenerateCommand(CommandType.Text, sqlString, new[] { paramType }); } @@ -153,10 +154,10 @@ private DbCommand BuildSelectConcatCommand(SqlType paramType) private DbCommand BuildSelectAddCommand(SqlType paramType) { var sqlString = new SqlStringBuilder() - .Add("select col + ") - .AddParameter() - .Add(" from table") - .ToSqlString(); + .Add("select col + ") + .AddParameter() + .Add(" from table") + .ToSqlString(); return _driver.GenerateCommand(CommandType.Text, sqlString, new[] { paramType }); } @@ -172,6 +173,7 @@ private DbCommand BuildInsertWithParamsInSelectCommand(SqlType paramType) return _driver.GenerateCommand(CommandType.Text, sqlString, new[] { paramType }); } + private DbCommand BuildInsertWithParamsInSelectCommandWithSelectInColumnName(SqlType paramType) { var sqlString = new SqlStringBuilder() @@ -184,7 +186,7 @@ private DbCommand BuildInsertWithParamsInSelectCommandWithSelectInColumnName(Sql return _driver.GenerateCommand(CommandType.Text, sqlString, new[] { paramType }); } - private DbCommand BuildInsertWithParamsInSelectCommandWithWhereInColumnName(SqlType paramType) + private DbCommand BuildInsertWithParamsInSelectCommandWithWhereInColumnName(SqlType paramType) { var sqlString = new SqlStringBuilder() .Add("insert into table1 (col1_where_aaa) ") diff --git a/src/NHibernate.Test/Criteria/ProjectionsTest.cs b/src/NHibernate.Test/Criteria/ProjectionsTest.cs index 165c12715cb..f4d928f6d9f 100644 --- a/src/NHibernate.Test/Criteria/ProjectionsTest.cs +++ b/src/NHibernate.Test/Criteria/ProjectionsTest.cs @@ -1,8 +1,10 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Text.RegularExpressions; using NHibernate.Criterion; using NHibernate.Dialect; +using NHibernate.SqlTypes; using NHibernate.Type; using NUnit.Framework; @@ -94,6 +96,11 @@ public void UsingSqlFunctions_Concat_WithCast() [Test] public void CastWithLength() { + if (Regex.IsMatch(Dialect.GetCastTypeName(SqlTypeFactory.GetString(3)), @"^[^(]*$")) + { + Assert.Ignore($"Dialect {Dialect} does not seem to handle string length in cast"); + } + using (var s = OpenSession()) { try @@ -109,8 +116,7 @@ public void CastWithLength() } catch (Exception e) { - if (!e.Message.Contains("truncation") && - (e.InnerException == null || !e.InnerException.Message.Contains("truncation"))) + if (e.InnerException == null || !e.InnerException.Message.Contains("truncation")) throw; } } diff --git a/src/NHibernate.Test/DialectTest/DialectFixture.cs b/src/NHibernate.Test/DialectTest/DialectFixture.cs index 1895f9be13b..17899c1e88f 100644 --- a/src/NHibernate.Test/DialectTest/DialectFixture.cs +++ b/src/NHibernate.Test/DialectTest/DialectFixture.cs @@ -178,6 +178,7 @@ public void GetDecimalTypeName() var dialect = Dialect.Dialect.GetDialect(cfg.Properties); Assert.That(dialect.GetTypeName(SqlTypeFactory.GetSqlType(DbType.Decimal, 40, 40)), Does.Not.Contain("40"), "oversized decimal"); + // This regex tests wether the type is qualified with expected length/precision/scale or not qualified at all. Assert.That(dialect.GetTypeName(SqlTypeFactory.GetSqlType(DbType.Decimal, 3, 2)), Does.Match(@"^[^(]*(\(\s*3\s*,\s*2\s*\))?\s*$"), "small decimal"); } @@ -190,8 +191,11 @@ public void GetTypeCastName() cfg.SetProperty(Environment.QueryDefaultCastScale, "3"); var dialect = Dialect.Dialect.GetDialect(cfg.Properties); + // Those regex test wether the type is qualified with expected length/precision/scale or not qualified at all. Assert.That(dialect.GetCastTypeName(SqlTypeFactory.Decimal), Does.Match(@"^[^(]*(\(\s*10\s*,\s*3\s*\))?\s*$"), "decimal"); + Assert.That(dialect.GetCastTypeName(SqlTypeFactory.GetSqlType(DbType.Decimal, 12, 4)), Does.Match(@"^[^(]*(\(\s*12\s*,\s*4\s*\))?\s*$"), "decimal(12,4)"); Assert.That(dialect.GetCastTypeName(new SqlType(DbType.String)), Does.Match(@"^[^(]*(\(\s*20\s*\))?\s*$"), "string"); + Assert.That(dialect.GetCastTypeName(SqlTypeFactory.GetString(25)), Does.Match(@"^[^(]*(\(\s*25\s*\))?\s*$"), "string(25)"); } } } diff --git a/src/NHibernate.Test/DriverTest/FirebirdClientDriverFixture.cs b/src/NHibernate.Test/DriverTest/FirebirdClientDriverFixture.cs index efaa46b7d2e..2bd4177e8db 100644 --- a/src/NHibernate.Test/DriverTest/FirebirdClientDriverFixture.cs +++ b/src/NHibernate.Test/DriverTest/FirebirdClientDriverFixture.cs @@ -14,157 +14,160 @@ public class FirebirdClientDriverFixture private string _connectionString; private FirebirdClientDriver _driver; + [OneTimeSetUp] + public void OneTimeSetup() + { + var cfg = TestConfigurationHelper.GetDefaultConfiguration(); + + var dlct = cfg.GetProperty("dialect"); + if (!dlct.Contains("Firebird")) + Assert.Ignore("Applies only to Firebird"); + + _driver = new FirebirdClientDriver(); + _driver.Configure(cfg.Properties); + _connectionString = cfg.GetProperty("connection.connection_string"); + } + [Test] public void ConnectionPooling_OpenThenCloseThenOpenAnotherOne_OnlyOneConnectionIsPooled() { - MakeDriver(); - _driver.ClearPool(_connectionString); var allreadyEstablished = GetEstablishedConnections(); - var connection1 = MakeConnection(); - var connection2 = MakeConnection(); - - //open first connection - connection1.Open(); - VerifyCountOfEstablishedConnectionsIs(allreadyEstablished + 1, "After first open"); + using (var connection1 = MakeConnection()) + using (var connection2 = MakeConnection()) + { + //open first connection + connection1.Open(); + VerifyCountOfEstablishedConnectionsIs(allreadyEstablished + 1, "After first open"); - //return it to the pool - connection1.Close(); - VerifyCountOfEstablishedConnectionsIs(allreadyEstablished + 1, "After first close"); + //return it to the pool + connection1.Close(); + VerifyCountOfEstablishedConnectionsIs(allreadyEstablished + 1, "After first close"); - //open the second connection - connection2.Open(); - VerifyCountOfEstablishedConnectionsIs(allreadyEstablished + 1, "After second open"); + //open the second connection + connection2.Open(); + VerifyCountOfEstablishedConnectionsIs(allreadyEstablished + 1, "After second open"); - //return it to the pool - connection2.Close(); - VerifyCountOfEstablishedConnectionsIs(allreadyEstablished + 1, "After second close"); + //return it to the pool + connection2.Close(); + VerifyCountOfEstablishedConnectionsIs(allreadyEstablished + 1, "After second close"); + } } [Test] public void ConnectionPooling_OpenThenCloseTwoAtTheSameTime_TowConnectionsArePooled() { - MakeDriver(); - _driver.ClearPool(_connectionString); var allreadyEstablished = GetEstablishedConnections(); - var connection1 = MakeConnection(); - var connection2 = MakeConnection(); - - //open first connection - connection1.Open(); - VerifyCountOfEstablishedConnectionsIs(allreadyEstablished + 1, "After first open"); + using (var connection1 = MakeConnection()) + using (var connection2 = MakeConnection()) + { + //open first connection + connection1.Open(); + VerifyCountOfEstablishedConnectionsIs(allreadyEstablished + 1, "After first open"); - //open second one - connection2.Open(); - VerifyCountOfEstablishedConnectionsIs(allreadyEstablished + 2, "After second open"); + //open second one + connection2.Open(); + VerifyCountOfEstablishedConnectionsIs(allreadyEstablished + 2, "After second open"); - //return connection1 to the pool - connection1.Close(); - VerifyCountOfEstablishedConnectionsIs(allreadyEstablished + 2, "After first close"); + //return connection1 to the pool + connection1.Close(); + VerifyCountOfEstablishedConnectionsIs(allreadyEstablished + 2, "After first close"); - //return connection2 to the pool - connection2.Close(); - VerifyCountOfEstablishedConnectionsIs(allreadyEstablished + 2, "After second close"); + //return connection2 to the pool + connection2.Close(); + VerifyCountOfEstablishedConnectionsIs(allreadyEstablished + 2, "After second close"); + } } [Test] public void AdjustCommand_StringParametersWithinConditionalSelect_ThenParameterIsWrappedByAVarcharCastStatement() { - MakeDriver(); - var cmd = BuildSelectCaseCommand(SqlTypeFactory.GetString(255)); - - _driver.AdjustCommand(cmd); + using (var cmd = BuildSelectCaseCommand(SqlTypeFactory.GetString(255))) + { + _driver.AdjustCommand(cmd); - var expectedCommandTxt = "select (case when col = @p0 then cast(@p1 as VARCHAR(255)) else cast(@p2 as VARCHAR(255)) end) from table"; - Assert.That(cmd.CommandText, Is.EqualTo(expectedCommandTxt)); + var expectedCommandTxt = + "select (case when col = @p0 then cast(@p1 as VARCHAR(4000)) else cast(@p2 as VARCHAR(4000)) end) from table"; + Assert.That(cmd.CommandText, Is.EqualTo(expectedCommandTxt)); + } } [Test] public void AdjustCommand_IntParametersWithinConditionalSelect_ThenParameterIsWrappedByAnIntCastStatement() { - MakeDriver(); - var cmd = BuildSelectCaseCommand(SqlTypeFactory.Int32); - - _driver.AdjustCommand(cmd); + using (var cmd = BuildSelectCaseCommand(SqlTypeFactory.Int32)) + { + _driver.AdjustCommand(cmd); - var expectedCommandTxt = "select (case when col = @p0 then cast(@p1 as INTEGER) else cast(@p2 as INTEGER) end) from table"; - Assert.That(cmd.CommandText, Is.EqualTo(expectedCommandTxt)); + var expectedCommandTxt = + "select (case when col = @p0 then cast(@p1 as INTEGER) else cast(@p2 as INTEGER) end) from table"; + Assert.That(cmd.CommandText, Is.EqualTo(expectedCommandTxt)); + } } [Test] public void AdjustCommand_ParameterWithinSelectConcat_ParameterIsCasted() { - MakeDriver(); - var cmd = BuildSelectConcatCommand(SqlTypeFactory.GetString(255)); - - _driver.AdjustCommand(cmd); + using (var cmd = BuildSelectConcatCommand(SqlTypeFactory.GetString(255))) + { + _driver.AdjustCommand(cmd); - var expected = "select col || cast(@p0 as VARCHAR(255)) || col from table"; - Assert.That(cmd.CommandText, Is.EqualTo(expected)); + var expected = "select col || cast(@p0 as VARCHAR(4000)) || col from table"; + Assert.That(cmd.CommandText, Is.EqualTo(expected)); + } } [Test] public void AdjustCommand_ParameterWithinSelectAddFunction_ParameterIsCasted() { - MakeDriver(); - var cmd = BuildSelectAddCommand(SqlTypeFactory.GetString(255)); - - _driver.AdjustCommand(cmd); + using (var cmd = BuildSelectAddCommand(SqlTypeFactory.GetString(255))) + { + _driver.AdjustCommand(cmd); - var expected = "select col + cast(@p0 as VARCHAR(255)) from table"; - Assert.That(cmd.CommandText, Is.EqualTo(expected)); + var expected = "select col + cast(@p0 as VARCHAR(4000)) from table"; + Assert.That(cmd.CommandText, Is.EqualTo(expected)); + } } [Test] public void AdjustCommand_InsertWithParamsInSelect_ParameterIsCasted() { - MakeDriver(); - var cmd = BuildInsertWithParamsInSelectCommand(SqlTypeFactory.Int32); - - _driver.AdjustCommand(cmd); + using (var cmd = BuildInsertWithParamsInSelectCommand(SqlTypeFactory.Int32)) + { + _driver.AdjustCommand(cmd); - var expected = "insert into table1 (col1, col2) select col1, cast(@p0 as INTEGER) from table2"; - Assert.That(cmd.CommandText, Is.EqualTo(expected)); + var expected = "insert into table1 (col1, col2) select col1, cast(@p0 as INTEGER) from table2"; + Assert.That(cmd.CommandText, Is.EqualTo(expected)); + } } [Test] public void AdjustCommand_InsertWithParamsInSelect_ParameterIsNotCasted_WhenColumnNameContainsSelect() { - MakeDriver(); - var cmd = BuildInsertWithParamsInSelectCommandWithSelectInColumnName(SqlTypeFactory.Int32); - - _driver.AdjustCommand(cmd); + using (var cmd = BuildInsertWithParamsInSelectCommandWithSelectInColumnName(SqlTypeFactory.Int32)) + { + _driver.AdjustCommand(cmd); - var expected = "insert into table1 (col1_select_aaa) values(@p0) from table2"; - Assert.That(cmd.CommandText, Is.EqualTo(expected)); + var expected = "insert into table1 (col1_select_aaa) values(@p0) from table2"; + Assert.That(cmd.CommandText, Is.EqualTo(expected)); + } } [Test] public void AdjustCommand_InsertWithParamsInSelect_ParameterIsNotCasted_WhenColumnNameContainsWhere() { - MakeDriver(); - var cmd = BuildInsertWithParamsInSelectCommandWithWhereInColumnName(SqlTypeFactory.Int32); - - _driver.AdjustCommand(cmd); - - var expected = "insert into table1 (col1_where_aaa) values(@p0) from table2"; - Assert.That(cmd.CommandText, Is.EqualTo(expected)); - } - - private void MakeDriver() - { - var cfg = TestConfigurationHelper.GetDefaultConfiguration(); - var dlct = cfg.GetProperty("dialect"); - if (!dlct.Contains("Firebird")) - Assert.Ignore("Applies only to Firebird"); + using (var cmd = BuildInsertWithParamsInSelectCommandWithWhereInColumnName(SqlTypeFactory.Int32)) + { + _driver.AdjustCommand(cmd); - _driver = new FirebirdClientDriver(); - _connectionString = cfg.GetProperty("connection.connection_string"); + var expected = "insert into table1 (col1_where_aaa) values(@p0) from table2"; + Assert.That(cmd.CommandText, Is.EqualTo(expected)); + } } private DbConnection MakeConnection() @@ -197,14 +200,14 @@ private int GetEstablishedConnections() private DbCommand BuildSelectCaseCommand(SqlType paramType) { var sqlString = new SqlStringBuilder() - .Add("select (case when col = ") - .AddParameter() - .Add(" then ") - .AddParameter() - .Add(" else ") - .AddParameter() - .Add(" end) from table") - .ToSqlString(); + .Add("select (case when col = ") + .AddParameter() + .Add(" then ") + .AddParameter() + .Add(" else ") + .AddParameter() + .Add(" end) from table") + .ToSqlString(); return _driver.GenerateCommand(CommandType.Text, sqlString, new[] { paramType, paramType, paramType }); } @@ -212,12 +215,12 @@ private DbCommand BuildSelectCaseCommand(SqlType paramType) private DbCommand BuildSelectConcatCommand(SqlType paramType) { var sqlString = new SqlStringBuilder() - .Add("select col || ") - .AddParameter() - .Add(" || ") - .Add("col ") - .Add("from table") - .ToSqlString(); + .Add("select col || ") + .AddParameter() + .Add(" || ") + .Add("col ") + .Add("from table") + .ToSqlString(); return _driver.GenerateCommand(CommandType.Text, sqlString, new[] { paramType }); } @@ -225,10 +228,10 @@ private DbCommand BuildSelectConcatCommand(SqlType paramType) private DbCommand BuildSelectAddCommand(SqlType paramType) { var sqlString = new SqlStringBuilder() - .Add("select col + ") - .AddParameter() - .Add(" from table") - .ToSqlString(); + .Add("select col + ") + .AddParameter() + .Add(" from table") + .ToSqlString(); return _driver.GenerateCommand(CommandType.Text, sqlString, new[] { paramType }); } @@ -244,6 +247,7 @@ private DbCommand BuildInsertWithParamsInSelectCommand(SqlType paramType) return _driver.GenerateCommand(CommandType.Text, sqlString, new[] { paramType }); } + private DbCommand BuildInsertWithParamsInSelectCommandWithSelectInColumnName(SqlType paramType) { var sqlString = new SqlStringBuilder() @@ -256,7 +260,7 @@ private DbCommand BuildInsertWithParamsInSelectCommandWithSelectInColumnName(Sql return _driver.GenerateCommand(CommandType.Text, sqlString, new[] { paramType }); } - private DbCommand BuildInsertWithParamsInSelectCommandWithWhereInColumnName(SqlType paramType) + private DbCommand BuildInsertWithParamsInSelectCommandWithWhereInColumnName(SqlType paramType) { var sqlString = new SqlStringBuilder() .Add("insert into table1 (col1_where_aaa) ") diff --git a/src/NHibernate/Dialect/Dialect.cs b/src/NHibernate/Dialect/Dialect.cs index a485c766107..0cb0d53c605 100644 --- a/src/NHibernate/Dialect/Dialect.cs +++ b/src/NHibernate/Dialect/Dialect.cs @@ -275,9 +275,11 @@ protected virtual string GetCastTypeName(SqlType sqlType, TypeNames castTypeName switch (sqlType.DbType) { case DbType.Decimal: + // Oracle dialect defines precision and scale for double, because it uses number instead of binary_double. + case DbType.Double: // We cannot know if the user needs its digit after or before the dot, so use a configurable // default. - return castTypeNames.Get(DbType.Decimal, 0, DefaultCastPrecision, DefaultCastScale); + return castTypeNames.Get(sqlType.DbType, 0, DefaultCastPrecision, DefaultCastScale); case DbType.DateTime: case DbType.DateTime2: case DbType.DateTimeOffset: diff --git a/src/NHibernate/Dialect/SybaseSQLAnywhere10Dialect.cs b/src/NHibernate/Dialect/SybaseSQLAnywhere10Dialect.cs index 7b40051ef65..5758cd5da31 100644 --- a/src/NHibernate/Dialect/SybaseSQLAnywhere10Dialect.cs +++ b/src/NHibernate/Dialect/SybaseSQLAnywhere10Dialect.cs @@ -92,7 +92,7 @@ protected virtual void RegisterNumericTypeMappings() RegisterColumnType(DbType.Double, "DOUBLE"); RegisterColumnType(DbType.Decimal, "NUMERIC(19,5)"); // Precision ranges from 0-127 // Anywhere max precision is 127, but .Net is limited to 28-29. - RegisterColumnType(DbType.Decimal, 38, "NUMERIC($p, $s)"); // Precision ranges from 0-127 + RegisterColumnType(DbType.Decimal, 28, "NUMERIC($p, $s)"); // Precision ranges from 0-127 } protected virtual void RegisterDateTimeTypeMappings()