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.
37 using System.Runtime.InteropServices;
38 using System.Text.RegularExpressions;
40 using System.Diagnostics;
42 namespace Mono.Data.SqliteClient
44 public class SqliteCommand : IDbCommand
48 private SqliteConnection parent_conn;
49 //private SqliteTransaction transaction;
50 private IDbTransaction transaction;
53 private CommandType type;
54 private UpdateRowSource upd_row_source;
55 private SqliteParameterCollection sql_params;
56 private bool prepared = false;
61 #region Constructors and destructors
63 public SqliteCommand ()
66 sql_params = new SqliteParameterCollection ();
69 public SqliteCommand (string sqlText)
72 sql_params = new SqliteParameterCollection ();
75 public SqliteCommand (string sqlText, SqliteConnection dbConn)
79 sql_params = new SqliteParameterCollection ();
82 public SqliteCommand (string sqlText, SqliteConnection dbConn, IDbTransaction trans)
87 sql_params = new SqliteParameterCollection ();
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
145 get { return sql_params; }
148 public IDbTransaction Transaction
150 get { return transaction; }
151 set { transaction = value; }
154 public UpdateRowSource UpdatedRowSource
156 get { return upd_row_source; }
157 set { upd_row_source = value; }
162 #region Internal Methods
164 internal int NumChanges ()
166 if (parent_conn.Version == 3)
167 return Sqlite.sqlite3_changes(parent_conn.Handle);
169 return Sqlite.sqlite_changes(parent_conn.Handle);
172 private string ReplaceParams(Match m)
174 string input = m.Value;
175 if (m.Groups["param"].Success)
177 Group g = m.Groups["param"];
178 string find = g.Value;
179 //FIXME: sqlite works internally only with strings, so this assumtion is mostly legit, but what about date formatting, etc?
180 //Need to fix SqlLiteDataReader first to acurately describe the tables
181 SqliteParameter sqlp = Parameters[find];
182 string replace = Convert.ToString(sqlp.Value);
183 if(sqlp.DbType == DbType.String)
185 replace = "\"" + replace + "\"";
188 input = Regex.Replace(input,find,replace);
197 #region Public Methods
199 public void Cancel ()
203 public string ProcessParameters()
205 string processedText = sql;
207 //Regex looks odd perhaps, but it works - same impl. as in the firebird db provider
208 //the named parameters are using the ADO.NET standard @-prefix but sqlite is considering ":" as a prefix for v.3...
209 //ref: http://www.mail-archive.com/sqlite-users@sqlite.org/msg01851.html
210 //Regex r = new Regex(@"(('[^']*?\@[^']*')*[^'@]*?)*(?<param>@\w+)+([^'@]*?('[^']*?\@[^']*'))*",RegexOptions.ExplicitCapture);
212 //The above statement is true for the commented regEx, but I changed it to use the :-prefix, because now (12.05.2005 sqlite3)
213 //sqlite is using : as Standard Parameterprefix
215 Regex r = new Regex(@"(('[^']*?\:[^']*')*[^':]*?)*(?<param>:\w+)+([^':]*?('[^']*?\:[^']*'))*",RegexOptions.ExplicitCapture);
216 MatchEvaluator me = new MatchEvaluator(ReplaceParams);
217 processedText = r.Replace(sql, me);
218 return processedText;
221 public void Prepare ()
223 SqliteError err = SqliteError.OK;
224 IntPtr pzTail = IntPtr.Zero;
226 if (parent_conn.Version == 3)
228 err = Sqlite.sqlite3_prepare (parent_conn.Handle, sql, sql.Length, out pStmt, out pzTail);
229 if (err != SqliteError.OK)
230 throw new ApplicationException ("Sqlite error in prepare " + err);
231 int pcount = Sqlite.sqlite3_bind_parameter_count (pStmt);
233 for (int i = 1; i <= pcount; i++)
235 String name = Sqlite.sqlite3_bind_parameter_name (pStmt, i);
236 SqliteParameter param = sql_params[name];
237 Type ptype = param.Value.GetType ();
239 if (ptype.Equals (typeof (String)))
241 String s = (String)param.Value;
242 err = Sqlite.sqlite3_bind_text (pStmt, i, s, s.Length, (IntPtr)(-1));
244 else if (ptype.Equals (typeof (DBNull)))
246 err = Sqlite.sqlite3_bind_null (pStmt, i);
248 else if (ptype.Equals (typeof (Boolean)))
250 bool b = (bool)param.Value;
251 err = Sqlite.sqlite3_bind_int (pStmt, i, b ? 1 : 0);
252 } else if (ptype.Equals (typeof (Byte)))
254 err = Sqlite.sqlite3_bind_int (pStmt, i, (Byte)param.Value);
256 else if (ptype.Equals (typeof (Char)))
258 err = Sqlite.sqlite3_bind_int (pStmt, i, (Char)param.Value);
260 else if (ptype.Equals (typeof (Int16)))
262 err = Sqlite.sqlite3_bind_int (pStmt, i, (Int16)param.Value);
264 else if (ptype.Equals (typeof (Int32)))
266 err = Sqlite.sqlite3_bind_int (pStmt, i, (Int32)param.Value);
268 else if (ptype.Equals (typeof (SByte)))
270 err = Sqlite.sqlite3_bind_int (pStmt, i, (SByte)param.Value);
272 else if (ptype.Equals (typeof (UInt16)))
274 err = Sqlite.sqlite3_bind_int (pStmt, i, (UInt16)param.Value);
276 else if (ptype.Equals (typeof (DateTime)))
278 DateTime dt = (DateTime)param.Value;
279 err = Sqlite.sqlite3_bind_int64 (pStmt, i, dt.ToFileTime ());
281 else if (ptype.Equals (typeof (Double)))
283 err = Sqlite.sqlite3_bind_double (pStmt, i, (Double)param.Value);
285 else if (ptype.Equals (typeof (Single)))
287 err = Sqlite.sqlite3_bind_double (pStmt, i, (Single)param.Value);
289 else if (ptype.Equals (typeof (UInt32)))
291 err = Sqlite.sqlite3_bind_int64 (pStmt, i, (UInt32)param.Value);
293 else if (ptype.Equals (typeof (Int64)))
295 err = Sqlite.sqlite3_bind_int64 (pStmt, i, (Int64)param.Value);
299 throw new ApplicationException("Unkown Parameter Type");
301 if (err != SqliteError.OK)
303 throw new ApplicationException ("Sqlite error in bind " + err);
309 IntPtr errMsg = IntPtr.Zero;
311 string sqlData = sql;
312 if (Parameters.Count > 0)
314 sqlData = ProcessParameters();
316 err = Sqlite.sqlite_compile (parent_conn.Handle, sqlData, out pzTail, out pStmt, out errMsg);
318 if (err != SqliteError.OK)
320 if (errMsg != IntPtr.Zero)
322 msg = Marshal.PtrToStringAnsi (errMsg);
323 Sqlite.sqliteFree (errMsg);
325 throw new ApplicationException ("Sqlite error " + msg);
332 IDbDataParameter IDbCommand.CreateParameter()
334 return CreateParameter ();
337 public SqliteParameter CreateParameter ()
339 return new SqliteParameter ();
342 public int ExecuteNonQuery ()
345 SqliteDataReader r = ExecuteReader (CommandBehavior.Default, false, out rows_affected);
346 return rows_affected;
349 public object ExecuteScalar ()
351 SqliteDataReader r = ExecuteReader ();
352 if (r == null || !r.Read ()) {
360 IDataReader IDbCommand.ExecuteReader ()
362 return ExecuteReader ();
365 IDataReader IDbCommand.ExecuteReader (CommandBehavior behavior)
367 return ExecuteReader (behavior);
370 public SqliteDataReader ExecuteReader ()
372 return ExecuteReader (CommandBehavior.Default);
375 public SqliteDataReader ExecuteReader (CommandBehavior behavior)
378 return ExecuteReader (behavior, true, out r);
381 public SqliteDataReader ExecuteReader (CommandBehavior behavior, bool want_results, out int rows_affected)
383 SqliteDataReader reader = null;
384 SqliteError err = SqliteError.OK;
385 IntPtr errMsg = IntPtr.Zero;
386 parent_conn.StartExec ();
397 reader = new SqliteDataReader (this, pStmt, parent_conn.Version);
401 if (parent_conn.Version == 3)
403 err = Sqlite.sqlite3_step (pStmt);
408 IntPtr pazValue = IntPtr.Zero;
409 IntPtr pazColName = IntPtr.Zero;
410 err = Sqlite.sqlite_step (pStmt, out cols, out pazValue, out pazColName);
416 if (parent_conn.Version == 3)
420 err = Sqlite.sqlite_finalize (pStmt, out errMsg);
422 parent_conn.EndExec ();
426 if (err != SqliteError.OK &&
427 err != SqliteError.DONE &&
428 err != SqliteError.ROW)
430 if (errMsg != IntPtr.Zero)
432 //msg = Marshal.PtrToStringAnsi (errMsg);
433 if (parent_conn.Version == 3)
435 err = Sqlite.sqlite3_finalize (pStmt, out errMsg);
439 err = Sqlite.sqlite_finalize (pStmt, out errMsg);
440 Sqlite.sqliteFree (errMsg);
443 throw new ApplicationException ("Sqlite error " + msg);
445 rows_affected = NumChanges ();
449 public int LastInsertRowID ()
451 if (parent_conn.Version == 3)
452 return Sqlite.sqlite3_last_insert_rowid(parent_conn.Handle);
454 return Sqlite.sqlite_last_insert_rowid(parent_conn.Handle);