Skip to content

Commit 111f755

Browse files
Throw AggregateHibernateException when schema auto-update fails
Co-authored-by: Frédéric Delaporte <[email protected]>
1 parent 0e7bc34 commit 111f755

File tree

13 files changed

+292
-24
lines changed

13 files changed

+292
-24
lines changed

doc/reference/modules/configuration.xml

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -829,7 +829,7 @@ var session = sessions.OpenSession(conn);
829829
is closed explicitly.
830830
<para>
831831
<emphasis role="strong">eg.</emphasis>
832-
<literal>create</literal> | <literal>create-drop</literal>
832+
<literal>create</literal> | <literal>create-drop</literal> | <literal>update</literal> | <literal>validate</literal>
833833
</para>
834834
</entry>
835835
</row>
@@ -858,6 +858,20 @@ var session = sessions.OpenSession(conn);
858858
</para>
859859
</entry>
860860
</row>
861+
<row>
862+
<entry>
863+
<literal>hbm2ddl.throw_on_update</literal>
864+
</entry>
865+
<entry>
866+
When <literal>hbm2ddl.auto</literal> is <literal>update</literal>, whether to throw or not on schema auto-update failures.
867+
<!-- 6.0 TODO: should become true by default. -->
868+
Disabled by default.
869+
<para>
870+
<emphasis role="strong">eg.</emphasis>
871+
<literal>true</literal> | <literal>false</literal>
872+
</para>
873+
</entry>
874+
</row>
861875
<row>
862876
<entry>
863877
<literal>use_proxy_validator</literal>

src/NHibernate.Test/Async/Tools/hbm2ddl/SchemaUpdate/MigrationFixture.cs

