Skip to content

Commit bbd7772

Browse files
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
1 parent 19a914e commit bbd7772

File tree

10 files changed

+250
-31
lines changed

10 files changed

+250
-31
lines changed

src/NHibernate.Test/Async/Criteria/ProjectionsTest.cs

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,12 @@
88
//------------------------------------------------------------------------------
99

1010

11+
using System;
1112
using System.Collections;
1213
using System.Collections.Generic;
1314
using NHibernate.Criterion;
1415
using NHibernate.Dialect;
16+
using NHibernate.Type;
1517
using NUnit.Framework;
1618

1719
namespace NHibernate.Test.Criteria
@@ -100,6 +102,59 @@ public async Task UsingSqlFunctions_Concat_WithCastAsync()
100102
}
101103
}
102104

105+
[Test]
106+
public async Task CastWithLengthAsync()
107+
{
108+
using (var s = OpenSession())
109+
{
110+
try
111+
{
112+
var shortName = await (s
113+
.CreateCriteria<Student>()
114+
.SetProjection(
115+
Projections.Cast(
116+
TypeFactory.GetStringType(3),
117+
Projections.Property("Name")))
118+
.UniqueResultAsync<string>());
119+
Assert.That(shortName, Is.EqualTo("aye"));
120+
}
121+
catch (Exception e)
122+
{
123+
if (!e.Message.Contains("truncation") &&
124+
(e.InnerException == null || !e.InnerException.Message.Contains("truncation")))
125+
throw;
126+
}
127+
}
128+
}
129+
130+
[Test]
131+
public async Task CastWithPrecisionScaleAsync()
132+
{
133+
if (TestDialect.HasBrokenDecimalType)
134+
Assert.Ignore("Dialect does not correctly handle decimal.");
135+
136+
using (var s = OpenSession())
137+
{
138+
var value = await (s
139+
.CreateCriteria<Student>()
140+
.SetProjection(
141+
Projections.Cast(
142+
TypeFactory.Basic("decimal(18,9)"),
143+
Projections.Constant(123456789.123456789m, TypeFactory.Basic("decimal(18,9)"))))
144+
.UniqueResultAsync<decimal>());
145+
Assert.That(value, Is.EqualTo(123456789.123456789m), "Same type cast");
146+
147+
value = await (s
148+
.CreateCriteria<Student>()
149+
.SetProjection(
150+
Projections.Cast(
151+
TypeFactory.Basic("decimal(18,7)"),
152+
Projections.Constant(123456789.987654321m, TypeFactory.Basic("decimal(18,9)"))))
153+
.UniqueResultAsync<decimal>());
154+
Assert.That(value, Is.EqualTo(123456789.9876543m), "Reduced scale cast");
155+
}
156+
}
157+
103158
[Test]
104159
public async Task CanUseParametersWithProjectionsAsync()
105160
{

src/NHibernate.Test/Async/DialectTest/DialectFixture.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
using System;
1212
using System.Collections.Generic;
1313
using System.Data;
14-
using System.Data.Common;
1514
using NHibernate.Dialect;
1615
using NHibernate.Driver;
1716
using NHibernate.Engine;
@@ -79,4 +78,4 @@ public async Task CurrentTimestampSelectionAsync()
7978
}
8079
}
8180
}
82-
}
81+
}

src/NHibernate.Test/Criteria/ProjectionsTest.cs

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1+
using System;
12
using System.Collections;
23
using System.Collections.Generic;
34
using NHibernate.Criterion;
45
using NHibernate.Dialect;
6+
using NHibernate.Type;
57
using NUnit.Framework;
68

79
namespace NHibernate.Test.Criteria
@@ -89,6 +91,59 @@ public void UsingSqlFunctions_Concat_WithCast()
8991
}
9092
}
9193

