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;
42 using System.Diagnostics;
43 using Group = System.Text.RegularExpressions.Group;
45 namespace Mono.Data.SqliteClient
47 public class SqliteCommand : IDbCommand
51 private SqliteConnection parent_conn;
52 //private SqliteTransaction transaction;
53 private IDbTransaction transaction;
56 private CommandType type;
57 private UpdateRowSource upd_row_source;
58 private SqliteParameterCollection sql_params;
59 private bool prepared = false;
61 static Regex v2Parameters = new Regex(@"(('[^']*?\:[^']*')*[^':]*?)*(?<param>:\w+)+([^':]*?('[^']*?\:[^']*'))*", RegexOptions.ExplicitCapture);
65 #region Constructors and destructors
67 public SqliteCommand ()
72 public SqliteCommand (string sqlText)
77 public SqliteCommand (string sqlText, SqliteConnection dbConn)
83 public SqliteCommand (string sqlText, SqliteConnection dbConn, IDbTransaction trans)
90 public void Dispose ()
98 public string CommandText
104 public int CommandTimeout
106 get { return timeout; }
107 set { timeout = value; }
110 public CommandType CommandType
113 set { type = value; }
116 IDbConnection IDbCommand.Connection
124 if (!(value is SqliteConnection))
126 throw new InvalidOperationException ("Can't set Connection to something other than a SqliteConnection");
128 parent_conn = (SqliteConnection) value;
132 public SqliteConnection Connection
134 get { return parent_conn; }
135 set { parent_conn = value; }
138 IDataParameterCollection IDbCommand.Parameters
140 get { return Parameters; }
143 public SqliteParameterCollection Parameters
147 if (sql_params == null) sql_params = new SqliteParameterCollection();
152 public IDbTransaction Transaction
154 get { return transaction; }
155 set { transaction = value; }
158 public UpdateRowSource UpdatedRowSource
160 get { return upd_row_source; }
161 set { upd_row_source = value; }
166 #region Internal Methods
168 internal int NumChanges ()
170 if (parent_conn.Version == 3)
171 return Sqlite.sqlite3_changes(parent_conn.Handle);
173 return Sqlite.sqlite_changes(parent_conn.Handle);
176 private string ReplaceParams(Match m)
178 string input = m.Value;
179 if (m.Groups["param"].Success)
181 Group g = m.Groups["param"];
182 string find = g.Value;
183 //FIXME: sqlite works internally only with strings, so this assumtion is mostly legit, but what about date formatting, etc?
184 //Need to fix SqlLiteDataReader first to acurately describe the tables
185 SqliteParameter sqlp = Parameters[find];
186 string replace = Convert.ToString(sqlp.Value);
187 if(sqlp.DbType == DbType.String)
189 replace = "\"" + replace + "\"";
192 input = Regex.Replace(input,find,replace);
199 private void BindParameters3 (IntPtr pStmt)
201 if (sql_params == null) return;
202 if (sql_params.Count == 0) return;
204 int pcount = Sqlite.sqlite3_bind_parameter_count (pStmt);
206 for (int i = 1; i <= pcount; i++)
208 String name = Sqlite.sqlite3_bind_parameter_name (pStmt, i);
209 SqliteParameter param = sql_params[name];
210 Type ptype = param.Value.GetType ();
214 if (ptype.Equals (typeof (String)))
216 String s = (String)param.Value;
217 err = Sqlite.sqlite3_bind_text16 (pStmt, i, s, s.Length, (IntPtr)(-1));
219 else if (ptype.Equals (typeof (DBNull)))
221 err = Sqlite.sqlite3_bind_null (pStmt, i);
223 else if (ptype.Equals (typeof (Boolean)))
225 bool b = (bool)param.Value;
226 err = Sqlite.sqlite3_bind_int (pStmt, i, b ? 1 : 0);
227 } else if (ptype.Equals (typeof (Byte)))
229 err = Sqlite.sqlite3_bind_int (pStmt, i, (Byte)param.Value);
231 else if (ptype.Equals (typeof (Char)))
233 err = Sqlite.sqlite3_bind_int (pStmt, i, (Char)param.Value);
235 else if (ptype.Equals (typeof (Int16)))
237 err = Sqlite.sqlite3_bind_int (pStmt, i, (Int16)param.Value);
239 else if (ptype.Equals (typeof (Int32)))
241 err = Sqlite.sqlite3_bind_int (pStmt, i, (Int32)param.Value);
243 else if (ptype.Equals (typeof (SByte)))
245 err = Sqlite.sqlite3_bind_int (pStmt, i, (SByte)param.Value);
247 else if (ptype.Equals (typeof (UInt16)))
249 err = Sqlite.sqlite3_bind_int (pStmt, i, (UInt16)param.Value);
251 else if (ptype.Equals (typeof (DateTime)))
253 DateTime dt = (DateTime)param.Value;
254 err = Sqlite.sqlite3_bind_int64 (pStmt, i, dt.ToFileTime ());
256 else if (ptype.Equals (typeof (Double)))
258 err = Sqlite.sqlite3_bind_double (pStmt, i, (Double)param.Value);
260 else if (ptype.Equals (typeof (Single)))
262 err = Sqlite.sqlite3_bind_double (pStmt, i, (Single)param.Value);
264 else if (ptype.Equals (typeof (UInt32)))
266 err = Sqlite.sqlite3_bind_int64 (pStmt, i, (UInt32)param.Value);
268 else if (ptype.Equals (typeof (Int64)))
270 err = Sqlite.sqlite3_bind_int64 (pStmt, i, (Int64)param.Value);
274 throw new ApplicationException("Unkown Parameter Type");
276 if (err != SqliteError.OK)
278 throw new ApplicationException ("Sqlite error in bind " + err);
283 private void GetNextStatement (IntPtr pzStart, out IntPtr pzTail, out IntPtr pStmt)
285 if (parent_conn.Version == 3)
287 SqliteError err = Sqlite.sqlite3_prepare16 (parent_conn.Handle, pzStart, -1, out pStmt, out pzTail);
288 if (err != SqliteError.OK)
289 throw new SqliteSyntaxException (GetError3());
294 SqliteError err = Sqlite.sqlite_compile (parent_conn.Handle, pzStart, out pzTail, out pStmt, out errMsg);
296 if (err != SqliteError.OK)
298 string msg = "unknown error";
299 if (errMsg != IntPtr.Zero)
301 msg = Marshal.PtrToStringAnsi (errMsg);
302 Sqlite.sqliteFree (errMsg);
304 throw new SqliteSyntaxException (msg);
309 // Executes a statement and ignores its result.
310 private void ExecuteStatement (IntPtr pStmt) {
312 IntPtr pazValue, pazColName;
313 ExecuteStatement (pStmt, out cols, out pazValue, out pazColName);
316 // Executes a statement and returns whether there is more data available.
317 internal bool ExecuteStatement (IntPtr pStmt, out int cols, out IntPtr pazValue, out IntPtr pazColName) {
320 if (parent_conn.Version == 3)
322 err = Sqlite.sqlite3_step (pStmt);
323 if (err == SqliteError.ERROR)
324 throw new SqliteExecutionException (GetError3());
325 pazValue = IntPtr.Zero; pazColName = IntPtr.Zero; // not used for v=3
326 cols = Sqlite.sqlite3_column_count (pStmt);
330 err = Sqlite.sqlite_step (pStmt, out cols, out pazValue, out pazColName);
331 if (err == SqliteError.ERROR)
332 throw new SqliteExecutionException ();
335 if (err == SqliteError.BUSY)
336 throw new SqliteBusyException();
338 if (err == SqliteError.MISUSE)
339 throw new SqliteExecutionException();
341 // err is either ROW or DONE.
342 return err == SqliteError.ROW;
347 #region Public Methods
349 public void Cancel ()
353 public string ProcessParameters()
355 string processedText = sql;
357 //Regex looks odd perhaps, but it works - same impl. as in the firebird db provider
358 //the named parameters are using the ADO.NET standard @-prefix but sqlite is considering ":" as a prefix for v.3...
359 //ref: http://www.mail-archive.com/sqlite-users@sqlite.org/msg01851.html
360 //Regex r = new Regex(@"(('[^']*?\@[^']*')*[^'@]*?)*(?<param>@\w+)+([^'@]*?('[^']*?\@[^']*'))*",RegexOptions.ExplicitCapture);
362 //The above statement is true for the commented regEx, but I changed it to use the :-prefix, because now (12.05.2005 sqlite3)
363 //sqlite is using : as Standard Parameterprefix
365 MatchEvaluator me = new MatchEvaluator(ReplaceParams);
366 processedText = v2Parameters.Replace(sql, me);
367 return processedText;
370 public void Prepare ()
372 // There isn't much we can do here. If a table schema
373 // changes after preparing a statement, Sqlite bails,
374 // so we can only compile statements right before we
377 if (prepared) return;
379 if (Parameters.Count > 0 && parent_conn.Version == 2)
381 sql = ProcessParameters();
387 IDbDataParameter IDbCommand.CreateParameter()
389 return CreateParameter ();
392 public SqliteParameter CreateParameter ()
394 return new SqliteParameter ();
397 public int ExecuteNonQuery ()
400 ExecuteReader (CommandBehavior.Default, false, out rows_affected);
401 return rows_affected;
404 public object ExecuteScalar ()
406 SqliteDataReader r = ExecuteReader ();
407 if (r == null || !r.Read ()) {
415 IDataReader IDbCommand.ExecuteReader ()
417 return ExecuteReader ();
420 IDataReader IDbCommand.ExecuteReader (CommandBehavior behavior)
422 return ExecuteReader (behavior);
425 public SqliteDataReader ExecuteReader ()
427 return ExecuteReader (CommandBehavior.Default);
430 public SqliteDataReader ExecuteReader (CommandBehavior behavior)
433 return ExecuteReader (behavior, true, out r);
436 public SqliteDataReader ExecuteReader (CommandBehavior behavior, bool want_results, out int rows_affected)
438 // The SQL string may contain multiple sql commands, so the main
439 // thing to do is have Sqlite iterate through the commands.
440 // If want_results, only the last command is returned as a
441 // DataReader. Otherwise, no command is returned as a
444 IntPtr psql; // pointer to SQL command
446 // Sqlite 2 docs say this: By default, SQLite assumes that all data uses a fixed-size 8-bit
447 // character (iso8859). But if you give the --enable-utf8 option to the configure script, then the
448 // library assumes UTF-8 variable sized characters. This makes a difference for the LIKE and GLOB
449 // operators and the LENGTH() and SUBSTR() functions. The static string sqlite_encoding will be set
450 // to either "UTF-8" or "iso8859" to indicate how the library was compiled. In addition, the sqlite.h
451 // header file will define one of the macros SQLITE_UTF8 or SQLITE_ISO8859, as appropriate.
453 // We have no way of knowing whether Sqlite 2 expects ISO8859 or UTF-8, but ISO8859 seems to be the
454 // default. Therefore, we need to use an ISO8859(-1) compatible encoding, like ANSI.
455 // OTOH, the user may want to specify the encoding of the bytes stored in the database, regardless
456 // of what Sqlite is treating them as,
458 // For Sqlite 3, we use the UTF-16 prepare function, so we need a UTF-16 string.
460 if (parent_conn.Version == 2)
461 psql = Sqlite.StringToHeap (sql, parent_conn.Encoding);
463 psql = Marshal.StringToCoTaskMemUni (sql);
465 IntPtr pzTail = psql;
468 parent_conn.StartExec ();
476 GetNextStatement(pzTail, out pzTail, out pStmt);
478 if (pStmt == IntPtr.Zero)
479 throw new Exception();
481 // pzTail is positioned after the last byte in the
482 // statement, which will be the NULL character if
483 // this was the last statement.
484 bool last = Marshal.ReadByte(pzTail) == 0;
487 if (parent_conn.Version == 3)
488 BindParameters3 (pStmt);
490 if (last && want_results)
491 return new SqliteDataReader (this, pStmt, parent_conn.Version);
493 ExecuteStatement(pStmt);
495 if (last) // rows_affected is only used if !want_results
496 rows_affected = NumChanges ();
499 if (parent_conn.Version == 3)
500 Sqlite.sqlite3_finalize (pStmt);
502 Sqlite.sqlite_finalize (pStmt, out errMsgPtr);
510 parent_conn.EndExec ();
511 Marshal.FreeCoTaskMem (psql);
515 public int LastInsertRowID ()
517 if (parent_conn.Version == 3)
518 return Sqlite.sqlite3_last_insert_rowid(parent_conn.Handle);
520 return Sqlite.sqlite_last_insert_rowid(parent_conn.Handle);
523 private string GetError3() {
524 return Marshal.PtrToStringUni (Sqlite.sqlite3_errmsg16 (parent_conn.Handle));