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