94+
[Test]
95+
public void CastWithLength()
96+
{
97+
using (var s = OpenSession())
98+
{
99+
try
100+
{
101+
var shortName = s
102+
.CreateCriteria<Student>()
103+
.SetProjection(
104+
Projections.Cast(
105+
TypeFactory.GetStringType(3),
106+
Projections.Property("Name")))
107+
.UniqueResult<string>();
108+
Assert.That(shortName, Is.EqualTo("aye"));
109+
}
110+
catch (Exception e)
111+
{
112+
if (!e.Message.Contains("truncation") &&
113+
(e.InnerException == null || !e.InnerException.Message.Contains("truncation")))
114+
throw;
115+
}
116+
}
117+
}
118+
119+
[Test]
120+
public void CastWithPrecisionScale()
121+
{
122+
if (TestDialect.HasBrokenDecimalType)
123+
Assert.Ignore("Dialect does not correctly handle decimal.");
124+
125+
using (var s = OpenSession())
126+
{
127+
var value = s
128+
.CreateCriteria<Student>()
129+
.SetProjection(
130+
Projections.Cast(
131+
TypeFactory.Basic("decimal(18,9)"),
132+
Projections.Constant(123456789.123456789m, TypeFactory.Basic("decimal(18,9)"))))
133+
.UniqueResult<decimal>();
134+
Assert.That(value, Is.EqualTo(123456789.123456789m), "Same type cast");
135+
136+
value = s
137+
.CreateCriteria<Student>()
138+
.SetProjection(
139+
Projections.Cast(
140+
TypeFactory.Basic("decimal(18,7)"),
141+
Projections.Constant(123456789.987654321m, TypeFactory.Basic("decimal(18,9)"))))
142+
.UniqueResult<decimal>();
143+
Assert.That(value, Is.EqualTo(123456789.9876543m), "Reduced scale cast");
144+
}
145+
}
146+
92147
[Test]
93148
public void CanUseParametersWithProjections()
94149
{

src/NHibernate.Test/DialectTest/DialectFixture.cs

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
using System;
22
using System.Collections.Generic;
33
using System.Data;
4-
using System.Data.Common;
54
using NHibernate.Dialect;
65
using NHibernate.Driver;
76
using NHibernate.Engine;
@@ -171,5 +170,28 @@ public void CurrentTimestampSelection()
171170
}
172171
}
173172
}
173+
174+
[Test]
175+
public void GetDecimalTypeName()
176+
{
177+
var cfg = TestConfigurationHelper.GetDefaultConfiguration();
178+
var dialect = Dialect.Dialect.GetDialect(cfg.Properties);
179+
180+
Assert.That(dialect.GetTypeName(SqlTypeFactory.GetSqlType(DbType.Decimal, 40, 40)), Does.Not.Contain("40"), "oversized decimal");
181+
Assert.That(dialect.GetTypeName(SqlTypeFactory.GetSqlType(DbType.Decimal, 3, 2)), Does.Match(@"^[^(]*(\(\s*3\s*,\s*2\s*\))?\s*$"), "small decimal");
182+
}
183+
184+
[Test]
185+
public void GetTypeCastName()
186+
{
187+
var cfg = TestConfigurationHelper.GetDefaultConfiguration();
188+
cfg.SetProperty(Environment.QueryDefaultCastLength, "20");
189+
cfg.SetProperty(Environment.QueryDefaultCastPrecision, "10");
190+
cfg.SetProperty(Environment.QueryDefaultCastScale, "3");
191+
var dialect = Dialect.Dialect.GetDialect(cfg.Properties);
192+
193+
Assert.That(dialect.GetCastTypeName(SqlTypeFactory.Decimal), Does.Match(@"^[^(]*(\(\s*10\s*,\s*3\s*\))?\s*$"), "decimal");
194+
Assert.That(dialect.GetCastTypeName(new SqlType(DbType.String)), Does.Match(@"^[^(]*(\(\s*20\s*\))?\s*$"), "string");
195+
}
174196
}
175-
}
197+
}

