New test.
[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
185                                 SqliteParameter param = null;
186                                 if (name != null)
187                                         param = sql_params[name];
188                                 else
189                                         param = sql_params[i-1];
190                                 
191                                 if (param.Value == null) {
192                                         Sqlite.sqlite3_bind_null (pStmt, i);
193                                         continue;
194                                 }
195                                         
196                                 Type ptype = param.Value.GetType ();
197                                 if (ptype.IsEnum)
198                                         ptype = Enum.GetUnderlyingType (ptype);
199                                 
200                                 SqliteError err;
201                                 
202                                 if (ptype.Equals (typeof (String))) 
203                                 {
204                                         String s = (String)param.Value;
205                                         err = Sqlite.sqlite3_bind_text16 (pStmt, i, s, -1, (IntPtr)(-1));
206                                 } 
207                                 else if (ptype.Equals (typeof (DBNull))) 
208                                 {
209                                         err = Sqlite.sqlite3_bind_null (pStmt, i);
210                                 }
211                                 else if (ptype.Equals (typeof (Boolean))) 
212                                 {
213                                         bool b = (bool)param.Value;
214                                         err = Sqlite.sqlite3_bind_int (pStmt, i, b ? 1 : 0);
215                                 } else if (ptype.Equals (typeof (Byte))) 
216                                 {
217                                         err = Sqlite.sqlite3_bind_int (pStmt, i, (Byte)param.Value);
218                                 }
219                                 else if (ptype.Equals (typeof (Char))) 
220                                 {
221                                         err = Sqlite.sqlite3_bind_int (pStmt, i, (Char)param.Value);
222                                 } 
223                                 else if (ptype.IsEnum) 
224                                 {
225                                         err = Sqlite.sqlite3_bind_int (pStmt, i, (Int32)param.Value);
226                                 }
227                                 else if (ptype.Equals (typeof (Int16))) 
228                                 {
229                                         err = Sqlite.sqlite3_bind_int (pStmt, i, (Int16)param.Value);
230                                 } 
231                                 else if (ptype.Equals (typeof (Int32))) 
232                                 {
233                                         err = Sqlite.sqlite3_bind_int (pStmt, i, (Int32)param.Value);
234                                 }
235                                 else if (ptype.Equals (typeof (SByte))) 
236                                 {
237                                         err = Sqlite.sqlite3_bind_int (pStmt, i, (SByte)param.Value);
238                                 } 
239                                 else if (ptype.Equals (typeof (UInt16))) 
240                                 {
241                                         err = Sqlite.sqlite3_bind_int (pStmt, i, (UInt16)param.Value);
242                                 }
243                                 else if (ptype.Equals (typeof (DateTime))) 
244                                 {
245                                         DateTime dt = (DateTime)param.Value;
246                                         err = Sqlite.sqlite3_bind_int64 (pStmt, i, dt.ToFileTime ());
247                                 } 
248                                 else if (ptype.Equals (typeof (Double))) 
249                                 {
250                                         err = Sqlite.sqlite3_bind_double (pStmt, i, (Double)param.Value);
251                                 }
252                                 else if (ptype.Equals (typeof (Single))) 
253                                 {
254                                         err = Sqlite.sqlite3_bind_double (pStmt, i, (Single)param.Value);
255                                 } 
256                                 else if (ptype.Equals (typeof (UInt32))) 
257                                 {
258                                         err = Sqlite.sqlite3_bind_int64 (pStmt, i, (UInt32)param.Value);
259                                 }
260                                 else if (ptype.Equals (typeof (Int64))) 
261                                 {
262                                         err = Sqlite.sqlite3_bind_int64 (pStmt, i, (Int64)param.Value);
263                                 } 
264                                 else if (ptype.Equals (typeof (Byte[]))) 
265                                 {
266                                         err = Sqlite.sqlite3_bind_blob (pStmt, i, (Byte[])param.Value, ((Byte[])param.Value).Length, (IntPtr)(-1));
267                                 } 
268                                 else 
269                                 {
270                                         throw new ApplicationException("Unkown Parameter Type");
271                                 }
272                                 if (err != SqliteError.OK) 
273                                 {
274                                         throw new ApplicationException ("Sqlite error in bind " + err);
275                                 }
276                         }
277                 }
278
279                 private void GetNextStatement (IntPtr pzStart, out IntPtr pzTail, out IntPtr pStmt)
280                 {
281                         if (parent_conn.Version == 3)
282                         {
283                                 SqliteError err = Sqlite.sqlite3_prepare16 (parent_conn.Handle, pzStart, -1, out pStmt, out pzTail);
284                                 if (err != SqliteError.OK)
285                                         throw new SqliteSyntaxException (GetError3());
286                         }
287                         else
288                         {
289                                 IntPtr errMsg;
290                                 SqliteError err = Sqlite.sqlite_compile (parent_conn.Handle, pzStart, out pzTail, out pStmt, out errMsg);
291                                 
292                                 if (err != SqliteError.OK) 
293                                 {
294                                         string msg = "unknown error";
295                                         if (errMsg != IntPtr.Zero) 
296                                         {
297                                                 msg = Marshal.PtrToStringAnsi (errMsg);
298                                                 Sqlite.sqliteFree (errMsg);
299                                         }
300                                         throw new SqliteSyntaxException (msg);
301                                 }
302                         }
303                 }
304                 
305                 // Executes a statement and ignores its result.
306                 private void ExecuteStatement (IntPtr pStmt) {
307                         int cols;
308                         IntPtr pazValue, pazColName;
309                         ExecuteStatement (pStmt, out cols, out pazValue, out pazColName);
310                 }
311
312                 // Executes a statement and returns whether there is more data available.
313                 internal bool ExecuteStatement (IntPtr pStmt, out int cols, out IntPtr pazValue, out IntPtr pazColName) {
314                         SqliteError err;
315                         
316                         if (parent_conn.Version == 3) 
317                         {
318                                 err = Sqlite.sqlite3_step (pStmt);
319                                 if (err == SqliteError.ERROR)
320                                         throw new SqliteExecutionException (GetError3());
321                                 pazValue = IntPtr.Zero; pazColName = IntPtr.Zero; // not used for v=3
322                                 cols = Sqlite.sqlite3_column_count (pStmt);
323                         }
324                         else 
325                         {
326                                 err = Sqlite.sqlite_step (pStmt, out cols, out pazValue, out pazColName);
327                                 if (err == SqliteError.ERROR)
328                                         throw new SqliteExecutionException ();
329                         }
330                         
331                         if (err == SqliteError.BUSY)
332                                 throw new SqliteBusyException();
333                         
334                         if (err == SqliteError.MISUSE)
335                                 throw new SqliteExecutionException();
336                                 
337                         // err is either ROW or DONE.
338                         return err == SqliteError.ROW;
339                 }
340                 
341                 #endregion
342
343                 #region Public Methods
344                 
345                 public void Cancel ()
346                 {
347                 }
348                 
349                 public string BindParameters2()
350                 {
351                         string text = sql;
352                         
353                         // There used to be a crazy regular expression here, but it caused Mono
354                         // to go into an infinite loop of some sort when there were no parameters
355                         // in the SQL string.  That was too complicated anyway.
356                         
357                         // Here we search for substrings of the form [:?]wwwww where w is a letter or digit
358                         // (not sure what a legitimate Sqlite3 identifier is), except those within quotes.
359                         
360                         char inquote = (char)0;
361                         int counter = 0;
362                         for (int i = 0; i < text.Length; i++) {
363                                 char c = text[i];
364                                 if (c == inquote) {
365                                         inquote = (char)0;
366                                 } else if (inquote == (char)0 && (c == '\'' || c == '"')) {
367                                         inquote = c;
368                                 } else if (inquote == (char)0 && (c == ':' || c == '?')) {
369                                         int start = i;
370                                         while (++i < text.Length && char.IsLetterOrDigit(text[i])) { } // scan to end
371                                         string name = text.Substring(start, i-start);
372                                         SqliteParameter p;
373                                         if (name.Length > 1)
374                                                 p = Parameters[name];
375                                         else
376                                                 p = Parameters[counter];
377                                         string value = "'" + Convert.ToString(p.Value).Replace("'", "''") + "'";
378                                         text = text.Remove(start, name.Length).Insert(start, value);
379                                         i += value.Length - name.Length - 1;
380                                         counter++;
381                                 }
382                         }
383                         
384                         return text;
385                 }
386                 
387                 public void Prepare ()
388                 {
389                         // There isn't much we can do here.  If a table schema
390                         // changes after preparing a statement, Sqlite bails,
391                         // so we can only compile statements right before we
392                         // want to run them.
393                         
394                         if (prepared) return;           
395                 
396                         if (Parameters.Count > 0 && parent_conn.Version == 2)
397                         {
398                                 sql = BindParameters2();
399                         }
400                         
401                         prepared = true;
402                 }
403                 
404                 IDbDataParameter IDbCommand.CreateParameter()
405                 {
406                         return CreateParameter ();
407                 }
408                 
409                 public SqliteParameter CreateParameter ()
410                 {
411                         return new SqliteParameter ();
412                 }
413                 
414                 public int ExecuteNonQuery ()
415                 {
416                         int rows_affected;
417                         ExecuteReader (CommandBehavior.Default, false, out rows_affected);
418                         return rows_affected;
419                 }
420                 
421                 public object ExecuteScalar ()
422                 {
423                         SqliteDataReader r = ExecuteReader ();
424                         if (r == null || !r.Read ()) {
425                                 return null;
426                         }
427                         object o = r[0];
428                         r.Close ();
429                         return o;
430                 }
431                 
432                 IDataReader IDbCommand.ExecuteReader ()
433                 {
434                         return ExecuteReader ();
435                 }
436                 
437                 IDataReader IDbCommand.ExecuteReader (CommandBehavior behavior)
438                 {
439                         return ExecuteReader (behavior);
440                 }
441                 
442                 public SqliteDataReader ExecuteReader ()
443                 {
444                         return ExecuteReader (CommandBehavior.Default);
445                 }
446                 
447                 public SqliteDataReader ExecuteReader (CommandBehavior behavior)
448                 {
449                         int r;
450                         return ExecuteReader (behavior, true, out r);
451                 }
452                 
453                 public SqliteDataReader ExecuteReader (CommandBehavior behavior, bool want_results, out int rows_affected)
454                 {
455                         Prepare ();
456                         
457                         // The SQL string may contain multiple sql commands, so the main
458                         // thing to do is have Sqlite iterate through the commands.
459                         // If want_results, only the last command is returned as a
460                         // DataReader.  Otherwise, no command is returned as a
461                         // DataReader.
462                 
463                         IntPtr psql; // pointer to SQL command
464                         
465                         // Sqlite 2 docs say this: By default, SQLite assumes that all data uses a fixed-size 8-bit 
466                         // character (iso8859).  But if you give the --enable-utf8 option to the configure script, then the 
467                         // library assumes UTF-8 variable sized characters. This makes a difference for the LIKE and GLOB 
468                         // operators and the LENGTH() and SUBSTR() functions. The static string sqlite_encoding will be set 
469                         // to either "UTF-8" or "iso8859" to indicate how the library was compiled. In addition, the sqlite.h 
470                         // header file will define one of the macros SQLITE_UTF8 or SQLITE_ISO8859, as appropriate.
471                         // 
472                         // We have no way of knowing whether Sqlite 2 expects ISO8859 or UTF-8, but ISO8859 seems to be the
473                         // default.  Therefore, we need to use an ISO8859(-1) compatible encoding, like ANSI.
474                         // OTOH, the user may want to specify the encoding of the bytes stored in the database, regardless
475                         // of what Sqlite is treating them as, 
476                         
477                         // For Sqlite 3, we use the UTF-16 prepare function, so we need a UTF-16 string.
478                         
479                         if (parent_conn.Version == 2)
480                                 psql = Sqlite.StringToHeap (sql.Trim(), parent_conn.Encoding);
481                         else
482                                 psql = Marshal.StringToHGlobalUni (sql.Trim());
483
484                         IntPtr pzTail = psql;
485                         IntPtr errMsgPtr;
486                         
487                         parent_conn.StartExec ();
488                         
489                         rows_affected = 0;
490                         
491                         try {
492                                 while (true) {
493                                         IntPtr pStmt;
494                                          
495                                         GetNextStatement(pzTail, out pzTail, out pStmt);
496                                         
497                                         if (pStmt == IntPtr.Zero)
498                                                 throw new Exception();
499                                         
500                                         // pzTail is positioned after the last byte in the
501                                         // statement, which will be the NULL character if
502                                         // this was the last statement.
503                                         bool last = Marshal.ReadByte(pzTail) == 0;
504
505                                         try {
506                                                 if (parent_conn.Version == 3)
507                                                         BindParameters3 (pStmt);
508                                                 
509                                                 if (last && want_results)
510                                                         return new SqliteDataReader (this, pStmt, parent_conn.Version);
511
512                                                 ExecuteStatement(pStmt);
513                                                 
514                                                 if (last) // rows_affected is only used if !want_results
515                                                         rows_affected = NumChanges ();
516                                                 
517                                         } finally {
518                                                 if (parent_conn.Version == 3) 
519                                                         Sqlite.sqlite3_finalize (pStmt);
520                                                 else
521                                                         Sqlite.sqlite_finalize (pStmt, out errMsgPtr);
522                                         }
523                                         
524                                         if (last) break;
525                                 }
526
527                                 return null;
528                         } finally {
529                                 parent_conn.EndExec ();
530                                 Marshal.FreeHGlobal (psql);
531                         }
532                 }
533
534                 public int LastInsertRowID () 
535                 {
536                         return parent_conn.LastInsertRowId;
537                 }
538                 
539                 private string GetError3() {
540                         return Marshal.PtrToStringUni (Sqlite.sqlite3_errmsg16 (parent_conn.Handle));
541                 }
542         #endregion
543         }
544 }