SqliteClient: Lots and lots of fixes.
[mono.git] / mcs / class / Mono.Data.SqliteClient / Mono.Data.SqliteClient / SqliteCommand.cs
1 //
2 // Mono.Data.SqliteClient.SqliteCommand.cs
3 //
4 // Represents a Transact-SQL statement or stored procedure to execute against 
5 // a Sqlite database file.
6 //
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>
13 //
14 // Copyright (C) 2002  Vladimir Vukicevic
15 //
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:
23 // 
24 // The above copyright notice and this permission notice shall be
25 // included in all copies or substantial portions of the Software.
26 // 
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.
34 //
35
36 using System;
37 using System.Collections;
38 using System.Text;
39 using System.Runtime.InteropServices;
40 using System.Text.RegularExpressions;
41 using System.Data;
42 using System.Diagnostics; 
43 using Group = System.Text.RegularExpressions.Group;
44
45 namespace Mono.Data.SqliteClient 
46 {
47         public class SqliteCommand : IDbCommand
48         {
49                 #region Fields
50                 
51                 private SqliteConnection parent_conn;
52                 //private SqliteTransaction transaction;
53                 private IDbTransaction transaction;
54                 private string sql;
55                 private int timeout;
56                 private CommandType type;
57                 private UpdateRowSource upd_row_source;
58                 private SqliteParameterCollection sql_params;
59                 private bool prepared = false;
60
61                 #endregion
62
63                 #region Constructors and destructors
64                 
65                 public SqliteCommand ()
66                 {
67                         sql = "";
68                 }
69                                 
70                 public SqliteCommand (string sqlText)
71                 {
72                         sql = sqlText;
73                 }
74                 
75                 public SqliteCommand (string sqlText, SqliteConnection dbConn)
76                 {
77                         sql = sqlText;
78                         parent_conn = dbConn;
79                 }
80                 
81                 public SqliteCommand (string sqlText, SqliteConnection dbConn, IDbTransaction trans)
82                 {
83                         sql = sqlText;
84                         parent_conn = dbConn;
85                         transaction = trans;
86                 }
87                 
88                 public void Dispose ()
89                 {
90                 }
91                 
92                 #endregion
93
94                 #region Properties
95                 
96                 public string CommandText 
97                 {
98                         get { return sql; }
99                         set { sql = value; prepared = false; }
100                 }
101                 
102                 public int CommandTimeout
103                 {
104                         get { return timeout; }
105                         set { timeout = value; }
106                 }
107                 
108                 public CommandType CommandType 
109                 {
110                         get { return type; }
111                         set { type = value; }
112                 }
113                 
114                 IDbConnection IDbCommand.Connection 
115                 {
116                         get 
117                         { 
118                                 return parent_conn; 
119                         }
120                         set 
121                         {
122                                 if (!(value is SqliteConnection)) 
123                                 {
124                                         throw new InvalidOperationException ("Can't set Connection to something other than a SqliteConnection");
125                                 }
126                                 parent_conn = (SqliteConnection) value;
127                         }
128                 }
129                 
130                 public SqliteConnection Connection
131                 {
132                         get { return parent_conn; }
133                         set { parent_conn = value; }
134                 }
135                 
136                 IDataParameterCollection IDbCommand.Parameters 
137                 {
138                         get { return Parameters; }
139                 }
140                 
141                 public SqliteParameterCollection Parameters 
142                 {
143                         get
144                         {
145                                 if (sql_params == null) sql_params = new SqliteParameterCollection();
146                                 return sql_params;
147                         }
148                 }
149                 
150                 public IDbTransaction Transaction 
151                 {
152                         get { return transaction; }
153                         set { transaction = value; }
154                 }
155                 
156                 public UpdateRowSource UpdatedRowSource 
157                 {
158                         get { return upd_row_source; }
159                         set { upd_row_source = value; }
160                 }
161                                 
162                 #endregion
163
164                 #region Internal Methods
165                 
166                 internal int NumChanges () 
167                 {
168                         if (parent_conn.Version == 3)
169                                 return Sqlite.sqlite3_changes(parent_conn.Handle);
170                         else
171                                 return Sqlite.sqlite_changes(parent_conn.Handle);
172                 }
173                 
174                 private void BindParameters3 (IntPtr pStmt)
175                 {
176                         if (sql_params == null) return;
177                         if (sql_params.Count == 0) return;
178                         
179                         int pcount = Sqlite.sqlite3_bind_parameter_count (pStmt);
180
181                         for (int i = 1; i <= pcount; i++) 
182                         {
183                                 String name = Sqlite.HeapToString (Sqlite.sqlite3_bind_parameter_name (pStmt, i), Encoding.UTF8);
184                                 if (name == null) continue;
185                                 
186                                 SqliteParameter param = sql_params[name];
187                                 
188                                 if (param.Value == null) {
189                                         Sqlite.sqlite3_bind_null (pStmt, i);
190                                         continue;
191                                 }
192                                         
193                                 Type ptype = param.Value.GetType ();
194                                 
195                                 SqliteError err;
196                                 
197                                 if (ptype.Equals (typeof (String))) 
198                                 {
199                                         String s = (String)param.Value;
200                                         err = Sqlite.sqlite3_bind_text16 (pStmt, i, s, -1, (IntPtr)(-1));
201                                 } 
202                                 else if (ptype.Equals (typeof (DBNull))) 
203                                 {
204                                         err = Sqlite.sqlite3_bind_null (pStmt, i);
205                                 }
206                                 else if (ptype.Equals (typeof (Boolean))) 
207                                 {
208                                         bool b = (bool)param.Value;
209                                         err = Sqlite.sqlite3_bind_int (pStmt, i, b ? 1 : 0);
210                                 } else if (ptype.Equals (typeof (Byte))) 
211                                 {
212                                         err = Sqlite.sqlite3_bind_int (pStmt, i, (Byte)param.Value);
213                                 }
214                                 else if (ptype.Equals (typeof (Char))) 
215                                 {
216                                         err = Sqlite.sqlite3_bind_int (pStmt, i, (Char)param.Value);
217                                 } 
218                                 else if (ptype.Equals (typeof (Int16))) 
219                                 {
220                                         err = Sqlite.sqlite3_bind_int (pStmt, i, (Int16)param.Value);
221                                 } 
222                                 else if (ptype.Equals (typeof (Int32))) 
223                                 {
224                                         err = Sqlite.sqlite3_bind_int (pStmt, i, (Int32)param.Value);
225                                 }
226                                 else if (ptype.Equals (typeof (SByte))) 
227                                 {
228                                         err = Sqlite.sqlite3_bind_int (pStmt, i, (SByte)param.Value);
229                                 } 
230                                 else if (ptype.Equals (typeof (UInt16))) 
231                                 {
232                                         err = Sqlite.sqlite3_bind_int (pStmt, i, (UInt16)param.Value);
233                                 }
234                                 else if (ptype.Equals (typeof (DateTime))) 
235                                 {
236                                         DateTime dt = (DateTime)param.Value;
237                                         err = Sqlite.sqlite3_bind_int64 (pStmt, i, dt.ToFileTime ());
238                                 } 
239                                 else if (ptype.Equals (typeof (Double))) 
240                                 {
241                                         err = Sqlite.sqlite3_bind_double (pStmt, i, (Double)param.Value);
242                                 }
243                                 else if (ptype.Equals (typeof (Single))) 
244                                 {
245                                         err = Sqlite.sqlite3_bind_double (pStmt, i, (Single)param.Value);
246                                 } 
247                                 else if (ptype.Equals (typeof (UInt32))) 
248                                 {
249                                         err = Sqlite.sqlite3_bind_int64 (pStmt, i, (UInt32)param.Value);
250                                 }
251                                 else if (ptype.Equals (typeof (Int64))) 
252                                 {
253                                         err = Sqlite.sqlite3_bind_int64 (pStmt, i, (Int64)param.Value);
254                                 } 
255                                 else if (ptype.Equals (typeof (Byte[]))) 
256                                 {
257                                         err = Sqlite.sqlite3_bind_blob (pStmt, i, (Byte[])param.Value, ((Byte[])param.Value).Length, (IntPtr)(-1));
258                                 } 
259                                 else 
260                                 {
261                                         throw new ApplicationException("Unkown Parameter Type");
262                                 }
263                                 if (err != SqliteError.OK) 
264                                 {
265                                         throw new ApplicationException ("Sqlite error in bind " + err);
266                                 }
267                         }
268                 }
269
270                 private void GetNextStatement (IntPtr pzStart, out IntPtr pzTail, out IntPtr pStmt)
271                 {
272                         if (parent_conn.Version == 3)
273                         {
274                                 SqliteError err = Sqlite.sqlite3_prepare16 (parent_conn.Handle, pzStart, -1, out pStmt, out pzTail);
275                                 if (err != SqliteError.OK)
276                                         throw new SqliteSyntaxException (GetError3());
277                         }
278                         else
279                         {
280                                 IntPtr errMsg;
281                                 SqliteError err = Sqlite.sqlite_compile (parent_conn.Handle, pzStart, out pzTail, out pStmt, out errMsg);
282                                 
283                                 if (err != SqliteError.OK) 
284                                 {
285                                         string msg = "unknown error";
286                                         if (errMsg != IntPtr.Zero) 
287                                         {
288                                                 msg = Marshal.PtrToStringAnsi (errMsg);
289                                                 Sqlite.sqliteFree (errMsg);
290                                         }
291                                         throw new SqliteSyntaxException (msg);
292                                 }
293                         }
294                 }
295                 
296                 // Executes a statement and ignores its result.
297                 private void ExecuteStatement (IntPtr pStmt) {
298                         int cols;
299                         IntPtr pazValue, pazColName;
300                         ExecuteStatement (pStmt, out cols, out pazValue, out pazColName);
301                 }
302
303                 // Executes a statement and returns whether there is more data available.
304                 internal bool ExecuteStatement (IntPtr pStmt, out int cols, out IntPtr pazValue, out IntPtr pazColName) {
305                         SqliteError err;
306                         
307                         if (parent_conn.Version == 3) 
308                         {
309                                 err = Sqlite.sqlite3_step (pStmt);
310                                 if (err == SqliteError.ERROR)
311                                         throw new SqliteExecutionException (GetError3());
312                                 pazValue = IntPtr.Zero; pazColName = IntPtr.Zero; // not used for v=3
313                                 cols = Sqlite.sqlite3_column_count (pStmt);
314                         }
315                         else 
316                         {
317                                 err = Sqlite.sqlite_step (pStmt, out cols, out pazValue, out pazColName);
318                                 if (err == SqliteError.ERROR)
319                                         throw new SqliteExecutionException ();
320                         }
321                         
322                         if (err == SqliteError.BUSY)
323                                 throw new SqliteBusyException();
324                         
325                         if (err == SqliteError.MISUSE)
326                                 throw new SqliteExecutionException();
327                                 
328                         // err is either ROW or DONE.
329                         return err == SqliteError.ROW;
330                 }
331                 
332                 #endregion
333
334                 #region Public Methods
335                 
336                 public void Cancel ()
337                 {
338                 }
339                 
340                 public string BindParameters2()
341                 {
342                         string text = sql;
343                         
344                         // There used to be a crazy regular expression here, but it caused Mono
345                         // to go into an infinite loop of some sort when there were no parameters
346                         // in the SQL string.  That was too complicated anyway.
347                         
348                         // Here we search for substrings of the form :wwwww where w is a letter or digit
349                         // (not sure what a legitimate Sqlite3 identifier is), except those within quotes.
350                         
351                         char inquote = (char)0;
352                         for (int i = 0; i < text.Length; i++) {
353                                 char c = text[i];
354                                 if (c == inquote) {
355                                         inquote = (char)0;
356                                 } else if (inquote == (char)0 && (c == '\'' || c == '"')) {
357                                         inquote = c;
358                                 } else if (inquote == (char)0 && c == ':') {
359                                         int start = i;
360                                         while (++i < text.Length && char.IsLetterOrDigit(text[i])) { } // scan to end
361                                         string name = text.Substring(start, i-start);
362                                         string value = "'" + Convert.ToString(Parameters[name].Value).Replace("'", "''") + "'";
363                                         text = text.Replace(name, value);
364                                         i += value.Length - name.Length - 1;
365                                 }
366                         }
367                         
368                         return text;
369                 }
370                 
371                 public void Prepare ()
372                 {
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
376                         // want to run them.
377                         
378                         if (prepared) return;           
379                 
380                         if (Parameters.Count > 0 && parent_conn.Version == 2)
381                         {
382                                 sql = BindParameters2();
383                         }
384                         
385                         prepared = true;
386                 }
387                 
388                 IDbDataParameter IDbCommand.CreateParameter()
389                 {
390                         return CreateParameter ();
391                 }
392                 
393                 public SqliteParameter CreateParameter ()
394                 {
395                         return new SqliteParameter ();
396                 }
397                 
398                 public int ExecuteNonQuery ()
399                 {
400                         int rows_affected;
401                         ExecuteReader (CommandBehavior.Default, false, out rows_affected);
402                         return rows_affected;
403                 }
404                 
405                 public object ExecuteScalar ()
406                 {
407                         SqliteDataReader r = ExecuteReader ();
408                         if (r == null || !r.Read ()) {
409                                 return null;
410                         }
411                         object o = r[0];
412                         r.Close ();
413                         return o;
414                 }
415                 
416                 IDataReader IDbCommand.ExecuteReader ()
417                 {
418                         return ExecuteReader ();
419                 }
420                 
421                 IDataReader IDbCommand.ExecuteReader (CommandBehavior behavior)
422                 {
423                         return ExecuteReader (behavior);
424                 }
425                 
426                 public SqliteDataReader ExecuteReader ()
427                 {
428                         return ExecuteReader (CommandBehavior.Default);
429                 }
430                 
431                 public SqliteDataReader ExecuteReader (CommandBehavior behavior)
432                 {
433                         int r;
434                         return ExecuteReader (behavior, true, out r);
435                 }
436                 
437                 public SqliteDataReader ExecuteReader (CommandBehavior behavior, bool want_results, out int rows_affected)
438                 {
439                         Prepare ();
440                         
441                         // The SQL string may contain multiple sql commands, so the main
442                         // thing to do is have Sqlite iterate through the commands.
443                         // If want_results, only the last command is returned as a
444                         // DataReader.  Otherwise, no command is returned as a
445                         // DataReader.
446                 
447                         IntPtr psql; // pointer to SQL command
448                         
449                         // Sqlite 2 docs say this: By default, SQLite assumes that all data uses a fixed-size 8-bit 
450                         // character (iso8859).  But if you give the --enable-utf8 option to the configure script, then the 
451                         // library assumes UTF-8 variable sized characters. This makes a difference for the LIKE and GLOB 
452                         // operators and the LENGTH() and SUBSTR() functions. The static string sqlite_encoding will be set 
453                         // to either "UTF-8" or "iso8859" to indicate how the library was compiled. In addition, the sqlite.h 
454                         // header file will define one of the macros SQLITE_UTF8 or SQLITE_ISO8859, as appropriate.
455                         // 
456                         // We have no way of knowing whether Sqlite 2 expects ISO8859 or UTF-8, but ISO8859 seems to be the
457                         // default.  Therefore, we need to use an ISO8859(-1) compatible encoding, like ANSI.
458                         // OTOH, the user may want to specify the encoding of the bytes stored in the database, regardless
459                         // of what Sqlite is treating them as, 
460                         
461                         // For Sqlite 3, we use the UTF-16 prepare function, so we need a UTF-16 string.
462                         
463                         if (parent_conn.Version == 2)
464                                 psql = Sqlite.StringToHeap (sql.Trim(), parent_conn.Encoding);
465                         else
466                                 psql = Marshal.StringToCoTaskMemUni (sql.Trim());
467
468                         IntPtr pzTail = psql;
469                         IntPtr errMsgPtr;
470                         
471                         parent_conn.StartExec ();
472                         
473                         rows_affected = 0;
474                         
475                         try {
476                                 while (true) {
477                                         IntPtr pStmt;
478                                          
479                                         GetNextStatement(pzTail, out pzTail, out pStmt);
480                                         
481                                         if (pStmt == IntPtr.Zero)
482                                                 throw new Exception();
483                                         
484                                         // pzTail is positioned after the last byte in the
485                                         // statement, which will be the NULL character if
486                                         // this was the last statement.
487                                         bool last = Marshal.ReadByte(pzTail) == 0;
488
489                                         try {
490                                                 if (parent_conn.Version == 3)
491                                                         BindParameters3 (pStmt);
492                                                 
493                                                 if (last && want_results)
494                                                         return new SqliteDataReader (this, pStmt, parent_conn.Version);
495
496                                                 ExecuteStatement(pStmt);
497                                                 
498                                                 if (last) // rows_affected is only used if !want_results
499                                                         rows_affected = NumChanges ();
500                                                 
501                                         } finally {
502                                                 if (parent_conn.Version == 3) 
503                                                         Sqlite.sqlite3_finalize (pStmt);
504                                                 else
505                                                         Sqlite.sqlite_finalize (pStmt, out errMsgPtr);
506                                         }
507                                         
508                                         if (last) break;
509                                 }
510
511                                 return null;
512                         } finally {
513                                 parent_conn.EndExec ();
514                                 Marshal.FreeCoTaskMem (psql);
515                         }
516                 }
517
518                 public int LastInsertRowID () 
519                 {
520                         if (parent_conn.Version == 3)
521                                 return Sqlite.sqlite3_last_insert_rowid(parent_conn.Handle);
522                         else
523                                 return Sqlite.sqlite_last_insert_rowid(parent_conn.Handle);
524                 }
525                 
526                 private string GetError3() {
527                         return Marshal.PtrToStringUni (Sqlite.sqlite3_errmsg16 (parent_conn.Handle));
528                 }
529         #endregion
530         }
531 }