forked from nhibernate/nhibernate-core
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathDriverBase.cs
365 lines (316 loc) · 11.4 KB
/
DriverBase.cs
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
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using System.Linq;
using NHibernate.Engine;
using NHibernate.SqlCommand;
using NHibernate.SqlTypes;
using NHibernate.Util;
using Environment = NHibernate.Cfg.Environment;
namespace NHibernate.Driver
{
/// <summary>
/// Base class for the implementation of IDriver
/// </summary>
public abstract class DriverBase : IDriver, ISqlParameterFormatter
{
private static readonly INHibernateLogger log = NHibernateLogger.For(typeof(DriverBase));
private int commandTimeout;
private bool prepareSql;
public virtual void Configure(IDictionary<string, string> settings)
{
// Command timeout
commandTimeout = PropertiesHelper.GetInt32(Environment.CommandTimeout, settings, -1);
if (commandTimeout > -1 && log.IsInfoEnabled())
{
log.Info(string.Format("setting ADO.NET command timeout to {0} seconds", commandTimeout));
}
// Prepare SQL
prepareSql = PropertiesHelper.GetBoolean(Environment.PrepareSql, settings, false);
if (prepareSql && SupportsPreparingCommands)
{
log.Info("preparing SQL enabled");
}
}
protected bool IsPrepareSqlEnabled
{
get { return prepareSql; }
}
public abstract DbConnection CreateConnection();
public abstract DbCommand CreateCommand();
/// <summary>
/// Unwraps the <see cref="DbCommand"/> in case it is wrapped, otherwise the same instance is returned.
/// </summary>
/// <param name="command">The command to unwrap.</param>
/// <returns>The unwrapped command.</returns>
public virtual DbCommand UnwrapDbCommand(DbCommand command)
{
return command;
}
/// <summary>
/// Begin an ADO <see cref="DbTransaction" />.
/// </summary>
/// <param name="isolationLevel">The isolation level requested for the transaction.</param>
/// <param name="connection">The connection on which to start the transaction.</param>
/// <returns>The started <see cref="DbTransaction" />.</returns>
public virtual DbTransaction BeginTransaction(IsolationLevel isolationLevel, DbConnection connection)
{
if (isolationLevel == IsolationLevel.Unspecified)
{
return connection.BeginTransaction();
}
return connection.BeginTransaction(isolationLevel);
}
/// <summary>
/// Does this Driver require the use of a Named Prefix in the SQL statement.
/// </summary>
/// <remarks>
/// For example, SqlClient requires <c>select * from simple where simple_id = @simple_id</c>
/// If this is false, like with the OleDb provider, then it is assumed that
/// the <c>?</c> can be a placeholder for the parameter in the SQL statement.
/// </remarks>
public abstract bool UseNamedPrefixInSql { get; }
/// <summary>
/// Does this Driver require the use of the Named Prefix when trying
/// to reference the Parameter in the Command's Parameter collection.
/// </summary>
/// <remarks>
/// This is really only useful when the UseNamedPrefixInSql == true. When this is true the
/// code will look like:
/// <code>DbParameter param = cmd.Parameters["@paramName"]</code>
/// if this is false the code will be
/// <code>DbParameter param = cmd.Parameters["paramName"]</code>.
/// </remarks>
public abstract bool UseNamedPrefixInParameter { get; }
/// <summary>
/// The Named Prefix for parameters.
/// </summary>
/// <remarks>
/// Sql Server uses <c>"@"</c> and Oracle uses <c>":"</c>.
/// </remarks>
public abstract string NamedPrefix { get; }
/// <summary>
/// Change the parameterName into the correct format DbCommand.CommandText
/// for the ConnectionProvider
/// </summary>
/// <param name="parameterName">The unformatted name of the parameter</param>
/// <returns>A parameter formatted for an DbCommand.CommandText</returns>
public string FormatNameForSql(string parameterName)
{
return UseNamedPrefixInSql ? (NamedPrefix + parameterName) : StringHelper.SqlParameter;
}
/// <summary>
/// Changes the parameterName into the correct format for an DbParameter
/// for the Driver.
/// </summary>
/// <remarks>
/// For SqlServerConnectionProvider it will change <c>id</c> to <c>@id</c>
/// </remarks>
/// <param name="parameterName">The unformatted name of the parameter</param>
/// <returns>A parameter formatted for an DbParameter.</returns>
public string FormatNameForParameter(string parameterName)
{
return UseNamedPrefixInParameter ? (NamedPrefix + parameterName) : parameterName;
}
public virtual bool SupportsMultipleOpenReaders
{
get { return true; }
}
/// <summary>
/// Does this Driver support DbCommand.Prepare().
/// </summary>
/// <remarks>
/// <para>
/// A value of <see langword="false" /> indicates that an exception would be thrown or the
/// company that produces the Driver we are wrapping does not recommend using
/// DbCommand.Prepare().
/// </para>
/// <para>
/// A value of <see langword="true" /> indicates that calling DbCommand.Prepare() will function
/// fine on this Driver.
/// </para>
/// </remarks>
protected virtual bool SupportsPreparingCommands
{
get { return true; }
}
public virtual DbCommand GenerateCommand(CommandType type, SqlString sqlString, SqlType[] parameterTypes)
{
var cmd = CreateCommand();
cmd.CommandType = type;
SetCommandTimeout(cmd);
SetCommandText(cmd, sqlString);
SetCommandParameters(cmd, parameterTypes);
return cmd;
}
protected virtual void SetCommandTimeout(DbCommand cmd)
{
if (commandTimeout >= 0)
{
try
{
cmd.CommandTimeout = commandTimeout;
}
catch (Exception e)
{
if (log.IsWarnEnabled())
{
log.Warn(e, e.ToString());
}
}
}
}
private static string ToParameterName(int index)
{
return "p" + index;
}
string ISqlParameterFormatter.GetParameterName(int index)
{
return FormatNameForSql(ToParameterName(index));
}
private void SetCommandText(DbCommand cmd, SqlString sqlString)
{
SqlStringFormatter formatter = GetSqlStringFormatter();
formatter.Format(sqlString);
cmd.CommandText = formatter.GetFormattedText();
}
protected virtual SqlStringFormatter GetSqlStringFormatter()
{
return new SqlStringFormatter(this, ";");
}
private void SetCommandParameters(DbCommand cmd, SqlType[] sqlTypes)
{
for (int i = 0; i < sqlTypes.Length; i++)
{
string paramName = ToParameterName(i);
var dbParam = GenerateParameter(cmd, paramName, sqlTypes[i]);
cmd.Parameters.Add(dbParam);
}
}
protected virtual void InitializeParameter(DbParameter dbParam, string name, SqlType sqlType)
{
if (sqlType == null)
{
throw new QueryException(String.Format("No type assigned to parameter '{0}'", name));
}
dbParam.ParameterName = FormatNameForParameter(name);
dbParam.DbType = sqlType.DbType;
}
/// <summary>
/// Generates an DbParameter for the DbCommand. It does not add the DbParameter to the DbCommand's
/// Parameter collection.
/// </summary>
/// <param name="command">The DbCommand to use to create the DbParameter.</param>
/// <param name="name">The name to set for DbParameter.Name</param>
/// <param name="sqlType">The SqlType to set for DbParameter.</param>
/// <returns>An DbParameter ready to be added to an DbCommand.</returns>
public DbParameter GenerateParameter(DbCommand command, string name, SqlType sqlType)
{
var dbParam = command.CreateParameter();
InitializeParameter(dbParam, name, sqlType);
return dbParam;
}
public void RemoveUnusedCommandParameters(DbCommand cmd, SqlString sqlString)
{
if (!UseNamedPrefixInSql)
return; // Applicable only to named parameters
var formatter = GetSqlStringFormatter();
formatter.Format(sqlString);
var assignedParameterNames = new HashSet<string>(formatter.AssignedParameterNames);
cmd.Parameters
.Cast<DbParameter>()
.Select(p => p.ParameterName)
.Where(p => !assignedParameterNames.Contains(UseNamedPrefixInParameter ? p : FormatNameForSql(p)))
.ToList()
.ForEach(unusedParameterName => cmd.Parameters.RemoveAt(unusedParameterName));
}
public virtual void ExpandQueryParameters(DbCommand cmd, SqlString sqlString, SqlType[] parameterTypes)
{
if (UseNamedPrefixInSql)
return; // named parameters are ok
var expandedParameters = new List<DbParameter>();
foreach (object part in sqlString)
{
var parameter = part as Parameter;
if (parameter != null)
{
var index = parameter.ParameterPosition.Value;
var originalParameter = cmd.Parameters[index];
var originalType = parameterTypes[index];
expandedParameters.Add(CloneParameter(cmd, originalParameter, originalType));
}
}
cmd.Parameters.Clear();
foreach (var parameter in expandedParameters)
cmd.Parameters.Add(parameter);
}
public virtual IResultSetsCommand GetResultSetsCommand(ISessionImplementor session)
{
throw new NotSupportedException(string.Format("The driver {0} does not support multiple queries.", GetType().FullName));
}
public virtual bool SupportsMultipleQueries
{
get { return false; }
}
protected virtual DbParameter CloneParameter(DbCommand cmd, DbParameter originalParameter, SqlType originalType)
{
var clone = GenerateParameter(cmd, originalParameter.ParameterName, originalType);
clone.Value = originalParameter.Value;
return clone;
}
public void PrepareCommand(DbCommand command)
{
AdjustCommand(command);
OnBeforePrepare(command);
if (SupportsPreparingCommands && prepareSql)
{
command.Prepare();
}
}
/// <summary>
/// Override to make any adjustments to the DbCommand object. (e.g., Oracle custom OUT parameter)
/// Parameters have been bound by this point, so their order can be adjusted too.
/// This is analogous to the RegisterResultSetOutParameter() function in Hibernate.
/// </summary>
protected virtual void OnBeforePrepare(DbCommand command)
{
}
/// <summary>
/// Override to make any adjustments to each DbCommand object before it added to the batcher.
/// </summary>
/// <param name="command">The command.</param>
/// <remarks>
/// This method is similar to the <see cref="OnBeforePrepare"/> but, instead be called just before execute the command (that can be a batch)
/// is executed before add each single command to the batcher and before <see cref="OnBeforePrepare"/> .
/// If you have to adjust parameters values/type (when the command is full filled) this is a good place where do it.
/// </remarks>
public virtual void AdjustCommand(DbCommand command)
{
}
public DbParameter GenerateOutputParameter(DbCommand command)
{
var param = GenerateParameter(command, "ReturnValue", SqlTypeFactory.Int32);
param.Direction = ParameterDirection.Output;
return param;
}
public virtual bool RequiresTimeSpanForTime => false;
#if NETFX
public virtual bool SupportsSystemTransactions => true;
public virtual bool SupportsNullEnlistment => true;
#else
public virtual bool SupportsSystemTransactions => false;
public virtual bool SupportsNullEnlistment => false;
#endif
/// <inheritdoc />
public virtual bool SupportsEnlistmentWhenAutoEnlistmentIsDisabled => true;
public virtual bool HasDelayedDistributedTransactionCompletion => false;
/// <inheritdoc />
public virtual DateTime MinDate => DateTime.MinValue;
//6.0 TODO: Add property definition to IDialect
/// <summary>
/// Get the timeout in seconds for ADO.NET queries.
/// </summary>
public virtual int CommandTimeout => commandTimeout;
}
}