2 // Mono.Data.SqliteClient.SQLiteCommand.cs
5 // Robert Simpson (robert@blackcastlesoft.com)
7 // Adapted and modified for the Mono Project by
8 // Marek Habersack (grendello@gmail.com)
11 // Copyright (C) 2006 Novell, Inc (http://www.novell.com)
12 // Copyright (C) 2007 Marek Habersack
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:
22 // The above copyright notice and this permission notice shall be
23 // included in all copies or substantial portions of the Software.
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.
34 /********************************************************
35 * ADO.NET 2.0 Data Provider for SQLite Version 3.X
36 * Written by Robert Simpson (robert@blackcastlesoft.com)
38 * Released to the public domain, use at your own risk!
39 ********************************************************/
41 namespace Mono.Data.SqliteClient
45 using System.Data.Common;
46 using System.Collections.Generic;
47 using System.ComponentModel;
50 /// Sqlite implementation of DbCommand.
52 #if !PLATFORM_COMPACTFRAMEWORK
53 [Designer("Sqlite.Designer.SqliteCommandDesigner, Sqlite.Designer, Version=1.0.31.0, Culture=neutral, PublicKeyToken=db937bc2d44ff139"), ToolboxItem(true)]
55 public class SqliteCommand : DbCommand, ICloneable
58 /// The command text this command is based on
60 private string _commandText;
62 /// The connection the command is associated with
64 private SqliteConnection _cnn;
66 /// Indicates whether or not a DataReader is active on the command.
68 private SqliteDataReader _activeReader;
70 /// The timeout for the command, kludged because Sqlite doesn't support per-command timeout values
72 internal int _commandTimeout;
76 private bool _designTimeVisible;
78 /// Used by DbDataAdapter to determine updating behavior
80 private UpdateRowSource _updateRowSource;
82 /// The collection of parameters for the command
84 private SqliteParameterCollection _parameterCollection;
86 /// The SQL command text, broken into individual SQL statements as they are executed
88 internal List<SqliteStatement> _statementList;
90 /// Unprocessed SQL text that has not been executed
92 internal string _remainingText;
94 /// Transaction associated with this command
96 private SqliteTransaction _transaction;
99 /// Constructs a new SqliteCommand
102 /// Default constructor
104 public SqliteCommand() :this(null, null)
109 /// Initializes the command with the given command text
111 /// <param name="commandText">The SQL command text</param>
112 public SqliteCommand(string commandText)
113 : this(commandText, null, null)
118 /// Initializes the command with the given SQL command text and attach the command to the specified
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)
129 /// Initializes the command and associates it with the specified connection.
131 /// <param name="connection">The connection to associate with the command</param>
132 public SqliteCommand(SqliteConnection connection)
133 : this(null, connection, null)
137 private SqliteCommand(SqliteCommand source) : this(source.CommandText, source.Connection, source.Transaction)
139 CommandTimeout = source.CommandTimeout;
140 DesignTimeVisible = source.DesignTimeVisible;
141 UpdatedRowSource = source.UpdatedRowSource;
143 foreach (SqliteParameter param in source._parameterCollection)
145 Parameters.Add(param.Clone());
150 /// Initializes a command with the given SQL, connection and transaction
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)
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;
165 if (commandText != null)
166 CommandText = commandText;
168 if (connection != null)
169 DbConnection = connection;
171 if (transaction != null)
172 Transaction = transaction;
176 /// Disposes of the command and clears all member variables
178 /// <param name="disposing">Whether or not the class is being explicitly or implicitly disposed</param>
179 protected override void Dispose(bool disposing)
181 base.Dispose(disposing);
183 // If a reader is active on this command, don't destroy it completely
184 if (_activeReader != null)
186 _activeReader._disposeCommand = true;
191 _parameterCollection.Clear();
196 /// Clears and destroys all statements currently prepared
198 internal void ClearCommands()
200 if (_activeReader != null)
201 _activeReader.Close();
203 if (_statementList == null) return;
205 int x = _statementList.Count;
206 for (int n = 0; n < x; n++)
207 _statementList[n].Dispose();
209 _statementList = null;
211 _parameterCollection.Unbind();
215 /// Builds an array of prepared statements for each complete SQL statement in the command text
217 internal SqliteStatement BuildNextCommand()
219 SqliteStatement stmt = null;
223 if (_statementList == null)
224 _remainingText = _commandText;
226 stmt = _cnn._sql.Prepare(_remainingText, (_statementList == null) ? null : _statementList[_statementList.Count - 1], out _remainingText);
229 stmt._command = this;
230 if (_statementList == null)
231 _statementList = new List<SqliteStatement>();
233 _statementList.Add(stmt);
235 _parameterCollection.MapParameters(stmt);
236 stmt.BindParameters();
244 if (_statementList.Contains(stmt))
245 _statementList.Remove(stmt);
250 // If we threw an error compiling the statement, we cannot continue on so set the remaining text to null.
251 _remainingText = null;
257 internal SqliteStatement GetStatement(int index)
259 // Haven't built any statements yet
260 if (_statementList == null) return BuildNextCommand();
262 // If we're at the last built statement and want the next unbuilt statement, then build it
263 if (index == _statementList.Count)
265 if (String.IsNullOrEmpty(_remainingText) == false) return BuildNextCommand();
266 else return null; // No more commands
269 SqliteStatement stmt = _statementList[index];
270 stmt.BindParameters();
278 public override void Cancel()
283 /// The SQL command text associated with the command
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")]
288 public override string CommandText
296 if (_commandText == value) return;
298 if (_activeReader != null)
300 throw new InvalidOperationException("Cannot set CommandText while a DataReader is active");
304 _commandText = value;
306 if (_cnn == null) return;
311 /// The amount of time to wait for the connection to become available before erroring out
313 #if !PLATFORM_COMPACTFRAMEWORK
314 [DefaultValue((int)30)]
316 public override int CommandTimeout
320 return _commandTimeout;
324 _commandTimeout = value;
329 /// The type of the command. Sqlite only supports CommandType.Text
331 #if !PLATFORM_COMPACTFRAMEWORK
332 [RefreshProperties(RefreshProperties.All), DefaultValue(CommandType.Text)]
334 public override CommandType CommandType
338 return CommandType.Text;
342 if (value != CommandType.Text)
344 throw new NotSupportedException();
350 /// Forwards to the local CreateParameter() function
352 /// <returns></returns>
353 protected override DbParameter CreateDbParameter()
355 return CreateParameter();
359 /// Create a new parameter
361 /// <returns></returns>
362 public new SqliteParameter CreateParameter()
364 return new SqliteParameter();
368 /// The connection associated with this command
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")]
373 public new SqliteConnection Connection
378 if (_activeReader != null)
379 throw new InvalidOperationException("Cannot set Connection while a DataReader is active");
384 _cnn.RemoveCommand(this);
390 _cnn.AddCommand(this);
395 /// Forwards to the local Connection property
397 protected override DbConnection DbConnection
405 Connection = (SqliteConnection)value;
410 /// Returns the SqliteParameterCollection for the given command
412 #if !PLATFORM_COMPACTFRAMEWORK
413 [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
415 public new SqliteParameterCollection Parameters
417 get { return _parameterCollection; }
421 /// Forwards to the local Parameters property
423 protected override DbParameterCollection DbParameterCollection
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.
435 #if !PLATFORM_COMPACTFRAMEWORK
436 [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
438 public new SqliteTransaction Transaction
440 get { return _transaction; }
445 if (_activeReader != null)
446 throw new InvalidOperationException("Cannot set Transaction while a DataReader is active");
450 if (value._cnn != _cnn)
451 throw new ArgumentException("Transaction is not associated with the command's connection");
453 _transaction = value;
455 else if (value != null)
456 throw new ArgumentOutOfRangeException("SqliteTransaction", "Not associated with a connection");
461 /// Forwards to the local Transaction property
463 protected override DbTransaction DbTransaction
471 Transaction = (SqliteTransaction)value;
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.
480 private void InitializeForReader()
482 if (_activeReader != null)
483 throw new InvalidOperationException("DataReader already active on this command");
486 throw new InvalidOperationException("No connection associated with this command");
488 if (_cnn.State != ConnectionState.Open)
489 throw new InvalidOperationException("Database is not open");
491 // Map all parameters for statements already built
492 _parameterCollection.MapParameters(null);
494 // Set the default command timeout
495 _cnn._sql.SetTimeout(_commandTimeout * 1000);
499 /// Creates a new SqliteDataReader to execute/iterate the array of Sqlite prepared statements
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)
505 return ExecuteReader(behavior);
509 /// Overrides the default behavior to return a SqliteDataReader specialization class
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)
515 InitializeForReader();
517 SqliteDataReader rd = new SqliteDataReader(this, behavior);
524 /// Overrides the default behavior of DbDataReader to return a specialized SqliteDataReader class
526 /// <returns>A SqliteDataReader</returns>
527 public new SqliteDataReader ExecuteReader()
529 return ExecuteReader(CommandBehavior.Default);
533 /// Called by the SqliteDataReader when the data reader is closed.
535 internal void ClearDataReader()
537 _activeReader = null;
541 /// Execute the command and return the number of rows inserted/updated affected by it.
543 /// <returns></returns>
544 public override int ExecuteNonQuery()
546 InitializeForReader();
550 SqliteStatement stmt;
554 stmt = GetStatement(x);
556 if (stmt == null) break;
558 _cnn._sql.Step(stmt);
559 nAffected += _cnn._sql.Changes;
560 _cnn._sql.Reset(stmt);
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.
570 /// <returns>The first column of the first row of the first resultset from the query</returns>
571 public override object ExecuteScalar()
573 InitializeForReader();
577 SqliteType typ = new SqliteType();
578 SqliteStatement stmt;
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.
584 stmt = GetStatement(x);
586 if (stmt == null) break;
588 if (_cnn._sql.Step(stmt) == true && ret == null)
590 ret = _cnn._sql.GetValue(stmt, 0, ref typ);
592 _cnn._sql.Reset(stmt);
599 /// Does nothing. Commands are prepared as they are executed the first time, and kept in prepared state afterwards.
601 public override void Prepare()
606 /// Sets the method the SqliteCommandBuilder uses to determine how to update inserted or updated rows in a DataTable.
608 [DefaultValue(UpdateRowSource.FirstReturnedRecord)]
609 public override UpdateRowSource UpdatedRowSource
613 return _updateRowSource;
617 _updateRowSource = value;
622 /// Determines if the command is visible at design time. Defaults to True.
624 #if !PLATFORM_COMPACTFRAMEWORK
625 [DesignOnly(true), Browsable(false), DefaultValue(true), EditorBrowsable(EditorBrowsableState.Never)]
627 public override bool DesignTimeVisible
631 return _designTimeVisible;
635 _designTimeVisible = value;
636 #if !PLATFORM_COMPACTFRAMEWORK
637 TypeDescriptor.Refresh(this);
643 /// Clones a command, including all its parameters
645 /// <returns>A new SqliteCommand with the same commandtext, connection and parameters</returns>
646 public object Clone()
648 return new SqliteCommand(this);
651 #if MONO_BACKWARD_COMPAT
652 public int LastInsertRowID ()
654 return _cnn.LastInsertRowId;