Skip to content

Commit 2f8f317

Browse files
Generate stable sql objects names (nhibernate#1802)
Fix nhibernate#1769
1 parent f7080df commit 2f8f317

File tree

8 files changed

+195
-29
lines changed

8 files changed

+195
-29
lines changed

src/NHibernate.Test/MappingTest/TableFixture.cs

+9-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
using System.Linq;
2-
using System.Threading;
31
using NHibernate.Dialect;
42
using NHibernate.Mapping;
53
using NUnit.Framework;
@@ -61,5 +59,14 @@ public void SchemaNameQuoted()
6159

6260
Assert.AreEqual("[schema].name", tbl.GetQualifiedName(dialect));
6361
}
62+
63+
[Test]
64+
public void NameIsStable()
65+
{
66+
var tbl = new Table { Name = "name" };
67+
Assert.That(
68+
Constraint.GenerateName("FK_", tbl, null, new[] {new Column("col1"), new Column("col2")}),
69+
Is.EqualTo("FK_3B355A0C"));
70+
}
6471
}
6572
}

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

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1+
using System;
12
using NHibernate.Mapping;
23
using NUnit.Framework;
34

45
namespace NHibernate.Test.NHSpecificTest.NH1399
56
{
6-
[TestFixture]
7+
[TestFixture, Obsolete]
78
public class Fixture
89
{
910
[Test]
@@ -42,4 +43,4 @@ public void UsingTwoInstancesWithSameValuesTheFkNameIsTheSame()
4243
Assert.That(t2Fk_, Is.EqualTo(t2Fk));
4344
}
4445
}
45-
}
46+
}

src/NHibernate/Cfg/Configuration.cs

+7-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
using System;
22
using System.Collections;
33
using System.Collections.Generic;
4-
using System.Configuration;
54
using System.Diagnostics;
65
using System.IO;
76
using System.Linq;
@@ -1191,6 +1190,13 @@ private void SecondPassCompileForeignKeys(Table table, ISet<ForeignKey> done)
11911190
try
11921191
{
11931192
fk.AddReferencedTable(referencedClass);
1193+
1194+
if (string.IsNullOrEmpty(fk.Name))
1195+
{
1196+
fk.Name = Constraint.GenerateName(
1197+
fk.GeneratedConstraintNamePrefix, table, fk.ReferencedTable, fk.Columns);
1198+
}
1199+
11941200
fk.AlignColumns();
11951201
}
11961202
catch (MappingException me)

src/NHibernate/Mapping/Constraint.cs

+38
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System;
22
using System.Collections;
33
using System.Collections.Generic;
4+
using System.Linq;
45
using System.Text;
56
using NHibernate.Engine;
67
using NHibernate.Util;
@@ -38,6 +39,43 @@ public IEnumerable<Column> ColumnIterator
3839
get { return columns; }
3940
}
4041

42+
/// <summary>
43+
/// Generate a name hopefully unique using the table and column names.
44+
/// Static so the name can be generated prior to creating the Constraint.
45+
/// They're cached, keyed by name, in multiple locations.
46+
/// </summary>
47+
/// <param name="prefix">A name prefix for the generated name.</param>
48+
/// <param name="table">The table for which the name is generated.</param>
49+
/// <param name="referencedTable">The referenced table, if any.</param>
50+
/// <param name="columns">The columns for which the name is generated.</param>
51+
/// <returns>The generated name.</returns>
52+
/// <remarks>Hybrid of Hibernate <c>Constraint.generateName</c> and
53+
/// <c>NamingHelper.generateHashedFkName</c>.</remarks>
54+
public static string GenerateName(
55+
string prefix, Table table, Table referencedTable, IEnumerable<Column> columns)
56+
{
57+
// Use a concatenation that guarantees uniqueness, even if identical names
58+
// exist between all table and column identifiers.
59+
var sb = new StringBuilder("table`").Append(table.Name).Append("`");
60+
if (referencedTable != null)
61+
sb.Append("references`").Append(referencedTable.Name).Append("`");
62+
63+
// Ensure a consistent ordering of columns, regardless of the order
64+
// they were bound.
65+
foreach (var column in columns.OrderBy(c => c.CanonicalName))
66+
{
67+
sb.Append("column`").Append(column.CanonicalName).Append("`");
68+
}
69+
// Hash the generated name for avoiding collisions with user choosen names.
70+
// This is not 100% reliable, as hashing may still have a chance of generating
71+
// collisions.
72+
// Hibernate uses MD5 here, which .Net standrad implementation is rejected by
73+
// FIPS enabled machine. Better use a non-cryptographic hash.
74+
var name = prefix + Hasher.HashToString(sb.ToString());
75+
76+
return name;
77+
}
78+
4179
/// <summary>
4280
/// Adds the <see cref="Column"/> to the <see cref="ICollection"/> of
4381
/// Columns that are part of the constraint.

