Skip to content

Commit d636a40

Browse files
oskarbfredericDelaporte
authored andcommitted
Fix GUID to string conversion when BinaryGuid=False
* Add helper methods to get the configured connection string * Expand NH-3426 tests to expose nhibernateGH-2110 (and nhibernateGH-2109) * Detect BinaryGuid setting and apply correct GUID-to-string expression * Document the new sqlite.binaryguid setting BinaryGuid is a configuration property of some MySql drivers, but relates to type used in the database for handling GUIDs, which impacts the dialect. The cases for nhibernateGH-2109 are marked as ignored for now, as that is not a regression. Fixes nhibernate#2110
1 parent c31527e commit d636a40

File tree

5 files changed

+209
-14
lines changed

5 files changed

+209
-14
lines changed

doc/reference/modules/configuration.xml

+14
Original file line numberDiff line numberDiff line change
@@ -1081,6 +1081,20 @@ in the parameter binding.</programlisting>
10811081
</para>
10821082
</entry>
10831083
</row>
1084+
<row>
1085+
<entry>
1086+
<literal>sqlite.binaryguid</literal>
1087+
</entry>
1088+
<entry>
1089+
SQLite can store GUIDs in binary or text form, controlled by the BinaryGuid
1090+
connection string parameter (default is 'true'). The BinaryGuid setting will affect
1091+
how to cast GUID to string in SQL. NHibernate will attempt to detect this
1092+
setting automatically from the connection string, but if the connection
1093+
or connection string is being handled by the application instead of by NHibernate,
1094+
you can use the <literal>sqlite.binaryguid</literal> NHibernate setting to override the behavior.
1095+
The value can be <literal>true</literal> or <literal>false</literal>.
1096+
</entry>
1097+
</row>
10841098
<row>
10851099
<entry>
10861100
<literal>nhibernate-logger</literal>

src/NHibernate.Test/NHSpecificTest/NH3426/Fixture.cs

+93-2
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,57 @@
11
using System;
22
using System.Linq;
3+
using NHibernate.Cfg;
34
using NHibernate.Cfg.MappingSchema;
5+
using NHibernate.Dialect;
46
using NHibernate.Mapping.ByCode;
57
using NUnit.Framework;
8+
using Environment = NHibernate.Cfg.Environment;
69

710
namespace NHibernate.Test.NHSpecificTest.NH3426
811
{
9-
[TestFixture]
12+
/// <summary>
13+
/// Verify that we can convert a GUID column to a string in the standard GUID format inside
14+
/// the database engine.
15+
/// </summary>
16+
[TestFixture(true)]
17+
[TestFixture(false)]
1018
public class Fixture : TestCaseMappingByCode
1119
{
20+
private readonly bool _useBinaryGuid;
21+
22+
public Fixture(bool useBinaryGuid)
23+
{
24+
_useBinaryGuid = useBinaryGuid;
25+
}
26+
27+
protected override bool AppliesTo(Dialect.Dialect dialect)
28+
{
29+
// For SQLite, we run the tests for both storage modes (SQLite specific setting).
30+
if (dialect is SQLiteDialect)
31+
return true;
32+
33+
// For all other dialects, run the tests only once since the storage mode
34+
// is not relevant. (We use the case of _useBinaryGuid==true since this is probably
35+
// what most engines do internally.)
36+
return _useBinaryGuid;
37+
}
38+
39+
protected override void Configure(Configuration configuration)
40+
{
41+
base.Configure(configuration);
42+
43+
if (Dialect is SQLiteDialect)
44+
{
45+
var connStr = configuration.Properties[Environment.ConnectionString];
46+
47+
if (_useBinaryGuid)
48+
connStr += "BinaryGuid=True;";
49+
else
50+
connStr += "BinaryGuid=False;";
51+
52+
configuration.Properties[Environment.ConnectionString] = connStr;
53+
}
54+
}
1255

1356
protected override HbmMapping GetMappings()
1457
{
@@ -56,7 +99,7 @@ public void SelectGuidToString()
5699
.Select(x => new { Id = x.Id.ToString() })
57100
.ToList();
58101

59-
Assert.AreEqual(id.ToUpper(), list[0].Id.ToUpper());
102+
Assert.That(list[0].Id.ToUpper(), Is.EqualTo(id.ToUpper()));
60103
}
61104
}
62105

@@ -98,5 +141,53 @@ public void CompareStringColumnWithNullableGuidToString()
98141
Assert.That(list, Has.Count.EqualTo(1));
99142
}
100143
}
144+
145+
[Test]
146+
public void SelectGuidToStringImplicit()
147+
{
148+
if (Dialect is SQLiteDialect && _useBinaryGuid)
149+
Assert.Ignore("Fails with BinaryGuid=True due to GH-2109. (2019-04-09).");
150+
151+
if (Dialect is FirebirdDialect || Dialect is MySQLDialect || Dialect is Oracle8iDialect)
152+
Assert.Ignore("Since strguid() is not applied, it fails on Firebird, MySQL and Oracle " +
153+
"because a simple cast cannot be used for GUID to string conversion on " +
154+
"these dialects. See GH-2109.");
155+
156+
using (var session = OpenSession())
157+
{
158+
// Verify in-db GUID to string conversion when ToString() is applied to the entity that has
159+
// a GUID id column (that is, we deliberately avoid mentioning the Id property). This
160+
// exposes bug GH-2109.
161+
var list = session.Query<Entity>()
162+
.Select(x => new { Id = x.ToString() })
163+
.ToList();
164+
165+
Assert.That(list[0].Id.ToUpper(), Is.EqualTo(id.ToUpper()));
166+
}
167+
}
168+
169+
[Test]
170+
public void WhereGuidToStringImplicit()
171+
{
172+
if (Dialect is SQLiteDialect && _useBinaryGuid)
173+
Assert.Ignore("Fails with BinaryGuid=True due to GH-2109. (2019-04-09).");
174+
175+
if (Dialect is FirebirdDialect || Dialect is MySQLDialect || Dialect is Oracle8iDialect)
176+
Assert.Ignore("Since strguid() is not applied, it fails on Firebird, MySQL and Oracle " +
177+
"because a simple cast cannot be used for GUID to string conversion on " +
178+
"these dialects. See GH-2109.");
179+
180+
using (var session = OpenSession())
181+
{
182+
// Verify in-db GUID to string conversion when ToString() is applied to the entity that has
183+
// a GUID id column (that is, we deliberately avoid mentioning the Id property). This
184+
// exposes bug GH-2109.
185+
var list = session.Query<Entity>()
186+
.Where(x => x.ToString().ToUpper() == id)
187+
.ToList();
188+
189+
Assert.That(list, Has.Count.EqualTo(1));
190+
}
191+
}
101192
}
102193
}