Lines changed: 113 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
using System.Reflection;
1414
using NHibernate.Cfg;
1515
using NHibernate.Driver;
16+
using NHibernate.Engine;
17+
using NHibernate.Exceptions;
1618
using NHibernate.Tool.hbm2ddl;
1719
using NHibernate.Util;
1820
using NUnit.Framework;
@@ -25,18 +27,36 @@ namespace NHibernate.Test.Tools.hbm2ddl.SchemaUpdate
2527
[TestFixture]
2628
public class MigrationFixtureAsync
2729
{
28-
private async Task MigrateSchemaAsync(string resource1, string resource2, CancellationToken cancellationToken = default(CancellationToken))
30+
private Configuration _configurationToDrop;
31+
private FirebirdClientDriver _fireBirdDriver;
32+
33+
[OneTimeSetUp]
34+
public void OneTimeSetup()
2935
{
30-
Configuration v1cfg = TestConfigurationHelper.GetDefaultConfiguration();
31-
var driverClass = ReflectHelper.ClassForName(v1cfg.GetProperty(Environment.ConnectionDriver));
36+
var cfg = TestConfigurationHelper.GetDefaultConfiguration();
37+
var driverClass = ReflectHelper.ClassForName(cfg.GetProperty(Environment.ConnectionDriver));
3238
// Odbc is not supported by schema update: System.Data.Odbc.OdbcConnection.GetSchema("ForeignKeys") fails with an ArgumentException: ForeignKeys is undefined.
3339
// It seems it would require its own DataBaseSchema, but this is bound to the dialect, not the driver.
3440
if (typeof(OdbcDriver).IsAssignableFrom(driverClass))
3541
Assert.Ignore("Test is not compatible with ODBC");
3642

37-
using (Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(resource1))
38-
v1cfg.AddInputStream(stream);
39-
await (new SchemaExport(v1cfg).ExecuteAsync(false, true, true, cancellationToken));
43+
if (typeof(FirebirdClientDriver).IsAssignableFrom(driverClass))
44+
_fireBirdDriver = new FirebirdClientDriver();
45+
}
46+
47+
[TearDown]
48+
public void TearDown()
49+
{
50+
if (_configurationToDrop != null)
51+
DropSchema(_configurationToDrop);
52+
_configurationToDrop = null;
53+
}
54+
55+
private async Task MigrateSchemaAsync(string resource1, string resource2, CancellationToken cancellationToken = default(CancellationToken))
56+
{
57+
var v1cfg = GetConfigurationForMapping(resource1);
58+
await (DropSchemaAsync(v1cfg, cancellationToken));
59+
_configurationToDrop = v1cfg;
4060

4161
Tool.hbm2ddl.SchemaUpdate v1schemaUpdate = new Tool.hbm2ddl.SchemaUpdate(v1cfg);
4262
await (v1schemaUpdate.ExecuteAsync(true, true, cancellationToken));
@@ -46,9 +66,7 @@ public class MigrationFixtureAsync
4666

4767
Assert.AreEqual(0, v1schemaUpdate.Exceptions.Count);
4868

49-
Configuration v2cfg = TestConfigurationHelper.GetDefaultConfiguration();
50-
using (Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(resource2))
51-
v2cfg.AddInputStream(stream);
69+
var v2cfg = GetConfigurationForMapping(resource2);
5270

5371
Tool.hbm2ddl.SchemaUpdate v2schemaUpdate = new Tool.hbm2ddl.SchemaUpdate(v2cfg);
5472
await (v2schemaUpdate.ExecuteAsync(true, true, cancellationToken));
@@ -76,5 +94,90 @@ public async Task SimpleColumnReplaceAsync()
7694

7795
await (MigrateSchemaAsync(resource1, resource2));
7896
}
97+
98+
[Test]
99+
public async Task AutoUpdateFailuresAreThrownAsync()
100+
{
101+
var cfg1 = GetConfigurationForMapping("NHibernate.Test.Tools.hbm2ddl.SchemaUpdate.1_Person.hbm.xml");
102+
var sf1 = cfg1.BuildSessionFactory();
103+
var dialect = Dialect.Dialect.GetDialect(cfg1.Properties);
104+
if (!dialect.SupportsUnique)
105+
Assert.Ignore("This test requires a dialect supporting unique constraints");
106+
107+
_configurationToDrop = cfg1;
108+
await (CreateSchemaAsync(cfg1));
109+
110+
using (var s = sf1.OpenSession())
111+
using (var t = s.BeginTransaction())
112+
{
113+
await (s.SaveAsync(new Person()));
114+
await (s.SaveAsync(new Person()));
115+
await (t.CommitAsync());
116+
}
117+
118+
// This schema switches to not-nullable the person properties, which should fail due to an existing person with null properties.
119+
var cfg2 = GetConfigurationForMapping("NHibernate.Test.Tools.hbm2ddl.SchemaUpdate.3_Person.hbm.xml");
120+
cfg2.Properties[Environment.Hbm2ddlAuto] = SchemaAutoAction.Update.ToString();
121+
cfg2.Properties[Environment.Hbm2ddlThrowOnUpdate] = "true";
122+
Assert.That(() => cfg2.BuildSessionFactory(), Throws.InstanceOf<AggregateHibernateException>());
123+
}
124+
125+
private Configuration GetConfigurationForMapping(string resourcePath)
126+
{
127+
var cfg = TestConfigurationHelper.GetDefaultConfiguration();
128+
using (var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(resourcePath))
129+
cfg.AddInputStream(stream);
130+
return cfg;
131+
}
132+
133+
private Task CreateSchemaAsync(Configuration cfg, CancellationToken cancellationToken = default(CancellationToken))
134+
{
135+
try
136+
{
137+
// Firebird will pool each connection created during the test and will marked as used any table
138+
// referenced by queries. It will at best delays those tables drop until connections are actually
139+
// closed, or immediately fail dropping them.
140+
// This results in other tests failing when they try to create tables with the same name.
141+
// By clearing the connection pool the tables will get dropped.
142+
_fireBirdDriver?.ClearPool(null);
143+
144+
return new SchemaExport(cfg).CreateAsync(false, true, cancellationToken);
145+
}
146+
catch (Exception ex)
147+
{
148+
return Task.FromException<object>(ex);
149+
}
150+
}
151+
152+
private Task DropSchemaAsync(Configuration cfg, CancellationToken cancellationToken = default(CancellationToken))
153+
{
154+
try
155+
{
156+
// Firebird will pool each connection created during the test and will marked as used any table
157+
// referenced by queries. It will at best delays those tables drop until connections are actually
158+
// closed, or immediately fail dropping them.
159+
// This results in other tests failing when they try to create tables with the same name.
160+
// By clearing the connection pool the tables will get dropped.
161+
_fireBirdDriver?.ClearPool(null);
162+
163+
return new SchemaExport(cfg).DropAsync(false, true, cancellationToken);
164+
}
165+
catch (Exception ex)
166+
{
167+
return Task.FromException<object>(ex);
168+
}
169+
}
170+
171+
private void DropSchema(Configuration cfg)
172+
{
173+
// Firebird will pool each connection created during the test and will marked as used any table
174+
// referenced by queries. It will at best delays those tables drop until connections are actually
175+
// closed, or immediately fail dropping them.
176+
// This results in other tests failing when they try to create tables with the same name.
177+
// By clearing the connection pool the tables will get dropped.
178+
_fireBirdDriver?.ClearPool(null);
179+
180+
new SchemaExport(cfg).Drop(false, true);
181+
}
79182
}
80-
}
183+
}

