2 // Mono.Data.SqliteClient.SqliteCommand.cs
4 // Represents a Transact-SQL statement or stored procedure to execute against
5 // a Sqlite database file.
7 // Author(s): Vladimir Vukicevic <vladimir@pobox.com>
8 // Everaldo Canuto <everaldo_canuto@yahoo.com.br>
9 // Chris Turchin <chris@turchin.net>
10 // Jeroen Zwartepoorte <jeroen@xs4all.nl>
11 // Thomas Zoechling <thomas.zoechling@gmx.at>
12 // Joshua Tauberer <tauberer@for.net>
14 // Copyright (C) 2002 Vladimir Vukicevic
16 // Permission is hereby granted, free of charge, to any person obtaining
17 // a copy of this software and associated documentation files (the
18 // "Software"), to deal in the Software without restriction, including
19 // without limitation the rights to use, copy, modify, merge, publish,
20 // distribute, sublicense, and/or sell copies of the Software, and to
21 // permit persons to whom the Software is furnished to do so, subject to
22 // the following conditions:
24 // The above copyright notice and this permission notice shall be
25 // included in all copies or substantial portions of the Software.
27 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
28 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
29 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
30 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
31 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
32 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
33 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
37 using System.Collections;
39 using System.Runtime.InteropServices;
40 using System.Text.RegularExpressions;
43 using System.Data.Common;
45 using System.Diagnostics;
46 using Group = System.Text.RegularExpressions.Group;
48 namespace Mono.Data.SqliteClient
51 public class SqliteCommand : DbCommand, ICloneable
53 public class SqliteCommand : IDbCommand, ICloneable
58 private SqliteConnection parent_conn;
59 //private SqliteTransaction transaction;
60 private IDbTransaction transaction;
63 private CommandType type;
64 private UpdateRowSource upd_row_source;
65 private SqliteParameterCollection sql_params;
66 private bool prepared = false;
67 private bool _designTimeVisible = true;
71 #region Constructors and destructors
73 public SqliteCommand ()
78 public SqliteCommand (string sqlText)
83 public SqliteCommand (string sqlText, SqliteConnection dbConn)
89 public SqliteCommand (string sqlText, SqliteConnection dbConn, IDbTransaction trans)
97 public void Dispose ()
109 public string CommandText
112 set { sql = value; prepared = false; }
118 public int CommandTimeout
120 get { return timeout; }
121 set { timeout = value; }
127 public CommandType CommandType
130 set { type = value; }
134 protected override DbConnection DbConnection
136 get { return parent_conn; }
137 set { parent_conn = (SqliteConnection)value; }
140 public SqliteConnection Connection
142 get { return parent_conn; }
143 set { parent_conn = (SqliteConnection)value; }
147 IDbConnection IDbCommand.Connection
155 if (!(value is SqliteConnection))
157 throw new InvalidOperationException ("Can't set Connection to something other than a SqliteConnection");
159 parent_conn = (SqliteConnection) value;
168 SqliteParameterCollection Parameters
173 if (sql_params == null)
174 sql_params = new SqliteParameterCollection();
180 protected override DbParameterCollection DbParameterCollection
182 get { return (DbParameterCollection) Parameters; }
187 IDataParameterCollection IDbCommand.Parameters
189 get { return Parameters; }
195 protected override DbTransaction DbTransaction
197 public IDbTransaction Transaction
202 return (DbTransaction)transaction;
207 set { transaction = value; }
211 public override bool DesignTimeVisible
213 get { return _designTimeVisible; }
214 set { _designTimeVisible = value; }
221 public UpdateRowSource UpdatedRowSource
223 get { return upd_row_source; }
224 set { upd_row_source = value; }
229 #region Internal Methods
231 internal int NumChanges ()
233 if (parent_conn.Version == 3)
234 return Sqlite.sqlite3_changes(parent_conn.Handle);
236 return Sqlite.sqlite_changes(parent_conn.Handle);
239 private void BindParameters3 (IntPtr pStmt)
241 if (sql_params == null) return;
242 if (sql_params.Count == 0) return;
244 int pcount = Sqlite.sqlite3_bind_parameter_count (pStmt);
246 for (int i = 1; i <= pcount; i++)
248 String name = Sqlite.HeapToString (Sqlite.sqlite3_bind_parameter_name (pStmt, i), Encoding.UTF8);
250 SqliteParameter param = null;
252 param = sql_params[name] as SqliteParameter;
254 param = sql_params[i-1] as SqliteParameter;
256 if (param.Value == null) {
257 Sqlite.sqlite3_bind_null (pStmt, i);
261 Type ptype = param.Value.GetType ();
263 ptype = Enum.GetUnderlyingType (ptype);
267 if (ptype.Equals (typeof (String)))
269 String s = (String)param.Value;
270 err = Sqlite.sqlite3_bind_text16 (pStmt, i, s, -1, (IntPtr)(-1));
272 else if (ptype.Equals (typeof (DBNull)))
274 err = Sqlite.sqlite3_bind_null (pStmt, i);
276 else if (ptype.Equals (typeof (Boolean)))
278 bool b = (bool)param.Value;
279 err = Sqlite.sqlite3_bind_int (pStmt, i, b ? 1 : 0);
280 } else if (ptype.Equals (typeof (Byte)))
282 err = Sqlite.sqlite3_bind_int (pStmt, i, (Byte)param.Value);
284 else if (ptype.Equals (typeof (Char)))
286 err = Sqlite.sqlite3_bind_int (pStmt, i, (Char)param.Value);
288 else if (ptype.IsEnum)
290 err = Sqlite.sqlite3_bind_int (pStmt, i, (Int32)param.Value);
292 else if (ptype.Equals (typeof (Int16)))
294 err = Sqlite.sqlite3_bind_int (pStmt, i, (Int16)param.Value);
296 else if (ptype.Equals (typeof (Int32)))
298 err = Sqlite.sqlite3_bind_int (pStmt, i, (Int32)param.Value);
300 else if (ptype.Equals (typeof (SByte)))
302 err = Sqlite.sqlite3_bind_int (pStmt, i, (SByte)param.Value);
304 else if (ptype.Equals (typeof (UInt16)))
306 err = Sqlite.sqlite3_bind_int (pStmt, i, (UInt16)param.Value);
308 else if (ptype.Equals (typeof (DateTime)))
310 DateTime dt = (DateTime)param.Value;
311 err = Sqlite.sqlite3_bind_int64 (pStmt, i, dt.ToFileTime ());
313 else if (ptype.Equals (typeof (Double)))
315 err = Sqlite.sqlite3_bind_double (pStmt, i, (Double)param.Value);
317 else if (ptype.Equals (typeof (Single)))
319 err = Sqlite.sqlite3_bind_double (pStmt, i, (Single)param.Value);
321 else if (ptype.Equals (typeof (UInt32)))
323 err = Sqlite.sqlite3_bind_int64 (pStmt, i, (UInt32)param.Value);
325 else if (ptype.Equals (typeof (Int64)))
327 err = Sqlite.sqlite3_bind_int64 (pStmt, i, (Int64)param.Value);
329 else if (ptype.Equals (typeof (Byte[])))
331 err = Sqlite.sqlite3_bind_blob (pStmt, i, (Byte[])param.Value, ((Byte[])param.Value).Length, (IntPtr)(-1));
335 throw new ApplicationException("Unkown Parameter Type");
337 if (err != SqliteError.OK)
339 throw new ApplicationException ("Sqlite error in bind " + err);
344 private void GetNextStatement (IntPtr pzStart, out IntPtr pzTail, out IntPtr pStmt)
346 if (parent_conn.Version == 3)
348 SqliteError err = Sqlite.sqlite3_prepare16 (parent_conn.Handle, pzStart, -1, out pStmt, out pzTail);
349 if (err != SqliteError.OK)
350 throw new SqliteSyntaxException (GetError3());
355 SqliteError err = Sqlite.sqlite_compile (parent_conn.Handle, pzStart, out pzTail, out pStmt, out errMsg);
357 if (err != SqliteError.OK)
359 string msg = "unknown error";
360 if (errMsg != IntPtr.Zero)
362 msg = Marshal.PtrToStringAnsi (errMsg);
363 Sqlite.sqliteFree (errMsg);
365 throw new SqliteSyntaxException (msg);
370 // Executes a statement and ignores its result.
371 private void ExecuteStatement (IntPtr pStmt) {
373 IntPtr pazValue, pazColName;
374 ExecuteStatement (pStmt, out cols, out pazValue, out pazColName);
377 // Executes a statement and returns whether there is more data available.
378 internal bool ExecuteStatement (IntPtr pStmt, out int cols, out IntPtr pazValue, out IntPtr pazColName) {
381 if (parent_conn.Version == 3)
383 err = Sqlite.sqlite3_step (pStmt);
384 if (err == SqliteError.ERROR)
385 throw new SqliteExecutionException (GetError3());
386 pazValue = IntPtr.Zero; pazColName = IntPtr.Zero; // not used for v=3
387 cols = Sqlite.sqlite3_column_count (pStmt);
391 err = Sqlite.sqlite_step (pStmt, out cols, out pazValue, out pazColName);
392 if (err == SqliteError.ERROR)
393 throw new SqliteExecutionException ();
396 if (err == SqliteError.BUSY)
397 throw new SqliteBusyException();
399 if (err == SqliteError.MISUSE)
400 throw new SqliteExecutionException();
402 // err is either ROW or DONE.
403 return err == SqliteError.ROW;
408 #region Public Methods
410 object ICloneable.Clone ()
412 return new SqliteCommand (sql, parent_conn, transaction);
417 public void Cancel ()
421 public string BindParameters2()
425 // There used to be a crazy regular expression here, but it caused Mono
426 // to go into an infinite loop of some sort when there were no parameters
427 // in the SQL string. That was too complicated anyway.
429 // Here we search for substrings of the form [:?]wwwww where w is a letter or digit
430 // (not sure what a legitimate Sqlite3 identifier is), except those within quotes.
432 char inquote = (char)0;
434 for (int i = 0; i < text.Length; i++) {
438 } else if (inquote == (char)0 && (c == '\'' || c == '"')) {
440 } else if (inquote == (char)0 && (c == ':' || c == '?')) {
442 while (++i < text.Length && char.IsLetterOrDigit(text[i])) { } // scan to end
443 string name = text.Substring(start, i-start);
446 p = Parameters[name] as SqliteParameter;
448 p = Parameters[counter] as SqliteParameter;
449 string value = "'" + Convert.ToString(p.Value).Replace("'", "''") + "'";
450 text = text.Remove(start, name.Length).Insert(start, value);
451 i += value.Length - name.Length - 1;
462 public void Prepare ()
464 // There isn't much we can do here. If a table schema
465 // changes after preparing a statement, Sqlite bails,
466 // so we can only compile statements right before we
469 if (prepared) return;
471 if (Parameters.Count > 0 && parent_conn.Version == 2)
473 sql = BindParameters2();
480 IDbDataParameter IDbCommand.CreateParameter()
482 return CreateParameter ();
487 protected override DbParameter CreateDbParameter ()
489 public SqliteParameter CreateParameter ()
492 return new SqliteParameter ();
498 public int ExecuteNonQuery ()
501 ExecuteReader (CommandBehavior.Default, false, out rows_affected);
502 return rows_affected;
508 public object ExecuteScalar ()
510 SqliteDataReader r = (SqliteDataReader)ExecuteReader ();
511 if (r == null || !r.Read ()) {
520 IDataReader IDbCommand.ExecuteReader ()
522 return ExecuteReader ();
525 IDataReader IDbCommand.ExecuteReader (CommandBehavior behavior)
527 return ExecuteReader (behavior);
530 public SqliteDataReader ExecuteReader ()
532 return ExecuteReader (CommandBehavior.Default);
536 public new SqliteDataReader ExecuteReader (CommandBehavior behavior)
539 return ExecuteReader (behavior, true, out r);
543 public new SqliteDataReader ExecuteReader ()
545 return ExecuteReader (CommandBehavior.Default);
548 protected override DbDataReader ExecuteDbDataReader (CommandBehavior behavior)
550 return (DbDataReader) ExecuteReader (behavior);
554 public SqliteDataReader ExecuteReader (CommandBehavior behavior, bool want_results, out int rows_affected)
558 // The SQL string may contain multiple sql commands, so the main
559 // thing to do is have Sqlite iterate through the commands.
560 // If want_results, only the last command is returned as a
561 // DataReader. Otherwise, no command is returned as a
564 IntPtr psql; // pointer to SQL command
566 // Sqlite 2 docs say this: By default, SQLite assumes that all data uses a fixed-size 8-bit
567 // character (iso8859). But if you give the --enable-utf8 option to the configure script, then the
568 // library assumes UTF-8 variable sized characters. This makes a difference for the LIKE and GLOB
569 // operators and the LENGTH() and SUBSTR() functions. The static string sqlite_encoding will be set
570 // to either "UTF-8" or "iso8859" to indicate how the library was compiled. In addition, the sqlite.h
571 // header file will define one of the macros SQLITE_UTF8 or SQLITE_ISO8859, as appropriate.
573 // We have no way of knowing whether Sqlite 2 expects ISO8859 or UTF-8, but ISO8859 seems to be the
574 // default. Therefore, we need to use an ISO8859(-1) compatible encoding, like ANSI.
575 // OTOH, the user may want to specify the encoding of the bytes stored in the database, regardless
576 // of what Sqlite is treating them as,
578 // For Sqlite 3, we use the UTF-16 prepare function, so we need a UTF-16 string.
580 if (parent_conn.Version == 2)
581 psql = Sqlite.StringToHeap (sql.Trim(), parent_conn.Encoding);
583 psql = Marshal.StringToHGlobalUni (sql.Trim());
585 IntPtr pzTail = psql;
588 parent_conn.StartExec ();
596 GetNextStatement(pzTail, out pzTail, out pStmt);
598 if (pStmt == IntPtr.Zero)
599 throw new Exception();
601 // pzTail is positioned after the last byte in the
602 // statement, which will be the NULL character if
603 // this was the last statement.
604 bool last = Marshal.ReadByte(pzTail) == 0;
607 if (parent_conn.Version == 3)
608 BindParameters3 (pStmt);
610 if (last && want_results)
611 return new SqliteDataReader (this, pStmt, parent_conn.Version);
613 ExecuteStatement(pStmt);
615 if (last) // rows_affected is only used if !want_results
616 rows_affected = NumChanges ();
619 if (parent_conn.Version == 3)
620 Sqlite.sqlite3_finalize (pStmt);
622 Sqlite.sqlite_finalize (pStmt, out errMsgPtr);
630 parent_conn.EndExec ();
631 Marshal.FreeHGlobal (psql);
635 public int LastInsertRowID ()
637 return parent_conn.LastInsertRowId;
640 private string GetError3() {
641 return Marshal.PtrToStringUni (Sqlite.sqlite3_errmsg16 (parent_conn.Handle));