[jit] Fix the saving of the 'cfg->ret_var_set' flag when inlining, it was set to...
[mono.git] / mcs / class / Mono.Data.Sqlite / Mono.Data.Sqlite_2.0 / SQLiteCommand.cs
1 /********************************************************\r
2  * ADO.NET 2.0 Data Provider for SQLite Version 3.X\r
3  * Written by Robert Simpson (robert@blackcastlesoft.com)\r
4  * \r
5  * Released to the public domain, use at your own risk!\r
6  ********************************************************/\r
7 \r
8 namespace Mono.Data.Sqlite\r
9 {\r
10   using System;\r
11   using System.Data;\r
12   using System.Data.Common;\r
13   using System.Collections.Generic;\r
14   using System.ComponentModel;\r
15 \r
16   /// <summary>\r
17   /// SQLite implementation of DbCommand.\r
18   /// </summary>\r
19 #if !PLATFORM_COMPACTFRAMEWORK\r
20   [Designer("SQLite.Designer.SqliteCommandDesigner, SQLite.Designer, Version=1.0.36.0, Culture=neutral, PublicKeyToken=db937bc2d44ff139"), ToolboxItem(true)]\r
21 #endif\r
22   public sealed class SqliteCommand : DbCommand, ICloneable\r
23   {\r
24     /// <summary>\r
25     /// The command text this command is based on\r
26     /// </summary>\r
27     private string _commandText;\r
28     /// <summary>\r
29     /// The connection the command is associated with\r
30     /// </summary>\r
31     private SqliteConnection _cnn;\r
32     /// <summary>\r
33     /// The version of the connection the command is associated with\r
34     /// </summary>\r
35     private long _version;\r
36     /// <summary>\r
37     /// Indicates whether or not a DataReader is active on the command.\r
38     /// </summary>\r
39     private WeakReference _activeReader;\r
40     /// <summary>\r
41     /// The timeout for the command, kludged because SQLite doesn't support per-command timeout values\r
42     /// </summary>\r
43     internal int _commandTimeout;\r
44     /// <summary>\r
45     /// Designer support\r
46     /// </summary>\r
47     private bool _designTimeVisible;\r
48     /// <summary>\r
49     /// Used by DbDataAdapter to determine updating behavior\r
50     /// </summary>\r
51     private UpdateRowSource _updateRowSource;\r
52     /// <summary>\r
53     /// The collection of parameters for the command\r
54     /// </summary>\r
55     private SqliteParameterCollection _parameterCollection;\r
56     /// <summary>\r
57     /// The SQL command text, broken into individual SQL statements as they are executed\r
58     /// </summary>\r
59     internal List<SqliteStatement> _statementList;\r
60     /// <summary>\r
61     /// Unprocessed SQL text that has not been executed\r
62     /// </summary>\r
63     internal string _remainingText;\r
64     /// <summary>\r
65     /// Transaction associated with this command\r
66     /// </summary>\r
67     private SqliteTransaction _transaction;\r
68 \r
69     ///<overloads>\r
70     /// Constructs a new SqliteCommand\r
71     /// </overloads>\r
72     /// <summary>\r
73     /// Default constructor\r
74     /// </summary>\r
75     public SqliteCommand() :this(null, null)\r
76     {\r
77     }\r
78 \r
79     /// <summary>\r
80     /// Initializes the command with the given command text\r
81     /// </summary>\r
82     /// <param name="commandText">The SQL command text</param>\r
83     public SqliteCommand(string commandText) \r
84       : this(commandText, null, null)\r
85     {\r
86     }\r
87 \r
88     /// <summary>\r
89     /// Initializes the command with the given SQL command text and attach the command to the specified\r
90     /// connection.\r
91     /// </summary>\r
92     /// <param name="commandText">The SQL command text</param>\r
93     /// <param name="connection">The connection to associate with the command</param>\r
94     public SqliteCommand(string commandText, SqliteConnection connection)\r
95       : this(commandText, connection, null)\r
96     {\r
97     }\r
98 \r
99     /// <summary>\r
100     /// Initializes the command and associates it with the specified connection.\r
101     /// </summary>\r
102     /// <param name="connection">The connection to associate with the command</param>\r
103     public SqliteCommand(SqliteConnection connection) \r
104       : this(null, connection, null)\r
105     {\r
106     }\r
107 \r
108     private SqliteCommand(SqliteCommand source) : this(source.CommandText, source.Connection, source.Transaction)\r
109     {\r
110       CommandTimeout = source.CommandTimeout;\r
111       DesignTimeVisible = source.DesignTimeVisible;\r
112       UpdatedRowSource = source.UpdatedRowSource;\r
113 \r
114       foreach (SqliteParameter param in source._parameterCollection)\r
115       {\r
116         Parameters.Add(param.Clone());\r
117       }\r
118     }\r
119 \r
120     /// <summary>\r
121     /// Initializes a command with the given SQL, connection and transaction\r
122     /// </summary>\r
123     /// <param name="commandText">The SQL command text</param>\r
124     /// <param name="connection">The connection to associate with the command</param>\r
125     /// <param name="transaction">The transaction the command should be associated with</param>\r
126     public SqliteCommand(string commandText, SqliteConnection connection, SqliteTransaction transaction)\r
127     {\r
128       _statementList = null;\r
129       _activeReader = null;\r
130       _commandTimeout = 30;\r
131       _parameterCollection = new SqliteParameterCollection(this);\r
132       _designTimeVisible = true;\r
133       _updateRowSource = UpdateRowSource.None;\r
134       _transaction = null;\r
135 \r
136       if (commandText != null)\r
137         CommandText = commandText;\r
138 \r
139       if (connection != null)\r
140       {\r
141         DbConnection = connection;\r
142         _commandTimeout = connection.DefaultTimeout;\r
143       }\r
144 \r
145       if (transaction != null)\r
146         Transaction = transaction;\r
147     }\r
148 \r
149     /// <summary>\r
150     /// Disposes of the command and clears all member variables\r
151     /// </summary>\r
152     /// <param name="disposing">Whether or not the class is being explicitly or implicitly disposed</param>\r
153     protected override void Dispose(bool disposing)\r
154     {\r
155       base.Dispose(disposing);\r
156 \r
157       if (disposing)\r
158       {\r
159         // If a reader is active on this command, don't destroy the command, instead let the reader do it\r
160         SqliteDataReader reader = null;\r
161         if (_activeReader != null)\r
162         {\r
163           try\r
164           {\r
165             reader = _activeReader.Target as SqliteDataReader;\r
166           }\r
167           catch\r
168           {\r
169           }\r
170         }\r
171 \r
172         if (reader != null)\r
173         {\r
174           reader._disposeCommand = true;\r
175           _activeReader = null;\r
176           return;\r
177         }\r
178 \r
179         Connection = null;\r
180         _parameterCollection.Clear();\r
181         _commandText = null;\r
182       }\r
183     }\r
184 \r
185     /// <summary>\r
186     /// Clears and destroys all statements currently prepared\r
187     /// </summary>\r
188     internal void ClearCommands()\r
189     {\r
190       if (_activeReader != null)\r
191       {\r
192         SqliteDataReader reader = null;\r
193         try\r
194         {\r
195           reader = _activeReader.Target as SqliteDataReader;\r
196         }\r
197         catch\r
198         {\r
199         }\r
200 \r
201         if (reader != null)\r
202           reader.Close();\r
203 \r
204         _activeReader = null;\r
205       }\r
206 \r
207       if (_statementList == null) return;\r
208 \r
209       int x = _statementList.Count;\r
210       for (int n = 0; n < x; n++)\r
211         _statementList[n].Dispose();\r
212 \r
213       _statementList = null;\r
214 \r
215       _parameterCollection.Unbind();\r
216     }\r
217 \r
218     /// <summary>\r
219     /// Builds an array of prepared statements for each complete SQL statement in the command text\r
220     /// </summary>\r
221     internal SqliteStatement BuildNextCommand()\r
222     {\r
223       SqliteStatement stmt = null;\r
224 \r
225       try\r
226       {\r
227         if (_statementList == null)\r
228           _remainingText = _commandText;\r
229 \r
230         stmt = _cnn._sql.Prepare(_cnn, _remainingText, (_statementList == null) ? null : _statementList[_statementList.Count - 1], (uint)(_commandTimeout * 1000), out _remainingText);\r
231         if (stmt != null)\r
232         {\r
233           stmt._command = this;\r
234           if (_statementList == null)\r
235             _statementList = new List<SqliteStatement>();\r
236 \r
237           _statementList.Add(stmt);\r
238 \r
239           _parameterCollection.MapParameters(stmt);\r
240           stmt.BindParameters();\r
241         }        \r
242         return stmt;\r
243       }\r
244       catch (Exception)\r
245       {\r
246         if (stmt != null)\r
247         {\r
248           if (_statementList.Contains(stmt))\r
249             _statementList.Remove(stmt);\r
250 \r
251           stmt.Dispose();\r
252         }\r
253 \r
254         // If we threw an error compiling the statement, we cannot continue on so set the remaining text to null.\r
255         _remainingText = null;\r
256 \r
257         throw;\r
258       }\r
259     }\r
260 \r
261     internal SqliteStatement GetStatement(int index)\r
262     {\r
263       // Haven't built any statements yet\r
264       if (_statementList == null) return BuildNextCommand();\r
265 \r
266       // If we're at the last built statement and want the next unbuilt statement, then build it\r
267       if (index == _statementList.Count)\r
268       {\r
269         if (String.IsNullOrEmpty(_remainingText) == false) return BuildNextCommand();\r
270         else return null; // No more commands\r
271       }\r
272 \r
273       SqliteStatement stmt = _statementList[index];\r
274       stmt.BindParameters();\r
275 \r
276       return stmt;\r
277     }\r
278 \r
279     /// <summary>\r
280     /// Not implemented\r
281     /// </summary>\r
282     public override void Cancel()\r
283     {\r
284       if (_activeReader != null)\r
285       {\r
286         SqliteDataReader reader = _activeReader.Target as SqliteDataReader;\r
287         if (reader != null)\r
288           reader.Cancel();\r
289       }\r
290     }\r
291 \r
292     /// <summary>\r
293     /// The SQL command text associated with the command\r
294     /// </summary>\r
295 #if !PLATFORM_COMPACTFRAMEWORK\r
296     [DefaultValue(""), RefreshProperties(RefreshProperties.All), Editor("Microsoft.VSDesigner.Data.SQL.Design.SqlCommandTextEditor, Microsoft.VSDesigner, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", "System.Drawing.Design.UITypeEditor, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a")]\r
297 #endif\r
298     public override string CommandText\r
299     {\r
300       get\r
301       {\r
302         return _commandText;\r
303       }\r
304       set\r
305       {\r
306         if (_commandText == value) return;\r
307 \r
308         if (_activeReader != null && _activeReader.IsAlive)\r
309         {\r
310           throw new InvalidOperationException("Cannot set CommandText while a DataReader is active");\r
311         }\r
312 \r
313         ClearCommands();\r
314         _commandText = value;\r
315 \r
316         if (_cnn == null) return;\r
317       }\r
318     }\r
319 \r
320     /// <summary>\r
321     /// The amount of time to wait for the connection to become available before erroring out\r
322     /// </summary>\r
323 #if !PLATFORM_COMPACTFRAMEWORK\r
324     [DefaultValue((int)30)]\r
325 #endif\r
326     public override int CommandTimeout\r
327     {\r
328       get\r
329       {\r
330         return _commandTimeout;\r
331       }\r
332       set\r
333       {\r
334         _commandTimeout = value;\r
335       }\r
336     }\r
337 \r
338     /// <summary>\r
339     /// The type of the command.  SQLite only supports CommandType.Text\r
340     /// </summary>\r
341 #if !PLATFORM_COMPACTFRAMEWORK\r
342     [RefreshProperties(RefreshProperties.All), DefaultValue(CommandType.Text)]\r
343 #endif\r
344     public override CommandType CommandType\r
345     {\r
346       get\r
347       {\r
348         return CommandType.Text;\r
349       }\r
350       set\r
351       {\r
352         if (value != CommandType.Text)\r
353         {\r
354           throw new NotSupportedException();\r
355         }\r
356       }\r
357     }\r
358 \r
359     /// <summary>\r
360     /// Forwards to the local CreateParameter() function\r
361     /// </summary>\r
362     /// <returns></returns>\r
363     protected override DbParameter CreateDbParameter()\r
364     {\r
365       return CreateParameter();\r
366     }\r
367 \r
368     /// <summary>\r
369     /// Create a new parameter\r
370     /// </summary>\r
371     /// <returns></returns>\r
372     public new SqliteParameter CreateParameter()\r
373     {\r
374       return new SqliteParameter();\r
375     }\r
376 \r
377     /// <summary>\r
378     /// The connection associated with this command\r
379     /// </summary>\r
380 #if !PLATFORM_COMPACTFRAMEWORK\r
381     [DefaultValue((string)null), Editor("Microsoft.VSDesigner.Data.Design.DbConnectionEditor, Microsoft.VSDesigner, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", "System.Drawing.Design.UITypeEditor, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a")]\r
382 #endif\r
383     public new SqliteConnection Connection\r
384     {\r
385       get { return _cnn; }\r
386       set\r
387       {\r
388         if (_activeReader != null && _activeReader.IsAlive)\r
389           throw new InvalidOperationException("Cannot set Connection while a DataReader is active");\r
390 \r
391         if (_cnn != null)\r
392         {\r
393           ClearCommands();\r
394           //_cnn.RemoveCommand(this);\r
395         }\r
396 \r
397         _cnn = value;\r
398         if (_cnn != null)\r
399           _version = _cnn._version;\r
400 \r
401         //if (_cnn != null)\r
402         //  _cnn.AddCommand(this);\r
403       }\r
404     }\r
405 \r
406     /// <summary>\r
407     /// Forwards to the local Connection property\r
408     /// </summary>\r
409     protected override DbConnection DbConnection\r
410     {\r
411       get\r
412       {\r
413         return Connection;\r
414       }\r
415       set\r
416       {\r
417         Connection = (SqliteConnection)value;\r
418       }\r
419     }\r
420 \r
421     /// <summary>\r
422     /// Returns the SqliteParameterCollection for the given command\r
423     /// </summary>\r
424 #if !PLATFORM_COMPACTFRAMEWORK\r
425     [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]\r
426 #endif\r
427     public new SqliteParameterCollection Parameters\r
428     {\r
429       get { return _parameterCollection; }\r
430     }\r
431 \r
432     /// <summary>\r
433     /// Forwards to the local Parameters property\r
434     /// </summary>\r
435     protected override DbParameterCollection DbParameterCollection\r
436     {\r
437       get\r
438       {\r
439         return Parameters;\r
440       }\r
441     }\r
442 \r
443     /// <summary>\r
444     /// The transaction associated with this command.  SQLite only supports one transaction per connection, so this property forwards to the\r
445     /// command's underlying connection.\r
446     /// </summary>\r
447 #if !PLATFORM_COMPACTFRAMEWORK\r
448     [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]\r
449 #endif\r
450     public new SqliteTransaction Transaction\r
451     {\r
452       get { return _transaction; }\r
453       set\r
454       {\r
455         if (_cnn != null)\r
456         {\r
457           if (_activeReader != null && _activeReader.IsAlive)\r
458             throw new InvalidOperationException("Cannot set Transaction while a DataReader is active");\r
459 \r
460           if (value != null)\r
461           {\r
462             if (value._cnn != _cnn)\r
463               throw new ArgumentException("Transaction is not associated with the command's connection");\r
464           }\r
465           _transaction = value;\r
466         }\r
467         else\r
468         {\r
469           Connection = value.Connection;\r
470           _transaction = value;\r
471         }\r
472       }\r
473     }\r
474 \r
475     /// <summary>\r
476     /// Forwards to the local Transaction property\r
477     /// </summary>\r
478     protected override DbTransaction DbTransaction\r
479     {\r
480       get\r
481       {\r
482         return Transaction;\r
483       }\r
484       set\r
485       {\r
486         Transaction = (SqliteTransaction)value;\r
487       }\r
488     }\r
489 \r
490     /// <summary>\r
491     /// This function ensures there are no active readers, that we have a valid connection,\r
492     /// that the connection is open, that all statements are prepared and all parameters are assigned\r
493     /// in preparation for allocating a data reader.\r
494     /// </summary>\r
495     private void InitializeForReader()\r
496     {\r
497       if (_activeReader != null && _activeReader.IsAlive)\r
498         throw new InvalidOperationException("DataReader already active on this command");\r
499 \r
500       if (_cnn == null)\r
501         throw new InvalidOperationException("No connection associated with this command");\r
502 \r
503       if (_cnn.State != ConnectionState.Open)\r
504         throw new InvalidOperationException("Database is not open");\r
505 \r
506       // If the version of the connection has changed, clear out any previous commands before starting\r
507       if (_cnn._version != _version)\r
508       {\r
509         _version = _cnn._version;\r
510         ClearCommands();\r
511       }\r
512 \r
513       // Map all parameters for statements already built\r
514       _parameterCollection.MapParameters(null);\r
515 \r
516       //// Set the default command timeout\r
517       //_cnn._sql.SetTimeout(_commandTimeout * 1000);\r
518     }\r
519 \r
520     /// <summary>\r
521     /// Creates a new SqliteDataReader to execute/iterate the array of SQLite prepared statements\r
522     /// </summary>\r
523     /// <param name="behavior">The behavior the data reader should adopt</param>\r
524     /// <returns>Returns a SqliteDataReader object</returns>\r
525     protected override DbDataReader ExecuteDbDataReader(CommandBehavior behavior)\r
526     {\r
527       return ExecuteReader(behavior);\r
528     }\r
529 \r
530     /// <summary>\r
531     /// Overrides the default behavior to return a SqliteDataReader specialization class\r
532     /// </summary>\r
533     /// <param name="behavior">The flags to be associated with the reader</param>\r
534     /// <returns>A SqliteDataReader</returns>\r
535     public new SqliteDataReader ExecuteReader(CommandBehavior behavior)\r
536     {\r
537       InitializeForReader();\r
538 \r
539       SqliteDataReader rd = new SqliteDataReader(this, behavior);\r
540       _activeReader = new WeakReference(rd, false);\r
541 \r
542       return rd;\r
543     }\r
544 \r
545     /// <summary>\r
546     /// Overrides the default behavior of DbDataReader to return a specialized SqliteDataReader class\r
547     /// </summary>\r
548     /// <returns>A SqliteDataReader</returns>\r
549     public new SqliteDataReader ExecuteReader()\r
550     {\r
551       return ExecuteReader(CommandBehavior.Default);\r
552     }\r
553 \r
554     /// <summary>\r
555     /// Called by the SqliteDataReader when the data reader is closed.\r
556     /// </summary>\r
557     internal void ClearDataReader()\r
558     {\r
559       _activeReader = null;\r
560     }\r
561 \r
562     /// <summary>\r
563     /// Execute the command and return the number of rows inserted/updated affected by it.\r
564     /// </summary>\r
565     /// <returns></returns>\r
566     public override int ExecuteNonQuery()\r
567     {\r
568       using (SqliteDataReader reader = ExecuteReader(CommandBehavior.SingleRow | CommandBehavior.SingleResult))\r
569       {\r
570         while (reader.NextResult()) ;\r
571         return reader.RecordsAffected;\r
572       }\r
573     }\r
574 \r
575     /// <summary>\r
576     /// Execute the command and return the first column of the first row of the resultset\r
577     /// (if present), or null if no resultset was returned.\r
578     /// </summary>\r
579     /// <returns>The first column of the first row of the first resultset from the query</returns>\r
580     public override object ExecuteScalar()\r
581     {\r
582       using (SqliteDataReader reader = ExecuteReader(CommandBehavior.SingleRow | CommandBehavior.SingleResult))\r
583       {\r
584         if (reader.Read())\r
585           return reader[0];\r
586       }\r
587       return null;\r
588     }\r
589 \r
590     /// <summary>\r
591     /// Does nothing.  Commands are prepared as they are executed the first time, and kept in prepared state afterwards.\r
592     /// </summary>\r
593     public override void Prepare()\r
594     {\r
595     }\r
596 \r
597     /// <summary>\r
598     /// Sets the method the SqliteCommandBuilder uses to determine how to update inserted or updated rows in a DataTable.\r
599     /// </summary>\r
600     [DefaultValue(UpdateRowSource.None)]\r
601     public override UpdateRowSource UpdatedRowSource\r
602     {\r
603       get\r
604       {\r
605         return _updateRowSource;\r
606       }\r
607       set\r
608       {\r
609         _updateRowSource = value;\r
610       }\r
611     }\r
612 \r
613     /// <summary>\r
614     /// Determines if the command is visible at design time.  Defaults to True.\r
615     /// </summary>\r
616 #if !PLATFORM_COMPACTFRAMEWORK\r
617     [DesignOnly(true), Browsable(false), DefaultValue(true), EditorBrowsable(EditorBrowsableState.Never)]\r
618 #endif\r
619     public override bool DesignTimeVisible\r
620     {\r
621       get\r
622       {\r
623         return _designTimeVisible;\r
624       }\r
625       set\r
626       {\r
627         _designTimeVisible = value;\r
628 #if !PLATFORM_COMPACTFRAMEWORK\r
629         TypeDescriptor.Refresh(this);\r
630 #endif\r
631       }\r
632     }\r
633 \r
634     /// <summary>\r
635     /// Clones a command, including all its parameters\r
636     /// </summary>\r
637     /// <returns>A new SqliteCommand with the same commandtext, connection and parameters</returns>\r
638     public object Clone()\r
639     {\r
640       return new SqliteCommand(this);\r
641     }\r
642   }\r
643 }