src/NHibernate.Test/CfgTest/Loquacious/ConfigurationFixture.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,8 @@ public void CompleteConfiguration()
6363
.WithTimeout(10)
6464
.WithMaximumDepthOfOuterJoinFetching(11)
6565
.WithHqlToSqlSubstitutions("true 1, false 0, yes 'Y', no 'N'")
66-
.Schema
67-
.Validating();
66+
.Schema.Validating()
67+
.Schema.ThrowOnSchemaUpdate(true);
6868

6969
Assert.That(cfg.Properties[Environment.SessionFactoryName], Is.EqualTo("SomeName"));
7070
Assert.That(cfg.Properties[Environment.CacheProvider], Is.EqualTo(typeof(HashtableCacheProvider).AssemblyQualifiedName));
@@ -97,6 +97,7 @@ public void CompleteConfiguration()
9797
Assert.That(cfg.Properties[Environment.MaxFetchDepth], Is.EqualTo("11"));
9898
Assert.That(cfg.Properties[Environment.QuerySubstitutions], Is.EqualTo("true 1, false 0, yes 'Y', no 'N'"));
9999
Assert.That(cfg.Properties[Environment.Hbm2ddlAuto], Is.EqualTo("validate"));
100+
Assert.That(cfg.Properties[Environment.Hbm2ddlThrowOnUpdate], Is.EqualTo("true"));
100101

101102
// Keywords import and auto-validation require a valid connection string, disable them before checking
102103
// the session factory can be built.

src/NHibernate.Test/CfgTest/Loquacious/LambdaConfigurationFixture.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ public void FullConfiguration()
6363
db.MaximumDepthOfOuterJoinFetching = 11;
6464
db.HqlToSqlSubstitutions = "true 1, false 0, yes 'Y', no 'N'";
6565
db.SchemaAction = SchemaAutoAction.Validate;
66+
db.ThrowOnSchemaUpdate = true;
6667
});
6768

6869
Assert.That(configure.Properties[Environment.SessionFactoryName], Is.EqualTo("SomeName"));
@@ -106,6 +107,7 @@ public void FullConfiguration()
106107
Assert.That(configure.Properties[Environment.MaxFetchDepth], Is.EqualTo("11"));
107108
Assert.That(configure.Properties[Environment.QuerySubstitutions], Is.EqualTo("true 1, false 0, yes 'Y', no 'N'"));
108109
Assert.That(configure.Properties[Environment.Hbm2ddlAuto], Is.EqualTo("validate"));
110+
Assert.That(configure.Properties[Environment.Hbm2ddlThrowOnUpdate], Is.EqualTo("true"));
109111
Assert.That(configure.Properties[Environment.LinqToHqlGeneratorsRegistry], Is.EqualTo(typeof(DefaultLinqToHqlGeneratorsRegistry).AssemblyQualifiedName));
110112

111113
// Keywords import and auto-validation require a valid connection string, disable them before checking
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?xml version="1.0"?>
2+
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
3+
namespace="NHibernate.Test.Tools.hbm2ddl.SchemaUpdate"
4+
assembly="NHibernate.Test">
5+
6+
<class name="Person">
7+
<id name="Id">
8+
<generator class="native"/>
9+
</id>
10+
<property name="FirstName"/>
11+
<property name="LastName" >
12+
<column name="LastName" default="'[not-named]'" not-null="true" unique="true" />
13+
</property>
14+
</class>
15+
16+
</hibernate-mapping>