src/NHibernate.Test/DriverTest/FirebirdClientDriverFixture.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ public void ConnectionPooling_OpenThenCloseTwoAtTheSameTime_TowConnectionsArePoo
7676
public void AdjustCommand_StringParametersWithinConditionalSelect_ThenParameterIsWrappedByAVarcharCastStatement()
7777
{
7878
MakeDriver();
79-
var cmd = BuildSelectCaseCommand(SqlTypeFactory.GetString(0));
79+
var cmd = BuildSelectCaseCommand(SqlTypeFactory.GetString(255));
8080

8181
_driver.AdjustCommand(cmd);
8282

@@ -100,7 +100,7 @@ public void AdjustCommand_IntParametersWithinConditionalSelect_ThenParameterIsWr
100100
public void AdjustCommand_ParameterWithinSelectConcat_ParameterIsCasted()
101101
{
102102
MakeDriver();
103-
var cmd = BuildSelectConcatCommand(SqlTypeFactory.GetString(0));
103+
var cmd = BuildSelectConcatCommand(SqlTypeFactory.GetString(255));
104104

105105
_driver.AdjustCommand(cmd);
106106

@@ -112,7 +112,7 @@ public void AdjustCommand_ParameterWithinSelectConcat_ParameterIsCasted()
112112
public void AdjustCommand_ParameterWithinSelectAddFunction_ParameterIsCasted()
113113
{
114114
MakeDriver();
115-
var cmd = BuildSelectAddCommand(SqlTypeFactory.GetString(0));
115+
var cmd = BuildSelectAddCommand(SqlTypeFactory.GetString(255));
116116

117117
_driver.AdjustCommand(cmd);
118118

src/NHibernate/Cfg/Environment.cs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,24 @@ public static string Version
203203

204204
public const string QueryModelRewriterFactory = "query.query_model_rewriter_factory";
205205

206+
/// <summary>
207+
/// Set the default length used in casting when the target type is length bound and
208+
/// does not specify it. <c>4000</c> by default, automatically trim down according to dialect type registration.
209+
/// </summary>
210+
public const string QueryDefaultCastLength = "query.default_cast_length";
211+
212+
/// <summary>
213+
/// Set the default precision used in casting when the target type is decimal and
214+
/// does not specify it. <c>28</c> by default, automatically trim down according to dialect type registration.
215+
/// </summary>
216+
public const string QueryDefaultCastPrecision = "query.default_cast_precision";
217+
218+
/// <summary>
219+
/// Set the default scale used in casting when the target type is decimal and
220+
/// does not specify it. <c>10</c> by default, automatically trim down according to dialect type registration.
221+
/// </summary>
222+
public const string QueryDefaultCastScale = "query.default_cast_scale";
223+
206224
/// <summary>
207225
/// This may need to be set to 3 if you are using the OdbcDriver with MS SQL Server 2008+.
208226
/// </summary>

src/NHibernate/Dialect/Dialect.cs

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,9 @@ private static Dialect InstantiateDialect(string dialectName, IDictionary<string
199199
/// <param name="settings">The configuration settings.</param>
200200
public virtual void Configure(IDictionary<string, string> settings)
201201
{
202+
DefaultCastLength = PropertiesHelper.GetInt32(Environment.QueryDefaultCastLength, settings, 4000);
203+
DefaultCastPrecision = PropertiesHelper.GetByte(Environment.QueryDefaultCastPrecision, settings, null) ?? 28;
204+
DefaultCastScale = PropertiesHelper.GetByte(Environment.QueryDefaultCastScale, settings, null) ?? 10;
202205
}
203206

204207
#endregion
@@ -245,15 +248,48 @@ public virtual string GetLongestTypeName(DbType dbType)
245248
return _typeNames.GetLongest(dbType);
246249
}
247250

251+
protected int DefaultCastLength { get; set; }
252+
protected byte DefaultCastPrecision { get; set; }
253+
protected byte DefaultCastScale { get; set; }
254+
248255
/// <summary>
249256
/// Get the name of the database type appropriate for casting operations
250257
/// (via the CAST() SQL function) for the given <see cref="SqlType"/> typecode.
251258
/// </summary>
252259
/// <param name="sqlType">The <see cref="SqlType"/> typecode </param>
253260
/// <returns> The database type name </returns>
254-
public virtual string GetCastTypeName(SqlType sqlType)
261+
public virtual string GetCastTypeName(SqlType sqlType) =>
262+
GetCastTypeName(sqlType, _typeNames);
263+
264+
/// <summary>
265+
/// Get the name of the database type appropriate for casting operations
266+
/// (via the CAST() SQL function) for the given <see cref="SqlType"/> typecode.
267+
/// </summary>
268+
/// <param name="sqlType">The <see cref="SqlType"/> typecode.</param>
269+
/// <param name="castTypeNames">The source for type names.</param>
270+
/// <returns>The database type name.</returns>
271+
protected virtual string GetCastTypeName(SqlType sqlType, TypeNames castTypeNames)
255272
{
256-
return GetTypeName(sqlType, Column.DefaultLength, Column.DefaultPrecision, Column.DefaultScale);
273+
if (sqlType.LengthDefined || sqlType.PrecisionDefined || sqlType.ScaleDefined)
274+
return castTypeNames.Get(sqlType.DbType, sqlType.Length, sqlType.Precision, sqlType.Scale);
275+
switch (sqlType.DbType)
276+
{
277+
case DbType.Decimal:
278+
// We cannot know if the user needs its digit after or before the dot, so use a configurable
279+
// default.
280+
return castTypeNames.Get(DbType.Decimal, 0, DefaultCastPrecision, DefaultCastScale);
281+
case DbType.DateTime:
282+
case DbType.DateTime2:
283+
case DbType.DateTimeOffset:
284+
case DbType.Time:
285+
case DbType.Currency:
286+
// Use default for these, dialects are supposed to map them to max capacity
287+
return castTypeNames.Get(sqlType.DbType);
288+
default:
289+
// Other types are either length bound or not length/precision/scale bound. Otherwise they need to be
290+
// handled previously.
291+
return castTypeNames.Get(sqlType.DbType, DefaultCastLength, 0, 0);
292+
}
257293
}
258294

259295
/// <summary>

src/NHibernate/Dialect/MySQLDialect.cs

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
using System.Text;
55
using NHibernate.Dialect.Function;
66
using NHibernate.Dialect.Schema;
7-
using NHibernate.Mapping;
87
using NHibernate.SqlCommand;
98
using NHibernate.SqlTypes;
109
using NHibernate.Util;
@@ -64,7 +63,7 @@ public MySQLDialect()
6463
RegisterColumnType(DbType.String, 16777215, "MEDIUMTEXT");
6564
//todo: future: add compatibility with decimal???
6665
//An unpacked fixed-point number. Behaves like a CHAR column;
67-
//“unpacked” means the number is stored as a string, using one character for each digit of the value.
66+
//“unpacked” means the number is stored as a string, using one character for each digit of the value.
6867
//M is the total number of digits and D is the number of digits after the decimal point
6968
//DECIMAL[(M[,D])] [UNSIGNED] [ZEROFILL]
7069

@@ -494,15 +493,8 @@ protected void RegisterCastType(DbType code, int capacity, string name)
494493
/// </summary>
495494
/// <param name="sqlType">The <see cref="SqlType"/> typecode </param>
496495
/// <returns> The database type name </returns>
497-
public override string GetCastTypeName(SqlType sqlType)
498-
{
499-
string result = castTypeNames.Get(sqlType.DbType, Column.DefaultLength, Column.DefaultPrecision, Column.DefaultScale);
500-
if (result == null)
501-
{
502-
throw new HibernateException(string.Format("No CAST() type mapping for SqlType {0}", sqlType));
503-
}
504-
return result;
505-
}
496+
public override string GetCastTypeName(SqlType sqlType) =>
497+
GetCastTypeName(sqlType, castTypeNames);
506498

507499
public override long TimestampResolutionInTicks
508500
{
@@ -552,4 +544,4 @@ public override long TimestampResolutionInTicks
552544

553545
#endregion
554546
}
555-
}
547+
}

0 commit comments

Comments
 (0)