src/NHibernate/Cfg/Environment.cs

+47
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,18 @@ public static string Version
280280
/// </summary>
281281
public const string FirebirdDisableParameterCasting = "firebird.disable_parameter_casting";
282282

283+
/// <summary>
284+
/// <para>
285+
/// SQLite can store GUIDs in binary or text form, controlled by the BinaryGuid
286+
/// connection string parameter (default is 'true'). The BinaryGuid setting will affect
287+
/// how to cast GUID to string in SQL. NHibernate will attempt to detect this
288+
/// setting automatically from the connection string, but if the connection
289+
/// or connection string is being handled by the application instead of by NHibernate,
290+
/// you can use the 'sqlite.binaryguid' NHibernate setting to override the behavior.
291+
/// </para>
292+
/// </summary>
293+
public const string SqliteBinaryGuid = "sqlite.binaryguid";
294+
283295
/// <summary>
284296
/// <para>Set whether tracking the session id or not. When <see langword="true"/>, each session
285297
/// will have an unique <see cref="Guid"/> that can be retrieved by <see cref="ISessionImplementor.SessionId"/>,
@@ -540,5 +552,40 @@ private static IObjectsFactory CreateCustomObjectsFactory(string assemblyQualifi
540552
}
541553
}
542554

555+
556+
/// <summary>
557+
/// Get a named connection string, if configured.
558+
/// </summary>
559+
/// <exception cref="HibernateException">
560+
/// Thrown when a <see cref="ConnectionStringName"/> was found
561+
/// in the <c>settings</c> parameter but could not be found in the app.config.
562+
/// </exception>
563+
internal static string GetNamedConnectionString(IDictionary<string, string> settings)
564+
{
565+
string connStringName;
566+
if (!settings.TryGetValue(ConnectionStringName, out connStringName))
567+
return null;
568+
569+
ConnectionStringSettings connectionStringSettings = ConfigurationManager.ConnectionStrings[connStringName];
570+
if (connectionStringSettings == null)
571+
throw new HibernateException($"Could not find named connection string '{connStringName}'.");
572+
573+
return connectionStringSettings.ConnectionString;
574+
}
575+
576+
577+
/// <summary>
578+
/// Get the configured connection string, from <see cref="ConnectionString"/> if that
579+
/// is set, otherwise from <see cref="ConnectionStringName"/>, or null if that isn't
580+
/// set either.
581+
/// </summary>
582+
internal static string GetConfiguredConnectionString(IDictionary<string, string> settings)
583+
{
584+
// Connection string in the configuration overrides named connection string.
585+
if (!settings.TryGetValue(ConnectionString, out string connString))
586+
connString = GetNamedConnectionString(settings);
587+
588+
return connString;
589+
}
543590
}
544591
}

src/NHibernate/Connection/ConnectionProvider.cs

+3-10
Original file line numberDiff line numberDiff line change
@@ -65,22 +65,15 @@ public virtual void Configure(IDictionary<string, string> settings)
6565
}
6666

