|
1 | 1 | using System;
|
2 |
| -using System.Collections; |
| 2 | +using System.Linq; |
| 3 | +using NHibernate.Cfg.MappingSchema; |
3 | 4 | using NHibernate.Criterion;
|
4 | 5 | using NHibernate.Dialect;
|
5 | 6 | using NHibernate.Driver;
|
| 7 | +using NHibernate.Exceptions; |
| 8 | +using NHibernate.Linq; |
| 9 | +using NHibernate.Mapping.ByCode; |
6 | 10 | using NUnit.Framework;
|
7 | 11 |
|
8 | 12 | namespace NHibernate.Test.TypesTest
|
9 | 13 | {
|
10 | 14 | /// <summary>
|
11 |
| - /// Summary description for StringTypeWithLengthFixture. |
| 15 | + /// Various tests regarding handling of size of query parameters. |
12 | 16 | /// </summary>
|
13 | 17 | [TestFixture]
|
14 |
| - public class StringTypeWithLengthFixture : TypeFixtureBase |
| 18 | + public class StringTypeWithLengthFixture : TestCaseMappingByCode |
15 | 19 | {
|
16 |
| - protected override string TypeName |
| 20 | + private int GetLongStringMappedLength() |
17 | 21 | {
|
18 |
| - get { return "String"; } |
19 |
| - } |
| 22 | + // This is a bit ugly... |
| 23 | + // |
| 24 | + // Return a value that should be the largest possible length of a string column |
| 25 | + // in the corresponding database. Note that the actual column type selected by the dialect |
| 26 | + // depends on this value, so it must be the largest possible value for the type |
| 27 | + // that the dialect will pick. Doesn't matter if the dialect can pick another |
| 28 | + // type for an even larger size. |
20 | 29 |
|
| 30 | + if (Dialect is Oracle8iDialect) |
| 31 | + return 2000; |
21 | 32 |
|
22 |
| - protected override IList Mappings |
23 |
| - { |
24 |
| - get |
25 |
| - { |
26 |
| - return new string[] |
27 |
| - { |
28 |
| - String.Format("TypesTest.{0}ClassWithLength.hbm.xml", TypeName) |
29 |
| - }; |
30 |
| - } |
| 33 | + if (Dialect is MySQLDialect) |
| 34 | + return 65535; |
| 35 | + |
| 36 | + return 4000; |
31 | 37 | }
|
32 | 38 |
|
33 |
| - protected override bool AppliesTo(Dialect.Dialect dialect) |
| 39 | + protected override HbmMapping GetMappings() |
34 | 40 | {
|
35 |
| - // this test only works where the driver has set an explicit length on the IDbDataParameter |
36 |
| - return dialect is MsSql2008Dialect; |
| 41 | + var mapper = new ModelMapper(); |
| 42 | + mapper.Class<StringClass>(ca => |
| 43 | + { |
| 44 | + ca.Lazy(false); |
| 45 | + ca.Id(x => x.Id, map => map.Generator(Generators.Assigned)); |
| 46 | + ca.Property(x => x.StringValue, map => map.Length(10)); |
| 47 | + ca.Property(x => x.LongStringValue, map => map.Length(GetLongStringMappedLength())); |
| 48 | + }); |
| 49 | + |
| 50 | + return mapper.CompileMappingForAllExplicitlyAddedEntities(); |
37 | 51 | }
|
38 | 52 |
|
| 53 | + |
39 | 54 | [Test]
|
40 |
| - public void NhThrowsOnTooLong() |
| 55 | + [Description("Values longer than the maximum possible string length " + |
| 56 | + "should raise an exception if they would otherwise be truncated.")] |
| 57 | + public void ShouldPreventInsertionOfVeryLongStringThatWouldBeTruncated() |
41 | 58 | {
|
42 |
| - int maxStringLength = 4000; |
43 |
| - PropertyValueException ex = Assert.Throws<PropertyValueException>(() => |
| 59 | + // This test case is for when the current driver will use a parameter size |
| 60 | + // that is significantly larger than the mapped column size (e.g. SqlClientDriver currently). |
| 61 | + |
| 62 | + // Note: This test could possible be written as |
| 63 | + // "database must raise an error OR it must store and return the full value" |
| 64 | + // to avoid this dialect specific exception. |
| 65 | + if (Dialect is SQLiteDialect) |
| 66 | + Assert.Ignore("SQLite does not enforce specified string lengths."); |
| 67 | + |
| 68 | + int maxStringLength = GetLongStringMappedLength(); |
| 69 | + |
| 70 | + var ex = Assert.Catch<Exception>( |
| 71 | + () => |
44 | 72 | {
|
45 | 73 | using (ISession s = OpenSession())
|
46 | 74 | {
|
47 |
| - StringClass b = new StringClass(); |
48 |
| - b.LongStringValue = new string('x', maxStringLength + 1); |
| 75 | + StringClass b = new StringClass {LongStringValue = new string('x', maxStringLength + 1)}; |
49 | 76 | s.Save(b);
|
50 | 77 | s.Flush();
|
51 | 78 | }
|
52 | 79 | });
|
53 | 80 |
|
54 |
| - Assert.That(ex.Message, Iz.EqualTo("Error dehydrating property value for NHibernate.Test.TypesTest.StringClass.LongStringValue")); |
55 |
| - Assert.That(ex.InnerException, Iz.TypeOf<HibernateException>()); |
56 |
| - Assert.That(ex.InnerException.Message, Iz.EqualTo("The length of the string value exceeds the length configured in the mapping/parameter.")); |
| 81 | + AssertFailedInsertExceptionDetailsAndEmptyTable(ex); |
57 | 82 | }
|
58 | 83 |
|
59 | 84 | [Test]
|
60 |
| - public void DbThrowsOnTooLong() |
| 85 | + [Description("Values longer than the mapped string length " + |
| 86 | + "should raise an exception if they would otherwise be truncated.")] |
| 87 | + public void ShouldPreventInsertionOfTooLongStringThatWouldBeTruncated() |
61 | 88 | {
|
62 |
| - bool dbThrewError = false; |
| 89 | + // Note: This test could possible be written as |
| 90 | + // "database must raise an error OR it must store and return the full value" |
| 91 | + // to avoid this dialect specific exception. |
| 92 | + if (Dialect is SQLiteDialect) |
| 93 | + Assert.Ignore("SQLite does not enforce specified string lengths."); |
63 | 94 |
|
64 |
| - try |
65 |
| - { |
66 |
| - using (ISession s = OpenSession()) |
| 95 | + var ex = Assert.Catch<Exception>( |
| 96 | + () => |
67 | 97 | {
|
68 |
| - StringClass b = new StringClass(); |
69 |
| - b.StringValue = "0123456789a"; |
70 |
| - s.Save(b); |
71 |
| - s.Flush(); |
72 |
| - } |
| 98 | + using (ISession s = OpenSession()) |
| 99 | + { |
| 100 | + StringClass b = new StringClass {StringValue = "0123456789a"}; |
| 101 | + s.Save(b); |
| 102 | + s.Flush(); |
| 103 | + } |
| 104 | + }, |
| 105 | + "An exception was expected when trying to put too large a value into a column."); |
| 106 | + |
| 107 | + AssertFailedInsertExceptionDetailsAndEmptyTable(ex); |
| 108 | + } |
| 109 | + |
| 110 | + |
| 111 | + private void AssertFailedInsertExceptionDetailsAndEmptyTable(Exception ex) |
| 112 | + { |
| 113 | + // We can get different sort of exceptions. |
| 114 | + if (ex is PropertyValueException) |
| 115 | + { |
| 116 | + // Some drivers/dialects set explicit parameter sizes, in which case we expect NH to |
| 117 | + // raise a PropertyValueException (to avoid ADO.NET from silently truncating). |
| 118 | + |
| 119 | + Assert.That( |
| 120 | + ex.Message, |
| 121 | + Is.StringStarting("Error dehydrating property value for NHibernate.Test.TypesTest.StringClass.")); |
| 122 | + |
| 123 | + Assert.That(ex.InnerException, Is.TypeOf<HibernateException>()); |
| 124 | + |
| 125 | + Assert.That( |
| 126 | + ex.InnerException.Message, |
| 127 | + Is.EqualTo("The length of the string value exceeds the length configured in the mapping/parameter.")); |
| 128 | + } |
| 129 | + else if (Dialect is MsSqlCeDialect && ex is InvalidOperationException) |
| 130 | + { |
| 131 | + Assert.That(ex.Message, Is.StringContaining("max=4000, len=4001")); |
73 | 132 | }
|
74 |
| - catch |
| 133 | + else |
75 | 134 | {
|
76 |
| - dbThrewError = true; |
| 135 | + // In other cases, we expect the database itself to raise an error. This case |
| 136 | + // will also happen if the driver does set an explicit parameter size, but that |
| 137 | + // size is larger than the mapped column size. |
| 138 | + Assert.That(ex, Is.TypeOf<GenericADOException>()); |
77 | 139 | }
|
78 | 140 |
|
79 |
| - Assert.That(dbThrewError, "Database did not throw an error when trying to put too large a value into a column"); |
| 141 | + // In any case, nothing should have been inserted. |
| 142 | + using (ISession s = OpenSession()) |
| 143 | + { |
| 144 | + Assert.That(s.Query<StringClass>().ToList(), Is.Empty); |
| 145 | + } |
80 | 146 | }
|
81 | 147 |
|
| 148 | + |
82 | 149 | [Test]
|
83 | 150 | public void CriteriaLikeParameterCanExceedColumnSize()
|
84 | 151 | {
|
85 |
| - if (!(sessions.ConnectionProvider.Driver is SqlClientDriver)) |
86 |
| - Assert.Ignore("This test fails against the ODBC driver. The driver would need to be override to allow longer parameter sizes than the column."); |
| 152 | + Exception exception = CatchException<Exception>( |
| 153 | + () => |
| 154 | + { |
| 155 | + using (ISession s = OpenSession()) |
| 156 | + using (s.BeginTransaction()) |
| 157 | + { |
| 158 | + s.Save(new StringClass {Id = 1, StringValue = "AAAAAAAAAB"}); |
| 159 | + s.Save(new StringClass {Id = 2, StringValue = "BAAAAAAAAA"}); |
87 | 160 |
|
88 |
| - using (ISession s = OpenSession()) |
89 |
| - using (ITransaction t = s.BeginTransaction()) |
90 |
| - { |
91 |
| - s.Save(new StringClass() { Id = 1, StringValue = "AAAAAAAAAB" }); |
92 |
| - s.Save(new StringClass() { Id = 2, StringValue = "BAAAAAAAAA" }); |
| 161 | + var aaItems = |
| 162 | + s.CreateCriteria<StringClass>() |
| 163 | + .Add(Restrictions.Like("StringValue", "%AAAAAAAAA%")) |
| 164 | + .List(); |
93 | 165 |
|
94 |
| - var aaItems = |
95 |
| - s.CreateCriteria<StringClass>() |
96 |
| - .Add(Restrictions.Like("StringValue", "%AAAAAAAAA%")) |
97 |
| - .List(); |
| 166 | + Assert.That(aaItems.Count, Is.EqualTo(2)); |
| 167 | + } |
| 168 | + }); |
98 | 169 |
|
99 |
| - Assert.That(aaItems.Count, Is.EqualTo(2)); |
100 |
| - } |
| 170 | + |
| 171 | + // This test fails against the ODBC driver. The driver would need to be override to allow longer parameter sizes than the column. |
| 172 | + AssertExpectedFailureOrNoException( |
| 173 | + exception, |
| 174 | + (sessions.ConnectionProvider.Driver is OdbcDriver)); |
101 | 175 | }
|
102 | 176 |
|
103 | 177 | [Test]
|
104 | 178 | public void HqlLikeParameterCanExceedColumnSize()
|
105 | 179 | {
|
106 |
| - if (!(sessions.ConnectionProvider.Driver is SqlClientDriver)) |
107 |
| - Assert.Ignore("This test fails against the ODBC driver. The driver would need to be override to allow longer parameter sizes than the column."); |
| 180 | + Exception exception = CatchException<Exception>( |
| 181 | + () => |
| 182 | + { |
| 183 | + using (ISession s = OpenSession()) |
| 184 | + using (s.BeginTransaction()) |
| 185 | + { |
| 186 | + s.Save(new StringClass {Id = 1, StringValue = "AAAAAAAAAB"}); |
| 187 | + s.Save(new StringClass {Id = 2, StringValue = "BAAAAAAAAA"}); |
108 | 188 |
|
109 |
| - using (ISession s = OpenSession()) |
110 |
| - using (ITransaction t = s.BeginTransaction()) |
| 189 | + var aaItems = |
| 190 | + s.CreateQuery("from StringClass s where s.StringValue like :likeValue") |
| 191 | + .SetParameter("likeValue", "%AAAAAAAAA%") |
| 192 | + .List(); |
| 193 | + |
| 194 | + Assert.That(aaItems.Count, Is.EqualTo(2)); |
| 195 | + } |
| 196 | + }); |
| 197 | + |
| 198 | + |
| 199 | + // This test fails against the ODBC driver. The driver would need to be override to allow longer parameter sizes than the column. |
| 200 | + AssertExpectedFailureOrNoException( |
| 201 | + exception, |
| 202 | + (sessions.ConnectionProvider.Driver is OdbcDriver)); |
| 203 | + } |
| 204 | + |
| 205 | + |
| 206 | + [Test] |
| 207 | + public void CriteriaEqualityParameterCanExceedColumnSize() |
| 208 | + { |
| 209 | + // We should be able to query a column with a value longer than |
| 210 | + // the specified column size, to avoid tedious exceptions. |
| 211 | + |
| 212 | + Exception exception = CatchException<Exception>( |
| 213 | + () => |
| 214 | + { |
| 215 | + using (ISession s = OpenSession()) |
| 216 | + using (s.BeginTransaction()) |
| 217 | + { |
| 218 | + s.Save(new StringClass {Id = 1, StringValue = "AAAAAAAAAB"}); |
| 219 | + s.Save(new StringClass {Id = 2, StringValue = "BAAAAAAAAA"}); |
| 220 | + |
| 221 | + var aaItems = |
| 222 | + s.CreateCriteria<StringClass>() |
| 223 | + .Add(Restrictions.Eq("StringValue", "AAAAAAAAABx")) |
| 224 | + .List(); |
| 225 | + |
| 226 | + Assert.That(aaItems.Count, Is.EqualTo(0)); |
| 227 | + } |
| 228 | + }); |
| 229 | + |
| 230 | + // Doesn't work on Firebird due to Firebird not figuring out parameter types on its own. |
| 231 | + // This test fails against the ODBC driver. The driver would need to be override to allow longer parameter sizes than the column. |
| 232 | + AssertExpectedFailureOrNoException( |
| 233 | + exception, |
| 234 | + (Dialect is FirebirdDialect) || (sessions.ConnectionProvider.Driver is OdbcDriver)); |
| 235 | + } |
| 236 | + |
| 237 | + |
| 238 | + [Test] |
| 239 | + public void HqlEqualityParameterCanExceedColumnSize() |
| 240 | + { |
| 241 | + // We should be able to query a column with a value longer than |
| 242 | + // the specified column size, to avoid tedious exceptions. |
| 243 | + |
| 244 | + Exception exception = CatchException<Exception>( |
| 245 | + () => |
| 246 | + { |
| 247 | + using (ISession s = OpenSession()) |
| 248 | + using (s.BeginTransaction()) |
| 249 | + { |
| 250 | + s.Save(new StringClass {Id = 1, StringValue = "AAAAAAAAAB"}); |
| 251 | + s.Save(new StringClass {Id = 2, StringValue = "BAAAAAAAAA"}); |
| 252 | + |
| 253 | + var aaItems = |
| 254 | + s.CreateQuery("from StringClass s where s.StringValue = :likeValue") |
| 255 | + .SetParameter("likeValue", "AAAAAAAAABx") |
| 256 | + .List(); |
| 257 | + |
| 258 | + Assert.That(aaItems.Count, Is.EqualTo(0)); |
| 259 | + } |
| 260 | + }); |
| 261 | + |
| 262 | + // Doesn't work on Firebird due to Firebird not figuring out parameter types on its own. |
| 263 | + // This test fails against the ODBC driver. The driver would need to be override to allow longer parameter sizes than the column. |
| 264 | + AssertExpectedFailureOrNoException( |
| 265 | + exception, |
| 266 | + (Dialect is FirebirdDialect) || (sessions.ConnectionProvider.Driver is OdbcDriver)); |
| 267 | + } |
| 268 | + |
| 269 | + |
| 270 | + /// <summary> |
| 271 | + /// Some test cases doesn't work during some scenarios for well-known reasons. If the test |
| 272 | + /// fails under these circumstances, mark it as IGNORED. If it _stops_ failing, mark it |
| 273 | + /// as a FAILURE so that it can be investigated. |
| 274 | + /// </summary> |
| 275 | + private void AssertExpectedFailureOrNoException(Exception exception, bool requireExceptionAndIgnoreTest) |
| 276 | + { |
| 277 | + if (requireExceptionAndIgnoreTest) |
111 | 278 | {
|
112 |
| - s.Save(new StringClass() { Id = 1, StringValue = "AAAAAAAAAB" }); |
113 |
| - s.Save(new StringClass() { Id = 2, StringValue = "BAAAAAAAAA" }); |
| 279 | + Assert.NotNull( |
| 280 | + exception, |
| 281 | + "Test was expected to have a well-known, but ignored, failure for the current configuration. If " + |
| 282 | + "that expected failure no longer occurs, it may now be possible to remove this exception."); |
| 283 | + |
| 284 | + Assert.Ignore("This test is known to fail for the current configuration."); |
| 285 | + } |
| 286 | + |
| 287 | + // If the above didn't ignore the exception, it's for real - rethrow to trigger test failure. |
| 288 | + if (exception != null) |
| 289 | + throw new Exception("Wrapped exception.", exception); |
| 290 | + } |
114 | 291 |
|
115 |
| - var aaItems = |
116 |
| - s.CreateQuery("from StringClass s where s.StringValue like :likeValue") |
117 |
| - .SetParameter("likeValue", "%AAAAAAAAA%") |
118 |
| - .List(); |
119 | 292 |
|
120 |
| - Assert.That(aaItems.Count, Is.EqualTo(2)); |
| 293 | + private TException CatchException<TException>(System.Action action) |
| 294 | + where TException : Exception |
| 295 | + { |
| 296 | + try |
| 297 | + { |
| 298 | + action(); |
121 | 299 | }
|
| 300 | + catch (TException exception) |
| 301 | + { |
| 302 | + return exception; |
| 303 | + } |
| 304 | + |
| 305 | + return null; |
122 | 306 | }
|
123 | 307 | }
|
124 | 308 | }
|
0 commit comments