src/NHibernate.Test/Tools/hbm2ddl/SchemaUpdate/MigrationFixture.cs

Lines changed: 87 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
using System.Reflection;
44
using NHibernate.Cfg;
55
using NHibernate.Driver;
6+
using NHibernate.Engine;
7+
using NHibernate.Exceptions;
68
using NHibernate.Tool.hbm2ddl;
79
using NHibernate.Util;
810
using NUnit.Framework;
@@ -13,18 +15,36 @@ namespace NHibernate.Test.Tools.hbm2ddl.SchemaUpdate
1315
[TestFixture]
1416
public class MigrationFixture
1517
{
16-
private void MigrateSchema(string resource1, string resource2)
18+
private Configuration _configurationToDrop;
19+
private FirebirdClientDriver _fireBirdDriver;
20+
21+
[OneTimeSetUp]
22+
public void OneTimeSetup()
1723
{
18-
Configuration v1cfg = TestConfigurationHelper.GetDefaultConfiguration();
19-
var driverClass = ReflectHelper.ClassForName(v1cfg.GetProperty(Environment.ConnectionDriver));
24+
var cfg = TestConfigurationHelper.GetDefaultConfiguration();
25+
var driverClass = ReflectHelper.ClassForName(cfg.GetProperty(Environment.ConnectionDriver));
2026
// Odbc is not supported by schema update: System.Data.Odbc.OdbcConnection.GetSchema("ForeignKeys") fails with an ArgumentException: ForeignKeys is undefined.
2127
// It seems it would require its own DataBaseSchema, but this is bound to the dialect, not the driver.
2228
if (typeof(OdbcDriver).IsAssignableFrom(driverClass))
2329
Assert.Ignore("Test is not compatible with ODBC");
2430

25-
using (Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(resource1))
26-
v1cfg.AddInputStream(stream);
27-
new SchemaExport(v1cfg).Execute(false, true, true);
31+
if (typeof(FirebirdClientDriver).IsAssignableFrom(driverClass))
32+
_fireBirdDriver = new FirebirdClientDriver();
33+
}
34+
35+
[TearDown]
36+
public void TearDown()
37+
{
38+
if (_configurationToDrop != null)
39+
DropSchema(_configurationToDrop);
40+
_configurationToDrop = null;
41+
}
42+
43+
private void MigrateSchema(string resource1, string resource2)
44+
{
45+
var v1cfg = GetConfigurationForMapping(resource1);
46+
DropSchema(v1cfg);
47+
_configurationToDrop = v1cfg;
2848

2949
Tool.hbm2ddl.SchemaUpdate v1schemaUpdate = new Tool.hbm2ddl.SchemaUpdate(v1cfg);
3050
v1schemaUpdate.Execute(true, true);
@@ -34,9 +54,7 @@ private void MigrateSchema(string resource1, string resource2)
3454

3555
Assert.AreEqual(0, v1schemaUpdate.Exceptions.Count);
3656

37-
Configuration v2cfg = TestConfigurationHelper.GetDefaultConfiguration();
38-
using (Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(resource2))
39-
v2cfg.AddInputStream(stream);
57+
var v2cfg = GetConfigurationForMapping(resource2);
4058

4159
Tool.hbm2ddl.SchemaUpdate v2schemaUpdate = new Tool.hbm2ddl.SchemaUpdate(v2cfg);
4260
v2schemaUpdate.Execute(true, true);
@@ -64,5 +82,64 @@ public void SimpleColumnReplace()
6482

6583
MigrateSchema(resource1, resource2);
6684
}
85+
86+
[Test]
87+
public void AutoUpdateFailuresAreThrown()
88+
{
89+
var cfg1 = GetConfigurationForMapping("NHibernate.Test.Tools.hbm2ddl.SchemaUpdate.1_Person.hbm.xml");
90+
var sf1 = cfg1.BuildSessionFactory();
91+
var dialect = Dialect.Dialect.GetDialect(cfg1.Properties);
92+
if (!dialect.SupportsUnique)
93+
Assert.Ignore("This test requires a dialect supporting unique constraints");
94+
95+
_configurationToDrop = cfg1;
96+
CreateSchema(cfg1);
97+
98+
using (var s = sf1.OpenSession())
99+
using (var t = s.BeginTransaction())
100+
{
101+
s.Save(new Person());
102+
s.Save(new Person());
103+
t.Commit();
104+
}
105+
106+
// This schema switches to not-nullable the person properties, which should fail due to an existing person with null properties.
107+
var cfg2 = GetConfigurationForMapping("NHibernate.Test.Tools.hbm2ddl.SchemaUpdate.3_Person.hbm.xml");
108+
cfg2.Properties[Environment.Hbm2ddlAuto] = SchemaAutoAction.Update.ToString();
109+
cfg2.Properties[Environment.Hbm2ddlThrowOnUpdate] = "true";
110+
Assert.That(() => cfg2.BuildSessionFactory(), Throws.InstanceOf<AggregateHibernateException>());
111+
}
112+
113+
private Configuration GetConfigurationForMapping(string resourcePath)
114+
{
115+
var cfg = TestConfigurationHelper.GetDefaultConfiguration();
116+
using (var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(resourcePath))
117+
cfg.AddInputStream(stream);
118+
return cfg;
119+
}
120+
121+
private void CreateSchema(Configuration cfg)
122+
{
123+
// Firebird will pool each connection created during the test and will marked as used any table
124+
// referenced by queries. It will at best delays those tables drop until connections are actually
125+
// closed, or immediately fail dropping them.
126+
// This results in other tests failing when they try to create tables with the same name.
127+
// By clearing the connection pool the tables will get dropped.
128+
_fireBirdDriver?.ClearPool(null);
129+
130+
new SchemaExport(cfg).Create(false, true);
131+
}
132+
133+
private void DropSchema(Configuration cfg)
134+
{
135+
// Firebird will pool each connection created during the test and will marked as used any table
136+
// referenced by queries. It will at best delays those tables drop until connections are actually
137+
// closed, or immediately fail dropping them.
138+
// This results in other tests failing when they try to create tables with the same name.
139+
// By clearing the connection pool the tables will get dropped.
140+
_fireBirdDriver?.ClearPool(null);
141+
142+
new SchemaExport(cfg).Drop(false, true);
143+
}
67144
}
68-
}
145+
}