src/NHibernate/Mapping/DenormalizedTable.cs

+10-12
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
using System;
2-
using System.Collections;
32
using NHibernate.Util;
43
using System.Collections.Generic;
4+
using System.Linq;
55

66
namespace NHibernate.Mapping
77
{
@@ -56,21 +56,19 @@ public override IEnumerable<Index> IndexIterator
5656
public override void CreateForeignKeys()
5757
{
5858
includedTable.CreateForeignKeys();
59-
IEnumerable includedFks = includedTable.ForeignKeyIterator;
60-
foreach (ForeignKey fk in includedFks)
59+
var includedFks = includedTable.ForeignKeyIterator;
60+
foreach (var fk in includedFks)
6161
{
62-
// NH Different behaviour (fk name)
63-
CreateForeignKey(GetForeignKeyName(fk), fk.Columns, fk.ReferencedEntityName);
62+
CreateForeignKey(
63+
Constraint.GenerateName(
64+
fk.GeneratedConstraintNamePrefix,
65+
this,
66+
null,
67+
fk.Columns),
68+
fk.Columns, fk.ReferencedEntityName);
6469
}
6570
}
6671

67-
private string GetForeignKeyName(ForeignKey fk)
68-
{
69-
// (the FKName length, of H3.2 implementation, may be too long for some RDBMS so we implement something different)
70-
int hash = fk.Name.GetHashCode() ^ Name.GetHashCode();
71-
return string.Format("KF{0}", hash.ToString("X"));
72-
}
73-
7472
public override Column GetColumn(Column column)
7573
{
7674
Column superColumn = base.GetColumn(column);

src/NHibernate/Mapping/ForeignKey.cs

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
using System.Collections;
21
using System.Collections.Generic;
32
using System.Text;
43
using NHibernate.Util;
@@ -233,5 +232,7 @@ public bool IsReferenceToPrimaryKey
233232
{
234233
get { return referencedColumns.Count == 0; }
235234
}
235+
236+
public string GeneratedConstraintNamePrefix => "FK_";
236237
}
237238
}

src/NHibernate/Mapping/Table.cs

