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;
40 using System.Runtime.InteropServices;
41 using System.Text.RegularExpressions;
43 using System.Diagnostics;
44 using Group = System.Text.RegularExpressions.Group;
46 namespace Mono.Data.SqliteClient
48 public class SqliteCommand : IDbCommand
52 private SqliteConnection parent_conn;
53 //private SqliteTransaction transaction;
54 private IDbTransaction transaction;
57 private CommandType type;
58 private UpdateRowSource upd_row_source;
59 private SqliteParameterCollection sql_params;
60 private bool prepared = false;
62 static Regex v2Parameters = new Regex(@"(('[^']*?\:[^']*')*[^':]*?)*(?<param>:\w+)+([^':]*?('[^']*?\:[^']*'))*", RegexOptions.ExplicitCapture);
66 #region Constructors and destructors
68 public SqliteCommand ()
73 public SqliteCommand (string sqlText)
78 public SqliteCommand (string sqlText, SqliteConnection dbConn)
84 public SqliteCommand (string sqlText, SqliteConnection dbConn, IDbTransaction trans)
91 public void Dispose ()
99 public string CommandText
105 public int CommandTimeout
107 get { return timeout; }
108 set { timeout = value; }
111 public CommandType CommandType
114 set { type = value; }
117 IDbConnection IDbCommand.Connection
125 if (!(value is SqliteConnection))
127 throw new InvalidOperationException ("Can't set Connection to something other than a SqliteConnection");
129 parent_conn = (SqliteConnection) value;
133 public SqliteConnection Connection
135 get { return parent_conn; }
136 set { parent_conn = value; }
139 IDataParameterCollection IDbCommand.Parameters
141 get { return Parameters; }
144 public SqliteParameterCollection Parameters
148 if (sql_params == null) sql_params = new SqliteParameterCollection();
153 public IDbTransaction Transaction
155 get { return transaction; }
156 set { transaction = value; }
159 public UpdateRowSource UpdatedRowSource
161 get { return upd_row_source; }
162 set { upd_row_source = value; }
167 #region Internal Methods
169 internal int NumChanges ()
171 if (parent_conn.Version == 3)
172 return Sqlite.sqlite3_changes(parent_conn.Handle);
174 return Sqlite.sqlite_changes(parent_conn.Handle);
177 private string ReplaceParams(Match m)
179 string input = m.Value;
180 if (m.Groups["param"].Success)
182 Group g = m.Groups["param"];
183 string find = g.Value;
184 //FIXME: sqlite works internally only with strings, so this assumtion is mostly legit, but what about date formatting, etc?
185 //Need to fix SqlLiteDataReader first to acurately describe the tables
186 SqliteParameter sqlp = Parameters[find];
187 string replace = Convert.ToString(sqlp.Value);
188 if(sqlp.DbType == DbType.String)
190 replace = "\"" + replace + "\"";
193 input = Regex.Replace(input,find,replace);
200 private void BindParameters3 (IntPtr pStmt)
202 if (sql_params == null) return;
203 if (sql_params.Count == 0) return;
205 int pcount = Sqlite.sqlite3_bind_parameter_count (pStmt);
207 for (int i = 1; i <= pcount; i++)
209 String name = Sqlite.sqlite3_bind_parameter_name (pStmt, i);
210 SqliteParameter param = sql_params[name];
211 Type ptype = param.Value.GetType ();
215 if (ptype.Equals (typeof (String)))
217 String s = (String)param.Value;
218 err = Sqlite.sqlite3_bind_text (pStmt, i, s, s.Length, (IntPtr)(-1));
220 else if (ptype.Equals (typeof (DBNull)))
222 err = Sqlite.sqlite3_bind_null (pStmt, i);
224 else if (ptype.Equals (typeof (Boolean)))
226 bool b = (bool)param.Value;
227 err = Sqlite.sqlite3_bind_int (pStmt, i, b ? 1 : 0);
228 } else if (ptype.Equals (typeof (Byte)))
230 err = Sqlite.sqlite3_bind_int (pStmt, i, (Byte)param.Value);
232 else if (ptype.Equals (typeof (Char)))
234 err = Sqlite.sqlite3_bind_int (pStmt, i, (Char)param.Value);
236 else if (ptype.Equals (typeof (Int16)))
238 err = Sqlite.sqlite3_bind_int (pStmt, i, (Int16)param.Value);
240 else if (ptype.Equals (typeof (Int32)))
242 err = Sqlite.sqlite3_bind_int (pStmt, i, (Int32)param.Value);
244 else if (ptype.Equals (typeof (SByte)))
246 err = Sqlite.sqlite3_bind_int (pStmt, i, (SByte)param.Value);
248 else if (ptype.Equals (typeof (UInt16)))
250 err = Sqlite.sqlite3_bind_int (pStmt, i, (UInt16)param.Value);
252 else if (ptype.Equals (typeof (DateTime)))
254 DateTime dt = (DateTime)param.Value;
255 err = Sqlite.sqlite3_bind_int64 (pStmt, i, dt.ToFileTime ());
257 else if (ptype.Equals (typeof (Double)))
259 err = Sqlite.sqlite3_bind_double (pStmt, i, (Double)param.Value);
261 else if (ptype.Equals (typeof (Single)))
263 err = Sqlite.sqlite3_bind_double (pStmt, i, (Single)param.Value);
265 else if (ptype.Equals (typeof (UInt32)))
267 err = Sqlite.sqlite3_bind_int64 (pStmt, i, (UInt32)param.Value);
269 else if (ptype.Equals (typeof (Int64)))
271 err = Sqlite.sqlite3_bind_int64 (pStmt, i, (Int64)param.Value);
275 throw new ApplicationException("Unkown Parameter Type");
277 if (err != SqliteError.OK)
279 throw new ApplicationException ("Sqlite error in bind " + err);
284 private void GetNextStatement (IntPtr pzStart, out IntPtr pzTail, out IntPtr pStmt)
286 if (parent_conn.Version == 3)
288 SqliteError err = Sqlite.sqlite3_prepare (parent_conn.Handle, pzStart, -1, out pStmt, out pzTail);
289 if (err != SqliteError.OK)
290 throw new SqliteSyntaxException (GetError3());
295 SqliteError err = Sqlite.sqlite_compile (parent_conn.Handle, pzStart, out pzTail, out pStmt, out errMsg);
297 if (err != SqliteError.OK)
299 string msg = "unknown error";
300 if (errMsg != IntPtr.Zero)
302 msg = Marshal.PtrToStringAnsi (errMsg);
303 Sqlite.sqliteFree (errMsg);
305 throw new SqliteSyntaxException (msg);
310 // Executes a statement and ignores its result.
311 private void ExecuteStatement (IntPtr pStmt) {
313 IntPtr pazValue, pazColName;
314 ExecuteStatement (pStmt, out cols, out pazValue, out pazColName);
317 // Executes a statement and returns whether there is more data available.
318 internal bool ExecuteStatement (IntPtr pStmt, out int cols, out IntPtr pazValue, out IntPtr pazColName) {
321 if (parent_conn.Version == 3)
323 err = Sqlite.sqlite3_step (pStmt);
324 if (err == SqliteError.ERROR)
325 throw new SqliteExecutionException (GetError3());
326 pazValue = IntPtr.Zero; pazColName = IntPtr.Zero; // not used for v=3
327 cols = Sqlite.sqlite3_column_count (pStmt);
331 err = Sqlite.sqlite_step (pStmt, out cols, out pazValue, out pazColName);
332 if (err == SqliteError.ERROR)
333 throw new SqliteExecutionException ();
336 if (err == SqliteError.BUSY)
337 throw new SqliteBusyException();
339 if (err == SqliteError.MISUSE)
340 throw new SqliteExecutionException();
342 // err is either ROW or DONE.
343 return err == SqliteError.ROW;
348 #region Public Methods
350 public void Cancel ()
354 public string ProcessParameters()
356 string processedText = sql;
358 //Regex looks odd perhaps, but it works - same impl. as in the firebird db provider
359 //the named parameters are using the ADO.NET standard @-prefix but sqlite is considering ":" as a prefix for v.3...
360 //ref: http://www.mail-archive.com/sqlite-users@sqlite.org/msg01851.html
361 //Regex r = new Regex(@"(('[^']*?\@[^']*')*[^'@]*?)*(?<param>@\w+)+([^'@]*?('[^']*?\@[^']*'))*",RegexOptions.ExplicitCapture);
363 //The above statement is true for the commented regEx, but I changed it to use the :-prefix, because now (12.05.2005 sqlite3)
364 //sqlite is using : as Standard Parameterprefix
366 MatchEvaluator me = new MatchEvaluator(ReplaceParams);
367 processedText = v2Parameters.Replace(sql, me);
368 return processedText;
371 public void Prepare ()
373 // There isn't much we can do here. If a table schema
374 // changes after preparing a statement, Sqlite bails,
375 // so we can only compile statements right before we
378 if (prepared) return;
380 if (Parameters.Count > 0 && parent_conn.Version == 2)
382 sql = ProcessParameters();
388 IDbDataParameter IDbCommand.CreateParameter()
390 return CreateParameter ();
393 public SqliteParameter CreateParameter ()
395 return new SqliteParameter ();
398 public int ExecuteNonQuery ()
401 ExecuteReader (CommandBehavior.Default, false, out rows_affected);
402 return rows_affected;
405 public object ExecuteScalar ()
407 SqliteDataReader r = ExecuteReader ();
408 if (r == null || !r.Read ()) {
416 IDataReader IDbCommand.ExecuteReader ()
418 return ExecuteReader ();
421 IDataReader IDbCommand.ExecuteReader (CommandBehavior behavior)
423 return ExecuteReader (behavior);
426 public SqliteDataReader ExecuteReader ()
428 return ExecuteReader (CommandBehavior.Default);
431 public SqliteDataReader ExecuteReader (CommandBehavior behavior)
434 return ExecuteReader (behavior, true, out r);
437 public SqliteDataReader ExecuteReader (CommandBehavior behavior, bool want_results, out int rows_affected)
439 // The SQL string may contain multiple sql commands, so the main
440 // thing to do is have Sqlite iterate through the commands.
441 // If want_results, only the last command is returned as a
442 // DataReader. Otherwise, no command is returned as a
445 IntPtr psql = UnixMarshal.StringToHeap(sql);
446 IntPtr pzTail = psql;
449 parent_conn.StartExec ();
457 GetNextStatement(pzTail, out pzTail, out pStmt);
459 if (pStmt == IntPtr.Zero)
460 throw new Exception();
462 // pzTail is positioned after the last byte in the
463 // statement, which will be the NULL character if
464 // this was the last statement.
465 bool last = Marshal.ReadByte(pzTail) == 0;
468 if (parent_conn.Version == 3)
469 BindParameters3 (pStmt);
471 if (last && want_results)
472 return new SqliteDataReader (this, pStmt, parent_conn.Version);
474 ExecuteStatement(pStmt);
476 if (last) // rows_affected is only used if !want_results
477 rows_affected = NumChanges ();
480 if (parent_conn.Version == 3)
481 Sqlite.sqlite3_finalize (pStmt);
483 Sqlite.sqlite_finalize (pStmt, out errMsgPtr);
491 parent_conn.EndExec ();
492 UnixMarshal.FreeHeap (psql);
496 public int LastInsertRowID ()
498 if (parent_conn.Version == 3)
499 return Sqlite.sqlite3_last_insert_rowid(parent_conn.Handle);
501 return Sqlite.sqlite_last_insert_rowid(parent_conn.Handle);
504 private string GetError3() {
505 return Marshal.PtrToStringAnsi (Sqlite.sqlite3_errmsg (parent_conn.Handle));