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