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>
13 // Copyright (C) 2002 Vladimir Vukicevic
15 // Permission is hereby granted, free of charge, to any person obtaining
16 // a copy of this software and associated documentation files (the
17 // "Software"), to deal in the Software without restriction, including
18 // without limitation the rights to use, copy, modify, merge, publish,
19 // distribute, sublicense, and/or sell copies of the Software, and to
20 // permit persons to whom the Software is furnished to do so, subject to
21 // the following conditions:
23 // The above copyright notice and this permission notice shall be
24 // included in all copies or substantial portions of the Software.
26 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
27 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
28 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
29 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
30 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
31 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
32 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
36 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;
60 private ArrayList pStmts;
64 #region Constructors and destructors
66 public SqliteCommand ()
71 public SqliteCommand (string sqlText)
76 public SqliteCommand (string sqlText, SqliteConnection dbConn)
82 public SqliteCommand (string sqlText, SqliteConnection dbConn, IDbTransaction trans)
89 public void Dispose ()
97 public string CommandText
103 public int CommandTimeout
105 get { return timeout; }
106 set { timeout = value; }
109 public CommandType CommandType
112 set { type = value; }
115 IDbConnection IDbCommand.Connection
123 if (!(value is SqliteConnection))
125 throw new InvalidOperationException ("Can't set Connection to something other than a SqliteConnection");
127 parent_conn = (SqliteConnection) value;
131 public SqliteConnection Connection
133 get { return parent_conn; }
134 set { parent_conn = value; }
137 IDataParameterCollection IDbCommand.Parameters
139 get { return Parameters; }
142 public SqliteParameterCollection Parameters
146 if (sql_params == null) sql_params = new SqliteParameterCollection();
151 public IDbTransaction Transaction
153 get { return transaction; }
154 set { transaction = value; }
157 public UpdateRowSource UpdatedRowSource
159 get { return upd_row_source; }
160 set { upd_row_source = value; }
165 #region Internal Methods
167 internal int NumChanges ()
169 if (parent_conn.Version == 3)
170 return Sqlite.sqlite3_changes(parent_conn.Handle);
172 return Sqlite.sqlite_changes(parent_conn.Handle);
175 private string ReplaceParams(Match m)
177 string input = m.Value;
178 if (m.Groups["param"].Success)
180 Group g = m.Groups["param"];
181 string find = g.Value;
182 //FIXME: sqlite works internally only with strings, so this assumtion is mostly legit, but what about date formatting, etc?
183 //Need to fix SqlLiteDataReader first to acurately describe the tables
184 SqliteParameter sqlp = Parameters[find];
185 string replace = Convert.ToString(sqlp.Value);
186 if(sqlp.DbType == DbType.String)
188 replace = "\"" + replace + "\"";
191 input = Regex.Replace(input,find,replace);
200 #region Public Methods
202 public void Cancel ()
206 public string ProcessParameters()
208 string processedText = sql;
210 //Regex looks odd perhaps, but it works - same impl. as in the firebird db provider
211 //the named parameters are using the ADO.NET standard @-prefix but sqlite is considering ":" as a prefix for v.3...
212 //ref: http://www.mail-archive.com/sqlite-users@sqlite.org/msg01851.html
213 //Regex r = new Regex(@"(('[^']*?\@[^']*')*[^'@]*?)*(?<param>@\w+)+([^'@]*?('[^']*?\@[^']*'))*",RegexOptions.ExplicitCapture);
215 //The above statement is true for the commented regEx, but I changed it to use the :-prefix, because now (12.05.2005 sqlite3)
216 //sqlite is using : as Standard Parameterprefix
218 Regex r = new Regex(@"(('[^']*?\:[^']*')*[^':]*?)*(?<param>:\w+)+([^':]*?('[^']*?\:[^']*'))*",RegexOptions.ExplicitCapture);
219 MatchEvaluator me = new MatchEvaluator(ReplaceParams);
220 processedText = r.Replace(sql, me);
221 return processedText;
224 public void Prepare ()
226 pStmts = new ArrayList();
227 string sqlcmds = sql;
229 if (Parameters.Count > 0 && parent_conn.Version == 2)
231 sqlcmds = ProcessParameters();
234 SqliteError err = SqliteError.OK;
235 IntPtr psql = UnixMarshal.StringToAlloc(sqlcmds);
236 IntPtr pzTail = psql;
238 do { // sql may contain multiple sql commands, loop until they're all processed
239 IntPtr pStmt = IntPtr.Zero;
240 if (parent_conn.Version == 3)
242 err = Sqlite.sqlite3_prepare (parent_conn.Handle, pzTail, sql.Length, out pStmt, out pzTail);
243 if (err != SqliteError.OK) {
244 string msg = Marshal.PtrToStringAnsi (Sqlite.sqlite3_errmsg (parent_conn.Handle));
245 throw new ApplicationException (msg);
251 err = Sqlite.sqlite_compile (parent_conn.Handle, pzTail, out pzTail, out pStmt, out errMsg);
253 if (err != SqliteError.OK)
255 string msg = "unknown error";
256 if (errMsg != IntPtr.Zero)
258 msg = Marshal.PtrToStringAnsi (errMsg);
259 Sqlite.sqliteFree (errMsg);
261 throw new ApplicationException ("Sqlite error: " + msg);
267 if (parent_conn.Version == 3)
269 int pcount = Sqlite.sqlite3_bind_parameter_count (pStmt);
270 if (sql_params == null) pcount = 0;
272 for (int i = 1; i <= pcount; i++)
274 String name = Sqlite.sqlite3_bind_parameter_name (pStmt, i);
275 SqliteParameter param = sql_params[name];
276 Type ptype = param.Value.GetType ();
278 if (ptype.Equals (typeof (String)))
280 String s = (String)param.Value;
281 err = Sqlite.sqlite3_bind_text (pStmt, i, s, s.Length, (IntPtr)(-1));
283 else if (ptype.Equals (typeof (DBNull)))
285 err = Sqlite.sqlite3_bind_null (pStmt, i);
287 else if (ptype.Equals (typeof (Boolean)))
289 bool b = (bool)param.Value;
290 err = Sqlite.sqlite3_bind_int (pStmt, i, b ? 1 : 0);
291 } else if (ptype.Equals (typeof (Byte)))
293 err = Sqlite.sqlite3_bind_int (pStmt, i, (Byte)param.Value);
295 else if (ptype.Equals (typeof (Char)))
297 err = Sqlite.sqlite3_bind_int (pStmt, i, (Char)param.Value);
299 else if (ptype.Equals (typeof (Int16)))
301 err = Sqlite.sqlite3_bind_int (pStmt, i, (Int16)param.Value);
303 else if (ptype.Equals (typeof (Int32)))
305 err = Sqlite.sqlite3_bind_int (pStmt, i, (Int32)param.Value);
307 else if (ptype.Equals (typeof (SByte)))
309 err = Sqlite.sqlite3_bind_int (pStmt, i, (SByte)param.Value);
311 else if (ptype.Equals (typeof (UInt16)))
313 err = Sqlite.sqlite3_bind_int (pStmt, i, (UInt16)param.Value);
315 else if (ptype.Equals (typeof (DateTime)))
317 DateTime dt = (DateTime)param.Value;
318 err = Sqlite.sqlite3_bind_int64 (pStmt, i, dt.ToFileTime ());
320 else if (ptype.Equals (typeof (Double)))
322 err = Sqlite.sqlite3_bind_double (pStmt, i, (Double)param.Value);
324 else if (ptype.Equals (typeof (Single)))
326 err = Sqlite.sqlite3_bind_double (pStmt, i, (Single)param.Value);
328 else if (ptype.Equals (typeof (UInt32)))
330 err = Sqlite.sqlite3_bind_int64 (pStmt, i, (UInt32)param.Value);
332 else if (ptype.Equals (typeof (Int64)))
334 err = Sqlite.sqlite3_bind_int64 (pStmt, i, (Int64)param.Value);
338 throw new ApplicationException("Unkown Parameter Type");
340 if (err != SqliteError.OK)
342 throw new ApplicationException ("Sqlite error in bind " + err);
346 } while ((int)pzTail - (int)psql < sql.Length);
348 UnixMarshal.Free(psql);
354 IDbDataParameter IDbCommand.CreateParameter()
356 return CreateParameter ();
359 public SqliteParameter CreateParameter ()
361 return new SqliteParameter ();
364 public int ExecuteNonQuery ()
367 SqliteDataReader r = ExecuteReader (CommandBehavior.Default, false, out rows_affected);
368 return rows_affected;
371 public object ExecuteScalar ()
373 SqliteDataReader r = ExecuteReader ();
374 if (r == null || !r.Read ()) {
382 IDataReader IDbCommand.ExecuteReader ()
384 return ExecuteReader ();
387 IDataReader IDbCommand.ExecuteReader (CommandBehavior behavior)
389 return ExecuteReader (behavior);
392 public SqliteDataReader ExecuteReader ()
394 return ExecuteReader (CommandBehavior.Default);
397 public SqliteDataReader ExecuteReader (CommandBehavior behavior)
400 return ExecuteReader (behavior, true, out r);
403 public SqliteDataReader ExecuteReader (CommandBehavior behavior, bool want_results, out int rows_affected)
405 SqliteDataReader reader = null;
406 SqliteError err = SqliteError.OK;
407 IntPtr errMsg = IntPtr.Zero;
408 parent_conn.StartExec ();
416 for (int i = 0; i < pStmts.Count; i++) {
417 IntPtr pStmt = (IntPtr)pStmts[i];
419 // If want_results, return the results of the last statement
420 // via the SqliteDataReader, and execute but ignore the results
421 // of the other statements.
422 if (i == pStmts.Count-1 && want_results)
424 reader = new SqliteDataReader (this, pStmt, parent_conn.Version);
428 // Execute but ignore the results of these statements.
429 if (parent_conn.Version == 3)
431 err = Sqlite.sqlite3_step (pStmt);
436 IntPtr pazValue = IntPtr.Zero;
437 IntPtr pazColName = IntPtr.Zero;
438 err = Sqlite.sqlite_step (pStmt, out cols, out pazValue, out pazColName);
440 // On error, misuse, or busy, don't bother with the rest of the statements.
441 if (err != SqliteError.ROW && err != SqliteError.DONE) break;
446 foreach (IntPtr pStmt in pStmts) {
447 if (parent_conn.Version == 3)
449 err = Sqlite.sqlite3_finalize (pStmt);
453 err = Sqlite.sqlite_finalize (pStmt, out errMsg);
456 parent_conn.EndExec ();
460 if (err != SqliteError.OK &&
461 err != SqliteError.DONE &&
462 err != SqliteError.ROW)
464 if (errMsg != IntPtr.Zero)
466 // TODO: Get the message text
468 throw new ApplicationException ("Sqlite error");
470 rows_affected = NumChanges ();
474 public int LastInsertRowID ()
476 if (parent_conn.Version == 3)
477 return Sqlite.sqlite3_last_insert_rowid(parent_conn.Handle);
479 return Sqlite.sqlite_last_insert_rowid(parent_conn.Handle);