forked from npgsql/npgsql
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathNpgsqlParameterCollectionTests.cs
More file actions
343 lines (283 loc) · 12.8 KB
/
NpgsqlParameterCollectionTests.cs
File metadata and controls
343 lines (283 loc) · 12.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
using NpgsqlTypes;
using NUnit.Framework;
using System;
using System.Data;
using System.Data.Common;
using System.Diagnostics.CodeAnalysis;
namespace Npgsql.Tests;
[NonParallelizable] // This test class has global effects on case sensitive matching in param collection.
[TestFixture(CompatMode.OnePass)]
[TestFixture(CompatMode.TwoPass)]
public class NpgsqlParameterCollectionTests
{
readonly CompatMode _compatMode;
const int LookupThreshold = NpgsqlParameterCollection.LookupThreshold;
[Test]
public void Can_only_add_NpgsqlParameter()
{
using var command = new NpgsqlCommand();
Assert.That(() => command.Parameters.Add("hello"), Throws.Exception.TypeOf<InvalidCastException>());
Assert.That(() => command.Parameters.Add(new SomeOtherDbParameter()), Throws.Exception.TypeOf<InvalidCastException>());
Assert.That(() => command.Parameters.Add(null!), Throws.Exception.TypeOf<ArgumentNullException>());
}
/// <summary>
/// Test which validates that Clear() indeed cleans up the parameters in a command so they can be added to other commands safely.
/// </summary>
[Test]
public void Clear()
{
var p = new NpgsqlParameter();
var c1 = new NpgsqlCommand();
var c2 = new NpgsqlCommand();
c1.Parameters.Add(p);
Assert.AreEqual(1, c1.Parameters.Count);
Assert.AreEqual(0, c2.Parameters.Count);
c1.Parameters.Clear();
Assert.AreEqual(0, c1.Parameters.Count);
c2.Parameters.Add(p);
Assert.AreEqual(0, c1.Parameters.Count);
Assert.AreEqual(1, c2.Parameters.Count);
}
[Test]
public void Hash_lookup_parameter_rename_bug()
{
if (_compatMode == CompatMode.TwoPass)
return;
using var command = new NpgsqlCommand();
// Put plenty of parameters in the collection to turn on hash lookup functionality.
for (var i = 0; i < LookupThreshold; i++)
{
command.Parameters.AddWithValue(string.Format("p{0:00}", i + 1), NpgsqlDbType.Text, string.Format("String parameter value {0}", i + 1));
}
// Make sure hash lookup is generated.
Assert.AreEqual(command.Parameters["p03"].ParameterName, "p03");
// Rename the target parameter.
command.Parameters["p03"].ParameterName = "a_new_name";
// Try to exploit the hash lookup bug.
// If the bug exists, the hash lookups will be out of sync with the list, and be unable
// to find the parameter by its new name.
Assert.That(command.Parameters.IndexOf("a_new_name"), Is.GreaterThanOrEqualTo(0));
}
[Test]
public void Remove_duplicate_parameter([Values(LookupThreshold, LookupThreshold - 2)] int count)
{
if (_compatMode == CompatMode.OnePass)
return;
using var command = new NpgsqlCommand();
// Put plenty of parameters in the collection to turn on hash lookup functionality.
for (var i = 0; i < count; i++)
{
command.Parameters.AddWithValue(string.Format("p{0:00}", i + 1), NpgsqlDbType.Text,
string.Format("String parameter value {0}", i + 1));
}
// Make sure lookup is generated.
Assert.AreEqual(command.Parameters["p02"].ParameterName, "p02");
// Add uppercased version causing a list to be created.
command.Parameters.AddWithValue("P02", NpgsqlDbType.Text, "String parameter value 2");
// Remove the original parameter by its name causing the multivalue to use a single value again.
command.Parameters.Remove(command.Parameters["p02"]);
// Test whether we can still find the last added parameter, and if its index is correctly shifted in the lookup.
Assert.IsTrue(command.Parameters.IndexOf("p02") == count - 1);
Assert.IsTrue(command.Parameters.IndexOf("P02") == count - 1);
// And finally test whether other parameters were also correctly shifted.
Assert.IsTrue(command.Parameters.IndexOf("p03") == 1);
}
[Test]
public void Remove_parameter([Values(LookupThreshold, LookupThreshold - 2)] int count)
{
using var command = new NpgsqlCommand();
// Put plenty of parameters in the collection to turn on hash lookup functionality.
for (var i = 0; i < count; i++)
{
command.Parameters.AddWithValue(string.Format("p{0:00}", i + 1), NpgsqlDbType.Text,
string.Format("String parameter value {0}", i + 1));
}
// Remove the parameter by its name
command.Parameters.Remove(command.Parameters["p02"]);
// Make sure we cannot find it, also not case insensitively.
Assert.IsTrue(command.Parameters.IndexOf("p02") == -1);
Assert.IsTrue(command.Parameters.IndexOf("P02") == -1);
}
[Test]
public void Remove_case_differing_parameter([Values(LookupThreshold, LookupThreshold - 2)] int count)
{
// We add two case-differing parameters which will match as well, before adding the others.
using var command = new NpgsqlCommand();
command.Parameters.Add(new NpgsqlParameter("PP0", 1));
command.Parameters.Add(new NpgsqlParameter("Pp0", 1));
for (var i = 0; i < count - 2; i++)
command.Parameters.Add(new NpgsqlParameter($"pp{i}", i));
// Removing Pp0.
command.Parameters.RemoveAt(1);
// Exact match to pp0 or case insensitive match to PP0 depending on mode.
Assert.That(command.Parameters.IndexOf("pp0"), Is.EqualTo(_compatMode == CompatMode.TwoPass ? 1 : 0));
// Exact match to PP0.
Assert.That(command.Parameters.IndexOf("PP0"), Is.EqualTo(0));
// Case insensitive match to PP0.
Assert.That(command.Parameters.IndexOf("Pp0"), Is.EqualTo(0));
}
[Test]
public void Correct_index_returned_for_duplicate_ParameterName([Values(LookupThreshold, LookupThreshold - 2)] int count)
{
if (_compatMode == CompatMode.OnePass)
return;
using var command = new NpgsqlCommand();
// Put plenty of parameters in the collection to turn on hash lookup functionality.
for (var i = 0; i < count; i++)
{
command.Parameters.AddWithValue(string.Format("parameter{0:00}", i + 1), NpgsqlDbType.Text, string.Format("String parameter value {0}", i + 1));
}
// Make sure lookup is generated.
Assert.AreEqual(command.Parameters["parameter02"].ParameterName, "parameter02");
// Add uppercased version.
command.Parameters.AddWithValue("Parameter02", NpgsqlDbType.Text, "String parameter value 2");
// Insert another case insensitive before the original.
command.Parameters.Insert(0, new NpgsqlParameter("ParameteR02", NpgsqlDbType.Text) { Value = "String parameter value 2" });
// Try to find the exact index.
Assert.IsTrue(command.Parameters.IndexOf("parameter02") == 2);
Assert.IsTrue(command.Parameters.IndexOf("Parameter02") == command.Parameters.Count - 1);
Assert.IsTrue(command.Parameters.IndexOf("ParameteR02") == 0);
// This name does not exist so we expect the first case insensitive match to be returned.
Assert.IsTrue(command.Parameters.IndexOf("ParaMeteR02") == 0);
// And finally test whether other parameters were also correctly shifted.
Assert.IsTrue(command.Parameters.IndexOf("parameter03") == 3);
}
[Test]
public void Finds_case_insensitive_lookups([Values(LookupThreshold, LookupThreshold - 2)] int count)
{
using var command = new NpgsqlCommand();
var parameters = command.Parameters;
for (var i = 0; i < count; i++)
parameters.Add(new NpgsqlParameter($"p{i}", i));
Assert.That(command.Parameters.IndexOf("P1"), Is.EqualTo(1));
}
[Test]
public void Finds_case_sensitive_lookups([Values(LookupThreshold, LookupThreshold - 2)] int count)
{
using var command = new NpgsqlCommand();
var parameters = command.Parameters;
for (var i = 0; i < count; i++)
parameters.Add(new NpgsqlParameter($"p{i}", i));
Assert.That(command.Parameters.IndexOf("p1"), Is.EqualTo(1));
}
[Test]
public void Throws_on_indexer_mismatch([Values(LookupThreshold, LookupThreshold - 2)] int count)
{
using var command = new NpgsqlCommand();
var parameters = command.Parameters;
for (var i = 0; i < count; i++)
parameters.Add(new NpgsqlParameter($"p{i}", i));
Assert.DoesNotThrow(() =>
{
command.Parameters["p1"] = new NpgsqlParameter("p1", 1);
command.Parameters["p1"] = new NpgsqlParameter("P1", 1);
});
Assert.Throws<ArgumentException>(() =>
{
command.Parameters["p1"] = new NpgsqlParameter("p2", 1);
});
}
[Test]
public void Positional_parameter_lookup_returns_first_match([Values(LookupThreshold, LookupThreshold - 2)] int count)
{
using var command = new NpgsqlCommand();
var parameters = command.Parameters;
for (var i = 0; i < count; i++)
parameters.Add(new NpgsqlParameter(NpgsqlParameter.PositionalName, i));
Assert.That(command.Parameters.IndexOf(""), Is.EqualTo(0));
}
[Test]
public void IndexOf_falls_back_to_first_insensitive_match([Values] bool manyParams)
{
if (_compatMode == CompatMode.OnePass)
return;
using var command = new NpgsqlCommand();
var parameters = command.Parameters;
parameters.Add(new NpgsqlParameter("foo", 8));
parameters.Add(new NpgsqlParameter("bar", 8));
parameters.Add(new NpgsqlParameter("BAR", 8));
Assert.That(parameters, Has.Count.LessThan(LookupThreshold));
if (manyParams)
for (var i = 0; i < LookupThreshold; i++)
parameters.Add(new NpgsqlParameter($"p{i}", i));
Assert.That(parameters.IndexOf("Bar"), Is.EqualTo(1));
}
[Test]
public void IndexOf_prefers_case_sensitive_match([Values] bool manyParams)
{
if (_compatMode == CompatMode.OnePass)
return;
using var command = new NpgsqlCommand();
var parameters = command.Parameters;
parameters.Add(new NpgsqlParameter("FOO", 8));
parameters.Add(new NpgsqlParameter("foo", 8));
Assert.That(parameters, Has.Count.LessThan(LookupThreshold));
if (manyParams)
for (var i = 0; i < LookupThreshold; i++)
parameters.Add(new NpgsqlParameter($"p{i}", i));
Assert.That(parameters.IndexOf("foo"), Is.EqualTo(1));
}
[Test]
public void IndexOf_matches_all_parameter_syntaxes()
{
using var command = new NpgsqlCommand();
var parameters = command.Parameters;
parameters.Add(new NpgsqlParameter("@foo0", 8));
parameters.Add(new NpgsqlParameter(":foo1", 8));
parameters.Add(new NpgsqlParameter("foo2", 8));
for (var i = 0; i < parameters.Count; i++)
{
Assert.That(parameters.IndexOf("foo" + i), Is.EqualTo(i));
Assert.That(parameters.IndexOf("@foo" + i), Is.EqualTo(i));
Assert.That(parameters.IndexOf(":foo" + i), Is.EqualTo(i));
}
}
[Test]
public void Cloning_succeeds([Values(LookupThreshold, LookupThreshold - 2)] int count)
{
var command = new NpgsqlCommand();
for (var i = 0; i < count; i++)
{
command.Parameters.Add(new NpgsqlParameter());
}
Assert.DoesNotThrow(() => command.Clone());
}
[Test]
public void Clean_name()
{
var param = new NpgsqlParameter();
var command = new NpgsqlCommand();
command.Parameters.Add(param);
param.ParameterName = null;
// These should not throw exceptions
Assert.AreEqual(0, command.Parameters.IndexOf(param.ParameterName));
Assert.AreEqual(NpgsqlParameter.PositionalName, param.ParameterName);
}
public NpgsqlParameterCollectionTests(CompatMode compatMode)
{
_compatMode = compatMode;
#if DEBUG
NpgsqlParameterCollection.TwoPassCompatMode = compatMode == CompatMode.TwoPass;
#else
if (compatMode == CompatMode.TwoPass)
Assert.Ignore("Cannot test case-insensitive NpgsqlParameterCollection behavior in RELEASE");
#endif
}
class SomeOtherDbParameter : DbParameter
{
public override void ResetDbType() {}
public override DbType DbType { get; set; }
public override ParameterDirection Direction { get; set; }
public override bool IsNullable { get; set; }
[AllowNull] public override string ParameterName { get; set; } = "";
[AllowNull] public override string SourceColumn { get; set; } = "";
public override object? Value { get; set; }
public override bool SourceColumnNullMapping { get; set; }
public override int Size { get; set; }
}
}
public enum CompatMode
{
TwoPass,
OnePass
}