6767
/// <summary>
68-
/// Get the .NET 2.0 named connection string
68+
/// Get a named connection string, if configured.
6969
/// </summary>
7070
/// <exception cref="HibernateException">
7171
/// Thrown when a <see cref="Environment.ConnectionStringName"/> was found
72-
/// in the <c>settings</c> parameter but could not be found in the app.config
72+
/// in the <c>settings</c> parameter but could not be found in the app.config.
7373
/// </exception>
7474
protected virtual string GetNamedConnectionString(IDictionary<string, string> settings)
7575
{
76-
string connStringName;
77-
if(!settings.TryGetValue(Environment.ConnectionStringName, out connStringName))
78-
return null;
79-
80-
ConnectionStringSettings connectionStringSettings = ConfigurationManager.ConnectionStrings[connStringName];
81-
if (connectionStringSettings == null)
82-
throw new HibernateException(string.Format("Could not find named connection string {0}", connStringName));
83-
return connectionStringSettings.ConnectionString;
76+
return Environment.GetNamedConnectionString(settings);
8477
}
8578

8679
/// <summary>

src/NHibernate/Dialect/SQLiteDialect.cs

+52-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Collections.Generic;
23
using System.Data;
34
using System.Data.Common;
45
using System.Text;
@@ -18,6 +19,13 @@ namespace NHibernate.Dialect
1819
/// </remarks>
1920
public class SQLiteDialect : Dialect
2021
{
22+
/// <summary>
23+
/// The effective value of the BinaryGuid connection string parameter.
24+
/// The default value in SQLite is true.
25+
/// </summary>
26+
private bool _binaryGuid = true;
27+
28+
2129
/// <summary>
2230
///
2331
/// </summary>
@@ -94,8 +102,50 @@ protected virtual void RegisterFunctions()
94102

95103
// NH-3787: SQLite requires the cast in SQL too for not defaulting to string.
96104
RegisterFunction("transparentcast", new CastFunction());
97-
98-
RegisterFunction("strguid", new SQLFunctionTemplate(NHibernateUtil.String, "substr(hex(?1), 7, 2) || substr(hex(?1), 5, 2) || substr(hex(?1), 3, 2) || substr(hex(?1), 1, 2) || '-' || substr(hex(?1), 11, 2) || substr(hex(?1), 9, 2) || '-' || substr(hex(?1), 15, 2) || substr(hex(?1), 13, 2) || '-' || substr(hex(?1), 17, 4) || '-' || substr(hex(?1), 21) "));
105+
106+
if (_binaryGuid)
107+
RegisterFunction("strguid", new SQLFunctionTemplate(NHibernateUtil.String, "substr(hex(?1), 7, 2) || substr(hex(?1), 5, 2) || substr(hex(?1), 3, 2) || substr(hex(?1), 1, 2) || '-' || substr(hex(?1), 11, 2) || substr(hex(?1), 9, 2) || '-' || substr(hex(?1), 15, 2) || substr(hex(?1), 13, 2) || '-' || substr(hex(?1), 17, 4) || '-' || substr(hex(?1), 21) "));
108+
else
109+
RegisterFunction("strguid", new SQLFunctionTemplate(NHibernateUtil.String, "cast(?1 as char)"));
110+
}
111+
112+
113+
public override void Configure(IDictionary<string, string> settings)
114+
{
115+
base.Configure(settings);
116+
117+
ConfigureBinaryGuid(settings);
118+
119+
// Re-register functions depending on settings.
120+
RegisterFunctions();
121+
}
122+
123+
private void ConfigureBinaryGuid(IDictionary<string, string> settings)
124+
{
125+
// We can use a SQLite specific setting to force it, but in common cases it
126+
// should be detected automatically from the connection string below.
127+
settings.TryGetValue(Cfg.Environment.SqliteBinaryGuid, out var strBinaryGuid);
128+
129+
if (string.IsNullOrWhiteSpace(strBinaryGuid))
130+
{
131+
string connectionString = Cfg.Environment.GetConfiguredConnectionString(settings);
132+
if (!string.IsNullOrWhiteSpace(connectionString))
133+
{
134+
var builder = new DbConnectionStringBuilder {ConnectionString = connectionString};
135+
136+
strBinaryGuid = GetConnectionStringProperty(builder, "BinaryGuid");
137+
}
138+
}
139+
140+
// Note that "BinaryGuid=false" is supported by System.Data.SQLite but not Microsoft.Data.Sqlite.
141+
142+
_binaryGuid = string.IsNullOrWhiteSpace(strBinaryGuid) || bool.Parse(strBinaryGuid);
143+
}
144+
145+
private string GetConnectionStringProperty(DbConnectionStringBuilder builder, string propertyName)
146+
{
147+
builder.TryGetValue(propertyName, out object propertyValue);
148+
return (string) propertyValue;
99149
}
100150

101151
#region private static readonly string[] DialectKeywords = { ... }

0 commit comments

Comments
 (0)