src/NHibernate/Cfg/Environment.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,12 @@ public static string Version
193193
[Obsolete("This setting has no usages and will be removed in a future version")]
194194
public const string QueryImports = "query.imports";
195195
public const string Hbm2ddlAuto = "hbm2ddl.auto";
196+
197+
// 6.0 TODO default should become true
198+
/// <summary>
199+
/// Whether to throw or not on schema auto-update failures. <c>false</c> by default.
200+
/// </summary>
201+
public const string Hbm2ddlThrowOnUpdate = "hbm2ddl.throw_on_update";
196202
public const string Hbm2ddlKeyWords = "hbm2ddl.keywords";
197203

198204
public const string SqlExceptionConverter = "sql_exception_converter";

src/NHibernate/Cfg/Loquacious/DbIntegrationConfiguration.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,18 @@ public DbIntegrationConfiguration Validating()
151151
return dbc;
152152
}
153153

154+
// 6.0 TODO default should become true
155+
/// <summary>
156+
/// Whether to throw or not on schema auto-update failures. <see langword="false" /> by default.
157+
/// </summary>
158+
/// <param name="throw"><see langword="true" /> to throw in case any failure is reported during schema auto-update,
159+
/// <see langword="false" /> to ignore failures.</param>
160+
public DbIntegrationConfiguration ThrowOnSchemaUpdate(bool @throw)
161+
{
162+
dbc.Configuration.SetProperty(Environment.Hbm2ddlThrowOnUpdate, @throw.ToString().ToLowerInvariant());
163+
return dbc;
164+
}
165+
154166
#region Implementation of IDbSchemaIntegrationConfiguration
155167
#pragma warning disable 618
156168

0 commit comments

Comments
 (0)