+9-11
Original file line numberDiff line numberDiff line change
@@ -808,15 +808,9 @@ public virtual ForeignKey CreateForeignKey(string keyName, IEnumerable<Column> k
808808
if (fk == null)
809809
{
810810
fk = new ForeignKey();
811-
if (!string.IsNullOrEmpty(keyName))
812-
{
813-
fk.Name = keyName;
814-
}
815-
else
816-
{
817-
fk.Name = "FK" + UniqueColumnString(kCols, referencedEntityName);
818-
//TODO: add referencedClass to disambiguate to FKs on the same columns, pointing to different tables
819-
}
811+
// NOTE : if the name is null, we will generate an implicit name during second pass processing
812+
// after we know the referenced table name (which might not be resolved yet).
813+
fk.Name = keyName;
820814
fk.Table = this;
821815
foreignKeys.Add(key, fk);
822816
fk.ReferencedEntityName = referencedEntityName;
@@ -837,8 +831,8 @@ public virtual ForeignKey CreateForeignKey(string keyName, IEnumerable<Column> k
837831

838832
public virtual UniqueKey CreateUniqueKey(IList<Column> keyColumns)
839833
{
840-
string keyName = "UK" + UniqueColumnString(keyColumns);
841-
UniqueKey uk = GetOrCreateUniqueKey(keyName);
834+
var keyName = Constraint.GenerateName( "UK_", this, null, keyColumns);
835+
var uk = GetOrCreateUniqueKey(keyName);
842836
uk.AddColumns(keyColumns);
843837
return uk;
844838
}
@@ -851,11 +845,15 @@ public virtual UniqueKey CreateUniqueKey(IList<Column> keyColumns)
851845
/// <returns>
852846
/// An unique string for the <see cref="Column"/> objects.
853847
/// </returns>
848+
// Since v5.2
849+
[Obsolete("Use Constraint.GenerateName instead.")]
854850
public string UniqueColumnString(IEnumerable uniqueColumns)
855851
{
856852
return UniqueColumnString(uniqueColumns, null);
857853
}
858854

855+
// Since v5.2
856+
[Obsolete("Use Constraint.GenerateName instead.")]
859857
public string UniqueColumnString(IEnumerable iterator, string referencedEntityName)
860858
{
861859
// NH Different implementation (NH-1399)

src/NHibernate/Util/Hasher.cs

+117
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
/*
2+
* Derived from MurmurHash2Simple,
3+
* http://landman-code.blogspot.com/2009/02/c-superfasthash-and-murmurhash2.html
4+
*/
5+
6+
/***** BEGIN LICENSE BLOCK *****
7+
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
8+
*
9+
* The contents of this file are subject to the Mozilla Public License Version
10+
* 1.1 (the "License"); you may not use this file except in compliance with
11+
* the License. You may obtain a copy of the License at
12+
* http://www.mozilla.org/MPL/
13+
*
14+
* Software distributed under the License is distributed on an "AS IS" basis,
15+
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
16+
* for the specific language governing rights and limitations under the
17+
* License.
18+
*
19+
* The Original Code is HashTableHashing.MurmurHash2.
20+
*
21+
* The Initial Developer of the Original Code is
22+
* Davy Landman.
23+
* Portions created by the Initial Developer are Copyright (C) 2009
24+
* the Initial Developer. All Rights Reserved.
25+
*
26+
* Contributor(s):
27+
*
28+
*
29+
* Alternatively, the contents of this file may be used under the terms of
30+
* either the GNU General Public License Version 2 or later (the "GPL"), or
31+
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
32+
* in which case the provisions of the GPL or the LGPL are applicable instead
33+
* of those above. If you wish to allow use of your version of this file only
34+
* under the terms of either the GPL or the LGPL, and not to allow others to
35+
* use your version of this file under the terms of the MPL, indicate your
36+
* decision by deleting the provisions above and replace them with the notice
37+
* and other provisions required by the GPL or the LGPL. If you do not delete
38+
* the provisions above, a recipient may use your version of this file under
39+
* the terms of any one of the MPL, the GPL or the LGPL.
40+
*
41+
* ***** END LICENSE BLOCK ***** */
42+
43+
using System;
44+
using System.Text;
45+
46+
namespace NHibernate.Util
47+
{
48+
/// <summary>A stable hasher using MurmurHash2 algorithm.</summary>
49+
internal static class Hasher
50+
{
51+
internal static string HashToString(string input)
52+
{
53+
var hash = Hash(input);
54+
return hash.ToString("X");
55+
}
56+
57+
internal static uint Hash(string input)
58+
{
59+
return Hash(Encoding.UTF8.GetBytes(input));
60+
}
61+
62+
internal static uint Hash(byte[] data)
63+
{
64+
return Hash(data, 0xc58f1a7b);
65+
}
66+
67+
private const uint _m = 0x5bd1e995;
68+
private const int _r = 24;
69+
70+
internal static uint Hash(byte[] data, uint seed)
71+
{
72+
var length = data.Length;
73+
if (length == 0)
74+
return 0;
75+
var h = seed ^ (uint) length;
76+
var currentIndex = 0;
77+
while (length >= 4)
78+
{
79+
var k = BitConverter.ToUInt32(data, currentIndex);
80+
k *= _m;
81+
k ^= k >> _r;
82+
k *= _m;
83+
84+
h *= _m;
85+
h ^= k;
86+
currentIndex += 4;
87+
length -= 4;
88+
}
89+
90+
switch (length)
91+
{
92+
case 3:
93+
h ^= BitConverter.ToUInt16(data, currentIndex);
94+
h ^= (uint) data[currentIndex + 2] << 16;
95+
h *= _m;
96+
break;
97+
case 2:
98+
h ^= BitConverter.ToUInt16(data, currentIndex);
99+
h *= _m;
100+
break;
101+
case 1:
102+
h ^= data[currentIndex];
103+
h *= _m;
104+
break;
105+
}
106+
107+
// Do a few final mixes of the hash to ensure the last few
108+
// bytes are well-incorporated.
109+
110+
h ^= h >> 13;
111+
h *= _m;
112+
h ^= h >> 15;
113+
114+
return h;
115+
}
116+
}
117+
}

0 commit comments

Comments
 (0)