Merge pull request #1138 from akoeplinger/fix-webtest-deadlocks
[mono.git] / mcs / class / Mono.Data.Sqlite / Mono.Data.Sqlite_2.0 / SQLite3.cs
1 /********************************************************\r
2  * ADO.NET 2.0 Data Provider for SQLite Version 3.X\r
3  * Written by Robert Simpson (robert@blackcastlesoft.com)\r
4  * \r
5  * Released to the public domain, use at your own risk!\r
6  ********************************************************/\r
7 \r
8 namespace Mono.Data.Sqlite\r
9 {\r
10   using System;\r
11   using System.Data;\r
12   using System.Runtime.InteropServices;\r
13   using System.Collections.Generic;\r
14   using System.Globalization;\r
15 \r
16   /// <summary>\r
17   /// This class implements SQLiteBase completely, and is the guts of the code that interop's SQLite with .NET\r
18   /// </summary>\r
19   internal class SQLite3 : SQLiteBase\r
20   {\r
21     /// <summary>\r
22     /// The opaque pointer returned to us by the sqlite provider\r
23     /// </summary>\r
24     protected SqliteConnectionHandle _sql;\r
25     protected string _fileName;\r
26     protected bool _usePool;\r
27     protected int _poolVersion = 0;\r
28 \r
29 #if !PLATFORM_COMPACTFRAMEWORK\r
30     private bool _buildingSchema = false;\r
31 #endif\r
32 #if MONOTOUCH\r
33     GCHandle gch;\r
34 #endif\r
35     /// <summary>\r
36     /// The user-defined functions registered on this connection\r
37     /// </summary>\r
38     protected SqliteFunction[] _functionsArray;\r
39 \r
40     internal SQLite3(SQLiteDateFormats fmt)\r
41       : base(fmt)\r
42     {\r
43 #if MONOTOUCH\r
44       gch = GCHandle.Alloc (this);\r
45 #endif\r
46     }\r
47 \r
48     protected override void Dispose(bool bDisposing)\r
49     {\r
50       if (bDisposing)\r
51         Close();\r
52     }\r
53 \r
54     // It isn't necessary to cleanup any functions we've registered.  If the connection\r
55     // goes to the pool and is resurrected later, re-registered functions will overwrite the\r
56     // previous functions.  The SqliteFunctionCookieHandle will take care of freeing unmanaged\r
57     // resources belonging to the previously-registered functions.\r
58     internal override void Close()\r
59     {\r
60       if (_sql != null)\r
61       {\r
62         if (_usePool)\r
63         {\r
64           SQLiteBase.ResetConnection(_sql);\r
65           SqliteConnectionPool.Add(_fileName, _sql, _poolVersion);\r
66         }\r
67         else\r
68           _sql.Dispose();\r
69       }\r
70 \r
71       _sql = null;\r
72 #if MONOTOUCH\r
73       if (gch.IsAllocated)\r
74         gch.Free ();\r
75 #endif\r
76     }\r
77 \r
78     internal override void Cancel()\r
79     {\r
80       UnsafeNativeMethods.sqlite3_interrupt(_sql);\r
81     }\r
82 \r
83     internal override string Version\r
84     {\r
85       get\r
86       {\r
87         return SQLite3.SQLiteVersion;\r
88       }\r
89     }\r
90 \r
91     internal static string SQLiteVersion\r
92     {\r
93       get\r
94       {\r
95         return UTF8ToString(UnsafeNativeMethods.sqlite3_libversion(), -1);\r
96       }\r
97     }\r
98 \r
99     internal override int Changes\r
100     {\r
101       get\r
102       {\r
103         return UnsafeNativeMethods.sqlite3_changes(_sql);\r
104       }\r
105     }\r
106 \r
107     internal override void Open(string strFilename, SQLiteOpenFlagsEnum flags, int maxPoolSize, bool usePool)\r
108     {\r
109       if (_sql != null) return;\r
110 \r
111       _usePool = usePool;\r
112       if (usePool)\r
113       {\r
114         _fileName = strFilename;\r
115         _sql = SqliteConnectionPool.Remove(strFilename, maxPoolSize, out _poolVersion);\r
116       }\r
117 \r
118       if (_sql == null)\r
119       {\r
120         IntPtr db;\r
121 \r
122 #if !SQLITE_STANDARD\r
123         int n = UnsafeNativeMethods.sqlite3_open_interop(ToUTF8(strFilename), (int)flags, out db);\r
124 #else\r
125         // Compatibility with versions < 3.5.0\r
126         int n;\r
127 \r
128         try {\r
129                 n = UnsafeNativeMethods.sqlite3_open_v2(ToUTF8(strFilename), out db, (int)flags, IntPtr.Zero);\r
130         } catch (EntryPointNotFoundException) {\r
131                 Console.WriteLine ("Your sqlite3 version is old - please upgrade to at least v3.5.0!");\r
132                 n = UnsafeNativeMethods.sqlite3_open (ToUTF8 (strFilename), out db);\r
133         }\r
134         \r
135 #endif\r
136         if (n > 0) throw new SqliteException(n, null);\r
137 \r
138         _sql = db;\r
139       }\r
140       // Bind functions to this connection.  If any previous functions of the same name\r
141       // were already bound, then the new bindings replace the old.\r
142       _functionsArray = SqliteFunction.BindFunctions(this);\r
143       SetTimeout(0);\r
144     }\r
145 \r
146     internal override void ClearPool()\r
147     {\r
148       SqliteConnectionPool.ClearPool(_fileName);\r
149     }\r
150 \r
151     internal override void SetTimeout(int nTimeoutMS)\r
152     {\r
153       int n = UnsafeNativeMethods.sqlite3_busy_timeout(_sql, nTimeoutMS);\r
154       if (n > 0) throw new SqliteException(n, SQLiteLastError());\r
155     }\r
156 \r
157     internal override bool Step(SqliteStatement stmt)\r
158     {\r
159       int n;\r
160       Random rnd = null;\r
161       uint starttick = (uint)Environment.TickCount;\r
162       uint timeout = (uint)(stmt._command._commandTimeout * 1000);\r
163 \r
164       while (true)\r
165       {\r
166         n = UnsafeNativeMethods.sqlite3_step(stmt._sqlite_stmt);\r
167 \r
168         if (n == 100) return true;\r
169         if (n == 101) return false;\r
170 \r
171         if (n > 0)\r
172         {\r
173           int r;\r
174 \r
175           // An error occurred, attempt to reset the statement.  If the reset worked because the\r
176           // schema has changed, re-try the step again.  If it errored our because the database\r
177           // is locked, then keep retrying until the command timeout occurs.\r
178           r = Reset(stmt);\r
179 \r
180           if (r == 0)\r
181             throw new SqliteException(n, SQLiteLastError());\r
182 \r
183           else if ((r == 6 || r == 5) && stmt._command != null) // SQLITE_LOCKED || SQLITE_BUSY\r
184           {\r
185             // Keep trying\r
186             if (rnd == null) // First time we've encountered the lock\r
187               rnd = new Random();\r
188 \r
189             // If we've exceeded the command's timeout, give up and throw an error\r
190             if ((uint)Environment.TickCount - starttick > timeout)\r
191             {\r
192               throw new SqliteException(r, SQLiteLastError());\r
193             }\r
194             else\r
195             {\r
196               // Otherwise sleep for a random amount of time up to 150ms\r
197               System.Threading.Thread.CurrentThread.Join(rnd.Next(1, 150));\r
198             }\r
199           }\r
200         }\r
201       }\r
202     }\r
203 \r
204     internal override int Reset(SqliteStatement stmt)\r
205     {\r
206       int n;\r
207 \r
208 #if !SQLITE_STANDARD\r
209       n = UnsafeNativeMethods.sqlite3_reset_interop(stmt._sqlite_stmt);\r
210 #else\r
211       n = UnsafeNativeMethods.sqlite3_reset(stmt._sqlite_stmt);\r
212 #endif\r
213 \r
214       // If the schema changed, try and re-prepare it\r
215       if (n == 17) // SQLITE_SCHEMA\r
216       {\r
217         // Recreate a dummy statement\r
218         string str;\r
219         using (SqliteStatement tmp = Prepare(null, stmt._sqlStatement, null, (uint)(stmt._command._commandTimeout * 1000), out str))\r
220         {\r
221           // Finalize the existing statement\r
222           stmt._sqlite_stmt.Dispose();\r
223           // Reassign a new statement pointer to the old statement and clear the temporary one\r
224           stmt._sqlite_stmt = tmp._sqlite_stmt;\r
225           tmp._sqlite_stmt = null;\r
226 \r
227           // Reapply parameters\r
228           stmt.BindParameters();\r
229         }\r
230         return -1; // Reset was OK, with schema change\r
231       }\r
232       else if (n == 6 || n == 5) // SQLITE_LOCKED || SQLITE_BUSY\r
233         return n;\r
234 \r
235       if (n > 0)\r
236         throw new SqliteException(n, SQLiteLastError());\r
237 \r
238       return 0; // We reset OK, no schema changes\r
239     }\r
240 \r
241     internal override string SQLiteLastError()\r
242     {\r
243       return SQLiteBase.SQLiteLastError(_sql);\r
244     }\r
245 \r
246     internal override SqliteStatement Prepare(SqliteConnection cnn, string strSql, SqliteStatement previous, uint timeoutMS, out string strRemain)\r
247     {\r
248       IntPtr stmt = IntPtr.Zero;\r
249       IntPtr ptr = IntPtr.Zero;\r
250       int len = 0;\r
251       int n = 17;\r
252       int retries = 0;\r
253       byte[] b = ToUTF8(strSql);\r
254       string typedefs = null;\r
255       SqliteStatement cmd = null;\r
256       Random rnd = null;\r
257       uint starttick = (uint)Environment.TickCount;\r
258 \r
259       GCHandle handle = GCHandle.Alloc(b, GCHandleType.Pinned);\r
260       IntPtr psql = handle.AddrOfPinnedObject();\r
261       try\r
262       {\r
263         while ((n == 17 || n == 6 || n == 5) && retries < 3)\r
264         {\r
265 #if !SQLITE_STANDARD\r
266           n = UnsafeNativeMethods.sqlite3_prepare_interop(_sql, psql, b.Length - 1, out stmt, out ptr, out len);\r
267 #else\r
268           n = UnsafeNativeMethods.sqlite3_prepare(_sql, psql, b.Length - 1, out stmt, out ptr);\r
269           len = -1;\r
270 #endif\r
271 \r
272           if (n == 17)\r
273             retries++;\r
274           else if (n == 1)\r
275           {\r
276             if (String.Compare(SQLiteLastError(), "near \"TYPES\": syntax error", StringComparison.OrdinalIgnoreCase) == 0)\r
277             {\r
278               int pos = strSql.IndexOf(';');\r
279               if (pos == -1) pos = strSql.Length - 1;\r
280 \r
281               typedefs = strSql.Substring(0, pos + 1);\r
282               strSql = strSql.Substring(pos + 1);\r
283 \r
284               strRemain = "";\r
285 \r
286               while (cmd == null && strSql.Length > 0)\r
287               {\r
288                 cmd = Prepare(cnn, strSql, previous, timeoutMS, out strRemain);\r
289                 strSql = strRemain;\r
290               }\r
291 \r
292               if (cmd != null)\r
293                 cmd.SetTypes(typedefs);\r
294 \r
295               return cmd;\r
296             }\r
297 #if !PLATFORM_COMPACTFRAMEWORK\r
298             else if (_buildingSchema == false && String.Compare(SQLiteLastError(), 0, "no such table: TEMP.SCHEMA", 0, 26, StringComparison.OrdinalIgnoreCase) == 0)\r
299             {\r
300               strRemain = "";\r
301               _buildingSchema = true;\r
302               try\r
303               {\r
304                 ISQLiteSchemaExtensions ext = ((IServiceProvider)SqliteFactory.Instance).GetService(typeof(ISQLiteSchemaExtensions)) as ISQLiteSchemaExtensions;\r
305 \r
306                 if (ext != null)\r
307                   ext.BuildTempSchema(cnn);\r
308 \r
309                 while (cmd == null && strSql.Length > 0)\r
310                 {\r
311                   cmd = Prepare(cnn, strSql, previous, timeoutMS, out strRemain);\r
312                   strSql = strRemain;\r
313                 }\r
314 \r
315                 return cmd;\r
316               }\r
317               finally\r
318               {\r
319                 _buildingSchema = false;\r
320               }\r
321             }\r
322 #endif\r
323           }\r
324           else if (n == 6 || n == 5) // Locked -- delay a small amount before retrying\r
325           {\r
326             // Keep trying\r
327             if (rnd == null) // First time we've encountered the lock\r
328               rnd = new Random();\r
329 \r
330             // If we've exceeded the command's timeout, give up and throw an error\r
331             if ((uint)Environment.TickCount - starttick > timeoutMS)\r
332             {\r
333               throw new SqliteException(n, SQLiteLastError());\r
334             }\r
335             else\r
336             {\r
337               // Otherwise sleep for a random amount of time up to 150ms\r
338               System.Threading.Thread.CurrentThread.Join(rnd.Next(1, 150));\r
339             }\r
340           }\r
341         }\r
342 \r
343         if (n > 0) throw new SqliteException(n, SQLiteLastError());\r
344 \r
345         strRemain = UTF8ToString(ptr, len);\r
346 \r
347         if (stmt != IntPtr.Zero) cmd = new SqliteStatement(this, stmt, strSql.Substring(0, strSql.Length - strRemain.Length), previous);\r
348 \r
349         return cmd;\r
350       }\r
351       finally\r
352       {\r
353         handle.Free();\r
354       }\r
355     }\r
356 \r
357     internal override void Bind_Double(SqliteStatement stmt, int index, double value)\r
358     {\r
359 #if !PLATFORM_COMPACTFRAMEWORK\r
360       int n = UnsafeNativeMethods.sqlite3_bind_double(stmt._sqlite_stmt, index, value);\r
361 #else\r
362       int n = UnsafeNativeMethods.sqlite3_bind_double_interop(stmt._sqlite_stmt, index, ref value);\r
363 #endif\r
364       if (n > 0) throw new SqliteException(n, SQLiteLastError());\r
365     }\r
366 \r
367     internal override void Bind_Int32(SqliteStatement stmt, int index, int value)\r
368     {\r
369       int n = UnsafeNativeMethods.sqlite3_bind_int(stmt._sqlite_stmt, index, value);\r
370       if (n > 0) throw new SqliteException(n, SQLiteLastError());\r
371     }\r
372 \r
373     internal override void Bind_Int64(SqliteStatement stmt, int index, long value)\r
374     {\r
375 #if !PLATFORM_COMPACTFRAMEWORK\r
376       int n = UnsafeNativeMethods.sqlite3_bind_int64(stmt._sqlite_stmt, index, value);\r
377 #else\r
378       int n = UnsafeNativeMethods.sqlite3_bind_int64_interop(stmt._sqlite_stmt, index, ref value);\r
379 #endif\r
380       if (n > 0) throw new SqliteException(n, SQLiteLastError());\r
381     }\r
382 \r
383     internal override void Bind_Text(SqliteStatement stmt, int index, string value)\r
384     {\r
385       byte[] b = ToUTF8(value);\r
386       int n = UnsafeNativeMethods.sqlite3_bind_text(stmt._sqlite_stmt, index, b, b.Length - 1, (IntPtr)(-1));\r
387       if (n > 0) throw new SqliteException(n, SQLiteLastError());\r
388     }\r
389 \r
390     internal override void Bind_DateTime(SqliteStatement stmt, int index, DateTime dt)\r
391     {\r
392       byte[] b = ToUTF8(dt);\r
393       int n = UnsafeNativeMethods.sqlite3_bind_text(stmt._sqlite_stmt, index, b, b.Length - 1, (IntPtr)(-1));\r
394       if (n > 0) throw new SqliteException(n, SQLiteLastError());\r
395     }\r
396 \r
397     internal override void Bind_Blob(SqliteStatement stmt, int index, byte[] blobData)\r
398     {\r
399       int n = UnsafeNativeMethods.sqlite3_bind_blob(stmt._sqlite_stmt, index, blobData, blobData.Length, (IntPtr)(-1));\r
400       if (n > 0) throw new SqliteException(n, SQLiteLastError());\r
401     }\r
402 \r
403     internal override void Bind_Null(SqliteStatement stmt, int index)\r
404     {\r
405       int n = UnsafeNativeMethods.sqlite3_bind_null(stmt._sqlite_stmt, index);\r
406       if (n > 0) throw new SqliteException(n, SQLiteLastError());\r
407     }\r
408 \r
409     internal override int Bind_ParamCount(SqliteStatement stmt)\r
410     {\r
411       return UnsafeNativeMethods.sqlite3_bind_parameter_count(stmt._sqlite_stmt);\r
412     }\r
413 \r
414     internal override string Bind_ParamName(SqliteStatement stmt, int index)\r
415     {\r
416 #if !SQLITE_STANDARD\r
417       int len;\r
418       return UTF8ToString(UnsafeNativeMethods.sqlite3_bind_parameter_name_interop(stmt._sqlite_stmt, index, out len), len);\r
419 #else\r
420       return UTF8ToString(UnsafeNativeMethods.sqlite3_bind_parameter_name(stmt._sqlite_stmt, index), -1);\r
421 #endif\r
422     }\r
423 \r
424     internal override int Bind_ParamIndex(SqliteStatement stmt, string paramName)\r
425     {\r
426       return UnsafeNativeMethods.sqlite3_bind_parameter_index(stmt._sqlite_stmt, ToUTF8(paramName));\r
427     }\r
428 \r
429     internal override int ColumnCount(SqliteStatement stmt)\r
430     {\r
431       return UnsafeNativeMethods.sqlite3_column_count(stmt._sqlite_stmt);\r
432     }\r
433 \r
434     internal override string ColumnName(SqliteStatement stmt, int index)\r
435     {\r
436 #if !SQLITE_STANDARD\r
437       int len;\r
438       return UTF8ToString(UnsafeNativeMethods.sqlite3_column_name_interop(stmt._sqlite_stmt, index, out len), len);\r
439 #else\r
440       return UTF8ToString(UnsafeNativeMethods.sqlite3_column_name(stmt._sqlite_stmt, index), -1);\r
441 #endif\r
442     }\r
443 \r
444     internal override TypeAffinity ColumnAffinity(SqliteStatement stmt, int index)\r
445     {\r
446       return UnsafeNativeMethods.sqlite3_column_type(stmt._sqlite_stmt, index);\r
447     }\r
448 \r
449     internal override string ColumnType(SqliteStatement stmt, int index, out TypeAffinity nAffinity)\r
450     {\r
451       int len;\r
452 #if !SQLITE_STANDARD\r
453       IntPtr p = UnsafeNativeMethods.sqlite3_column_decltype_interop(stmt._sqlite_stmt, index, out len);\r
454 #else\r
455       len = -1;\r
456       IntPtr p = UnsafeNativeMethods.sqlite3_column_decltype(stmt._sqlite_stmt, index);\r
457 #endif\r
458       nAffinity = ColumnAffinity(stmt, index);\r
459 \r
460       if (p != IntPtr.Zero) return UTF8ToString(p, len);\r
461       else\r
462       {\r
463         string[] ar = stmt.TypeDefinitions;\r
464         if (ar != null)\r
465         {\r
466           if (index < ar.Length && ar[index] != null)\r
467             return ar[index];\r
468         }\r
469         return String.Empty;\r
470 \r
471         //switch (nAffinity)\r
472         //{\r
473         //  case TypeAffinity.Int64:\r
474         //    return "BIGINT";\r
475         //  case TypeAffinity.Double:\r
476         //    return "DOUBLE";\r
477         //  case TypeAffinity.Blob:\r
478         //    return "BLOB";\r
479         //  default:\r
480         //    return "TEXT";\r
481         //}\r
482       }\r
483     }\r
484 \r
485     internal override int ColumnIndex(SqliteStatement stmt, string columnName)\r
486     {\r
487       int x = ColumnCount(stmt);\r
488 \r
489       for (int n = 0; n < x; n++)\r
490       {\r
491         if (String.Compare(columnName, ColumnName(stmt, n), true, CultureInfo.InvariantCulture) == 0)\r
492           return n;\r
493       }\r
494       return -1;\r
495     }\r
496 \r
497     internal override string ColumnOriginalName(SqliteStatement stmt, int index)\r
498     {\r
499 #if !SQLITE_STANDARD\r
500       int len;\r
501       return UTF8ToString(UnsafeNativeMethods.sqlite3_column_origin_name_interop(stmt._sqlite_stmt, index, out len), len);\r
502 #else\r
503       return UTF8ToString(UnsafeNativeMethods.sqlite3_column_origin_name(stmt._sqlite_stmt, index), -1);\r
504 #endif\r
505     }\r
506 \r
507     internal override string ColumnDatabaseName(SqliteStatement stmt, int index)\r
508     {\r
509 #if !SQLITE_STANDARD\r
510       int len;\r
511       return UTF8ToString(UnsafeNativeMethods.sqlite3_column_database_name_interop(stmt._sqlite_stmt, index, out len), len);\r
512 #else\r
513       return UTF8ToString(UnsafeNativeMethods.sqlite3_column_database_name(stmt._sqlite_stmt, index), -1);\r
514 #endif\r
515     }\r
516 \r
517     internal override string ColumnTableName(SqliteStatement stmt, int index)\r
518     {\r
519 #if !SQLITE_STANDARD\r
520       int len;\r
521       return UTF8ToString(UnsafeNativeMethods.sqlite3_column_table_name_interop(stmt._sqlite_stmt, index, out len), len);\r
522 #else\r
523       return UTF8ToString(UnsafeNativeMethods.sqlite3_column_table_name(stmt._sqlite_stmt, index), -1);\r
524 #endif\r
525     }\r
526 \r
527     internal override void ColumnMetaData(string dataBase, string table, string column, out string dataType, out string collateSequence, out bool notNull, out bool primaryKey, out bool autoIncrement)\r
528     {\r
529       IntPtr dataTypePtr;\r
530       IntPtr collSeqPtr;\r
531       int nnotNull;\r
532       int nprimaryKey;\r
533       int nautoInc;\r
534       int n;\r
535       int dtLen;\r
536       int csLen;\r
537 \r
538 #if !SQLITE_STANDARD\r
539       n = UnsafeNativeMethods.sqlite3_table_column_metadata_interop(_sql, ToUTF8(dataBase), ToUTF8(table), ToUTF8(column), out dataTypePtr, out collSeqPtr, out nnotNull, out nprimaryKey, out nautoInc, out dtLen, out csLen);\r
540 #else\r
541       dtLen = -1;\r
542       csLen = -1;\r
543       n = UnsafeNativeMethods.sqlite3_table_column_metadata(_sql, ToUTF8(dataBase), ToUTF8(table), ToUTF8(column), out dataTypePtr, out collSeqPtr, out nnotNull, out nprimaryKey, out nautoInc);\r
544 #endif\r
545       if (n > 0) throw new SqliteException(n, SQLiteLastError());\r
546 \r
547       dataType = UTF8ToString(dataTypePtr, dtLen);\r
548       collateSequence = UTF8ToString(collSeqPtr, csLen);\r
549 \r
550       notNull = (nnotNull == 1);\r
551       primaryKey = (nprimaryKey == 1);\r
552       autoIncrement = (nautoInc == 1);\r
553     }\r
554 \r
555     internal override double GetDouble(SqliteStatement stmt, int index)\r
556     {\r
557       double value;\r
558 #if !PLATFORM_COMPACTFRAMEWORK\r
559       value = UnsafeNativeMethods.sqlite3_column_double(stmt._sqlite_stmt, index);\r
560 #else\r
561       UnsafeNativeMethods.sqlite3_column_double_interop(stmt._sqlite_stmt, index, out value);\r
562 #endif\r
563       return value;\r
564     }\r
565 \r
566     internal override int GetInt32(SqliteStatement stmt, int index)\r
567     {\r
568       return UnsafeNativeMethods.sqlite3_column_int(stmt._sqlite_stmt, index);\r
569     }\r
570 \r
571     internal override long GetInt64(SqliteStatement stmt, int index)\r
572     {\r
573       long value;\r
574 #if !PLATFORM_COMPACTFRAMEWORK\r
575       value = UnsafeNativeMethods.sqlite3_column_int64(stmt._sqlite_stmt, index);\r
576 #else\r
577       UnsafeNativeMethods.sqlite3_column_int64_interop(stmt._sqlite_stmt, index, out value);\r
578 #endif\r
579       return value;\r
580     }\r
581 \r
582     internal override string GetText(SqliteStatement stmt, int index)\r
583     {\r
584 #if !SQLITE_STANDARD\r
585       int len;\r
586       return UTF8ToString(UnsafeNativeMethods.sqlite3_column_text_interop(stmt._sqlite_stmt, index, out len), len);\r
587 #else\r
588       return UTF8ToString(UnsafeNativeMethods.sqlite3_column_text(stmt._sqlite_stmt, index), -1);\r
589 #endif\r
590     }\r
591 \r
592     internal override DateTime GetDateTime(SqliteStatement stmt, int index)\r
593     {\r
594 #if !SQLITE_STANDARD\r
595       int len;\r
596       return ToDateTime(UnsafeNativeMethods.sqlite3_column_text_interop(stmt._sqlite_stmt, index, out len), len);\r
597 #else\r
598       return ToDateTime(UnsafeNativeMethods.sqlite3_column_text(stmt._sqlite_stmt, index), -1);\r
599 #endif\r
600     }\r
601 \r
602     internal override long GetBytes(SqliteStatement stmt, int index, int nDataOffset, byte[] bDest, int nStart, int nLength)\r
603     {\r
604       IntPtr ptr;\r
605       int nlen;\r
606       int nCopied = nLength;\r
607 \r
608       nlen = UnsafeNativeMethods.sqlite3_column_bytes(stmt._sqlite_stmt, index);\r
609       ptr = UnsafeNativeMethods.sqlite3_column_blob(stmt._sqlite_stmt, index);\r
610 \r
611       if (bDest == null) return nlen;\r
612 \r
613       if (nCopied + nStart > bDest.Length) nCopied = bDest.Length - nStart;\r
614       if (nCopied + nDataOffset > nlen) nCopied = nlen - nDataOffset;\r
615 \r
616       unsafe {\r
617               if (nCopied > 0)\r
618                       Marshal.Copy((IntPtr)((byte*)ptr + nDataOffset), bDest, nStart, nCopied);\r
619               else nCopied = 0;\r
620       }\r
621 \r
622       return nCopied;\r
623     }\r
624 \r
625     internal override long GetChars(SqliteStatement stmt, int index, int nDataOffset, char[] bDest, int nStart, int nLength)\r
626     {\r
627       int nlen;\r
628       int nCopied = nLength;\r
629 \r
630       string str = GetText(stmt, index);\r
631       nlen = str.Length;\r
632 \r
633       if (bDest == null) return nlen;\r
634 \r
635       if (nCopied + nStart > bDest.Length) nCopied = bDest.Length - nStart;\r
636       if (nCopied + nDataOffset > nlen) nCopied = nlen - nDataOffset;\r
637 \r
638       if (nCopied > 0)\r
639         str.CopyTo(nDataOffset, bDest, nStart, nCopied);\r
640       else nCopied = 0;\r
641 \r
642       return nCopied;\r
643     }\r
644 \r
645     internal override bool IsNull(SqliteStatement stmt, int index)\r
646     {\r
647       return (ColumnAffinity(stmt, index) == TypeAffinity.Null);\r
648     }\r
649 \r
650     internal override int AggregateCount(IntPtr context)\r
651     {\r
652       return UnsafeNativeMethods.sqlite3_aggregate_count(context);\r
653     }\r
654 \r
655 #if MONOTOUCH\r
656     class FunctionData {\r
657       public SQLiteCallback Func;\r
658       public SQLiteCallback FuncStep;\r
659       public SQLiteFinalCallback FuncFinal;\r
660     }\r
661 #endif\r
662 \r
663     internal override void CreateFunction(string strFunction, int nArgs, bool needCollSeq, SQLiteCallback func, SQLiteCallback funcstep, SQLiteFinalCallback funcfinal)\r
664     {\r
665       int n;\r
666 \r
667 #if MONOTOUCH\r
668       var data = new FunctionData();\r
669       data.Func = func;\r
670       data.FuncStep = funcstep;\r
671       data.FuncFinal = funcfinal;\r
672       SQLiteCallback func_callback = func == null ? null : new SQLiteCallback(scalar_callback);\r
673       SQLiteCallback funcstep_callback = funcstep == null ? null : new SQLiteCallback(step_callback);\r
674       SQLiteFinalCallback funcfinal_callback = funcfinal == null ? null : new SQLiteFinalCallback(final_callback);\r
675 \r
676       IntPtr user_data;\r
677       user_data = GCHandle.ToIntPtr(GCHandle.Alloc(data));\r
678       n = UnsafeNativeMethods.sqlite3_create_function_v2(_sql, ToUTF8(strFunction), nArgs, 4, user_data, func_callback, funcstep_callback, funcfinal_callback, destroy_callback);\r
679 \r
680       if (n == 0) {\r
681         // sqlite3_create_function_v2 will call 'destroy_callback' if it fails, so we need to recreate the gchandle here.\r
682         user_data = GCHandle.ToIntPtr(GCHandle.Alloc(data));\r
683         n = UnsafeNativeMethods.sqlite3_create_function_v2(_sql, ToUTF8(strFunction), nArgs, 1, user_data, func_callback, funcstep_callback, funcfinal_callback, destroy_callback);\r
684       }\r
685 #elif !SQLITE_STANDARD\r
686       n = UnsafeNativeMethods.sqlite3_create_function_interop(_sql, ToUTF8(strFunction), nArgs, 4, IntPtr.Zero, func, funcstep, funcfinal, (needCollSeq == true) ? 1 : 0);\r
687       if (n == 0) n = UnsafeNativeMethods.sqlite3_create_function_interop(_sql, ToUTF8(strFunction), nArgs, 1, IntPtr.Zero, func, funcstep, funcfinal, (needCollSeq == true) ? 1 : 0);\r
688 #else\r
689       n = UnsafeNativeMethods.sqlite3_create_function(_sql, ToUTF8(strFunction), nArgs, 4, IntPtr.Zero, func, funcstep, funcfinal);\r
690       if (n == 0) n = UnsafeNativeMethods.sqlite3_create_function(_sql, ToUTF8(strFunction), nArgs, 1, IntPtr.Zero, func, funcstep, funcfinal);\r
691 #endif\r
692       if (n > 0) throw new SqliteException(n, SQLiteLastError());\r
693     }\r
694 \r
695     internal override void CreateCollation(string strCollation, SQLiteCollation func, SQLiteCollation func16, IntPtr user_data)\r
696     {\r
697       int n = UnsafeNativeMethods.sqlite3_create_collation(_sql, ToUTF8(strCollation), 2, user_data, func16);\r
698       if (n == 0) UnsafeNativeMethods.sqlite3_create_collation(_sql, ToUTF8(strCollation), 1, user_data, func);\r
699       if (n > 0) throw new SqliteException(n, SQLiteLastError());\r
700     }\r
701 \r
702 #if MONOTOUCH\r
703     [MonoTouch.MonoPInvokeCallback(typeof(SQLiteCallback))]\r
704     internal static void scalar_callback(IntPtr context, int nArgs, IntPtr argsptr)\r
705     {\r
706       var handle = GCHandle.FromIntPtr (UnsafeNativeMethods.sqlite3_user_data(context));\r
707       var func = (FunctionData)handle.Target;\r
708       func.Func(context, nArgs, argsptr);\r
709     }\r
710 \r
711     [MonoTouch.MonoPInvokeCallback(typeof(SQLiteCallback))]\r
712     internal static void step_callback(IntPtr context, int nArgs, IntPtr argsptr)\r
713     {\r
714       var handle = GCHandle.FromIntPtr(UnsafeNativeMethods.sqlite3_user_data(context));\r
715       var func = (FunctionData)handle.Target;\r
716       func.FuncStep(context, nArgs, argsptr);\r
717     }\r
718 \r
719     [MonoTouch.MonoPInvokeCallback(typeof(SQLiteFinalCallback))]\r
720     internal static void final_callback(IntPtr context)\r
721     {\r
722       var handle = GCHandle.FromIntPtr(UnsafeNativeMethods.sqlite3_user_data(context));\r
723       var func = (FunctionData)handle.Target;\r
724       func.FuncFinal(context);\r
725     }\r
726 \r
727     [MonoTouch.MonoPInvokeCallback(typeof(SQLiteFinalCallback))]\r
728     internal static void destroy_callback(IntPtr context)\r
729     {\r
730       GCHandle.FromIntPtr(context).Free();\r
731     }\r
732 #endif\r
733 \r
734     internal override int ContextCollateCompare(CollationEncodingEnum enc, IntPtr context, string s1, string s2)\r
735     {\r
736 #if !SQLITE_STANDARD\r
737       byte[] b1;\r
738       byte[] b2;\r
739       System.Text.Encoding converter = null;\r
740 \r
741       switch (enc)\r
742       {\r
743         case CollationEncodingEnum.UTF8:\r
744           converter = System.Text.Encoding.UTF8;\r
745           break;\r
746         case CollationEncodingEnum.UTF16LE:\r
747           converter = System.Text.Encoding.Unicode;\r
748           break;\r
749         case CollationEncodingEnum.UTF16BE:\r
750           converter = System.Text.Encoding.BigEndianUnicode;\r
751           break;\r
752       }\r
753 \r
754       b1 = converter.GetBytes(s1);\r
755       b2 = converter.GetBytes(s2);\r
756 \r
757       return UnsafeNativeMethods.sqlite3_context_collcompare(context, b1, b1.Length, b2, b2.Length);\r
758 #else\r
759       throw new NotImplementedException();\r
760 #endif\r
761     }\r
762 \r
763     internal override int ContextCollateCompare(CollationEncodingEnum enc, IntPtr context, char[] c1, char[] c2)\r
764     {\r
765 #if !SQLITE_STANDARD\r
766       byte[] b1;\r
767       byte[] b2;\r
768       System.Text.Encoding converter = null;\r
769 \r
770       switch (enc)\r
771       {\r
772         case CollationEncodingEnum.UTF8:\r
773           converter = System.Text.Encoding.UTF8;\r
774           break;\r
775         case CollationEncodingEnum.UTF16LE:\r
776           converter = System.Text.Encoding.Unicode;\r
777           break;\r
778         case CollationEncodingEnum.UTF16BE:\r
779           converter = System.Text.Encoding.BigEndianUnicode;\r
780           break;\r
781       }\r
782 \r
783       b1 = converter.GetBytes(c1);\r
784       b2 = converter.GetBytes(c2);\r
785 \r
786       return UnsafeNativeMethods.sqlite3_context_collcompare(context, b1, b1.Length, b2, b2.Length);\r
787 #else\r
788       throw new NotImplementedException();\r
789 #endif\r
790     }\r
791 \r
792     internal override CollationSequence GetCollationSequence(SqliteFunction func, IntPtr context)\r
793     {\r
794 #if !SQLITE_STANDARD\r
795       CollationSequence seq = new CollationSequence();\r
796       int len;\r
797       int type;\r
798       int enc;\r
799       IntPtr p = UnsafeNativeMethods.sqlite3_context_collseq(context, out type, out enc, out len);\r
800 \r
801       if (p != null) seq.Name = UTF8ToString(p, len);\r
802       seq.Type = (CollationTypeEnum)type;\r
803       seq._func = func;\r
804       seq.Encoding = (CollationEncodingEnum)enc;\r
805 \r
806       return seq;\r
807 #else\r
808       throw new NotImplementedException();\r
809 #endif\r
810     }\r
811 \r
812     internal override long GetParamValueBytes(IntPtr p, int nDataOffset, byte[] bDest, int nStart, int nLength)\r
813     {\r
814       IntPtr ptr;\r
815       int nlen;\r
816       int nCopied = nLength;\r
817 \r
818       nlen = UnsafeNativeMethods.sqlite3_value_bytes(p);\r
819       ptr = UnsafeNativeMethods.sqlite3_value_blob(p);\r
820 \r
821       if (bDest == null) return nlen;\r
822 \r
823       if (nCopied + nStart > bDest.Length) nCopied = bDest.Length - nStart;\r
824       if (nCopied + nDataOffset > nlen) nCopied = nlen - nDataOffset;\r
825 \r
826       unsafe {\r
827               if (nCopied > 0)\r
828                       Marshal.Copy((IntPtr)((byte*)ptr + nDataOffset), bDest, nStart, nCopied);\r
829               else nCopied = 0;\r
830       }\r
831 \r
832       return nCopied;\r
833     }\r
834 \r
835     internal override double GetParamValueDouble(IntPtr ptr)\r
836     {\r
837       double value;\r
838 #if !PLATFORM_COMPACTFRAMEWORK\r
839       value = UnsafeNativeMethods.sqlite3_value_double(ptr);\r
840 #else\r
841       UnsafeNativeMethods.sqlite3_value_double_interop(ptr, out value);\r
842 #endif\r
843       return value;\r
844     }\r
845 \r
846     internal override int GetParamValueInt32(IntPtr ptr)\r
847     {\r
848       return UnsafeNativeMethods.sqlite3_value_int(ptr);\r
849     }\r
850 \r
851     internal override long GetParamValueInt64(IntPtr ptr)\r
852     {\r
853       Int64 value;\r
854 #if !PLATFORM_COMPACTFRAMEWORK\r
855       value = UnsafeNativeMethods.sqlite3_value_int64(ptr);\r
856 #else\r
857       UnsafeNativeMethods.sqlite3_value_int64_interop(ptr, out value);\r
858 #endif\r
859       return value;\r
860     }\r
861 \r
862     internal override string GetParamValueText(IntPtr ptr)\r
863     {\r
864 #if !SQLITE_STANDARD\r
865       int len;\r
866       return UTF8ToString(UnsafeNativeMethods.sqlite3_value_text_interop(ptr, out len), len);\r
867 #else\r
868       return UTF8ToString(UnsafeNativeMethods.sqlite3_value_text(ptr), -1);\r
869 #endif\r
870     }\r
871 \r
872     internal override TypeAffinity GetParamValueType(IntPtr ptr)\r
873     {\r
874       return UnsafeNativeMethods.sqlite3_value_type(ptr);\r
875     }\r
876 \r
877     internal override void ReturnBlob(IntPtr context, byte[] value)\r
878     {\r
879       UnsafeNativeMethods.sqlite3_result_blob(context, value, value.Length, (IntPtr)(-1));\r
880     }\r
881 \r
882     internal override void ReturnDouble(IntPtr context, double value)\r
883     {\r
884 #if !PLATFORM_COMPACTFRAMEWORK\r
885       UnsafeNativeMethods.sqlite3_result_double(context, value);\r
886 #else\r
887       UnsafeNativeMethods.sqlite3_result_double_interop(context, ref value);\r
888 #endif\r
889     }\r
890 \r
891     internal override void ReturnError(IntPtr context, string value)\r
892     {\r
893       UnsafeNativeMethods.sqlite3_result_error(context, ToUTF8(value), value.Length);\r
894     }\r
895 \r
896     internal override void ReturnInt32(IntPtr context, int value)\r
897     {\r
898       UnsafeNativeMethods.sqlite3_result_int(context, value);\r
899     }\r
900 \r
901     internal override void ReturnInt64(IntPtr context, long value)\r
902     {\r
903 #if !PLATFORM_COMPACTFRAMEWORK\r
904       UnsafeNativeMethods.sqlite3_result_int64(context, value);\r
905 #else\r
906       UnsafeNativeMethods.sqlite3_result_int64_interop(context, ref value);\r
907 #endif\r
908     }\r
909 \r
910     internal override void ReturnNull(IntPtr context)\r
911     {\r
912       UnsafeNativeMethods.sqlite3_result_null(context);\r
913     }\r
914 \r
915     internal override void ReturnText(IntPtr context, string value)\r
916     {\r
917       byte[] b = ToUTF8(value);\r
918       UnsafeNativeMethods.sqlite3_result_text(context, ToUTF8(value), b.Length - 1, (IntPtr)(-1));\r
919     }\r
920 \r
921     internal override IntPtr AggregateContext(IntPtr context)\r
922     {\r
923       return UnsafeNativeMethods.sqlite3_aggregate_context(context, 1);\r
924     }\r
925 \r
926     internal override void SetPassword(byte[] passwordBytes)\r
927     {\r
928       int n = UnsafeNativeMethods.sqlite3_key(_sql, passwordBytes, passwordBytes.Length);\r
929       if (n > 0) throw new SqliteException(n, SQLiteLastError());\r
930     }\r
931 \r
932     internal override void ChangePassword(byte[] newPasswordBytes)\r
933     {\r
934       int n = UnsafeNativeMethods.sqlite3_rekey(_sql, newPasswordBytes, (newPasswordBytes == null) ? 0 : newPasswordBytes.Length);\r
935       if (n > 0) throw new SqliteException(n, SQLiteLastError());\r
936     }\r
937                 \r
938 #if MONOTOUCH\r
939     SQLiteUpdateCallback update_callback;\r
940     SQLiteCommitCallback commit_callback;\r
941     SQLiteRollbackCallback rollback_callback;\r
942                 \r
943     [MonoTouch.MonoPInvokeCallback (typeof (SQLiteUpdateCallback))]\r
944     static void update (IntPtr puser, int type, IntPtr database, IntPtr table, Int64 rowid)\r
945     {\r
946       SQLite3 instance = GCHandle.FromIntPtr (puser).Target as SQLite3;\r
947       instance.update_callback (puser, type, database, table, rowid);\r
948     }\r
949                         \r
950     internal override void SetUpdateHook (SQLiteUpdateCallback func)\r
951     {\r
952       update_callback = func;\r
953       if (func == null)\r
954         UnsafeNativeMethods.sqlite3_update_hook (_sql, null, IntPtr.Zero);\r
955       else\r
956         UnsafeNativeMethods.sqlite3_update_hook (_sql, update, GCHandle.ToIntPtr (gch));\r
957     }\r
958 \r
959     [MonoTouch.MonoPInvokeCallback (typeof (SQLiteCommitCallback))]\r
960     static int commit (IntPtr puser)\r
961     {\r
962       SQLite3 instance = GCHandle.FromIntPtr (puser).Target as SQLite3;\r
963       return instance.commit_callback (puser);\r
964     }\r
965                 \r
966     internal override void SetCommitHook (SQLiteCommitCallback func)\r
967     {\r
968       commit_callback = func;\r
969       if (func == null)\r
970         UnsafeNativeMethods.sqlite3_commit_hook (_sql, null, IntPtr.Zero);\r
971       else\r
972         UnsafeNativeMethods.sqlite3_commit_hook (_sql, commit, GCHandle.ToIntPtr (gch));\r
973     }\r
974 \r
975     [MonoTouch.MonoPInvokeCallback (typeof (SQLiteRollbackCallback))]\r
976     static void rollback (IntPtr puser)\r
977     {\r
978       SQLite3 instance = GCHandle.FromIntPtr (puser).Target as SQLite3;\r
979       instance.rollback_callback (puser);\r
980     }\r
981 \r
982     internal override void SetRollbackHook (SQLiteRollbackCallback func)\r
983     {\r
984       rollback_callback = func;\r
985       if (func == null)\r
986         UnsafeNativeMethods.sqlite3_rollback_hook (_sql, null, IntPtr.Zero);\r
987       else\r
988         UnsafeNativeMethods.sqlite3_rollback_hook (_sql, rollback, GCHandle.ToIntPtr (gch));\r
989     }\r
990 #else\r
991     internal override void SetUpdateHook(SQLiteUpdateCallback func)\r
992     {\r
993       UnsafeNativeMethods.sqlite3_update_hook(_sql, func, IntPtr.Zero);\r
994     }\r
995 \r
996     internal override void SetCommitHook(SQLiteCommitCallback func)\r
997     {\r
998       UnsafeNativeMethods.sqlite3_commit_hook(_sql, func, IntPtr.Zero);\r
999     }\r
1000 \r
1001     internal override void SetRollbackHook(SQLiteRollbackCallback func)\r
1002     {\r
1003       UnsafeNativeMethods.sqlite3_rollback_hook(_sql, func, IntPtr.Zero);\r
1004     }\r
1005 #endif\r
1006     /// <summary>\r
1007     /// Helper function to retrieve a column of data from an active statement.\r
1008     /// </summary>\r
1009     /// <param name="stmt">The statement being step()'d through</param>\r
1010     /// <param name="index">The column index to retrieve</param>\r
1011     /// <param name="typ">The type of data contained in the column.  If Uninitialized, this function will retrieve the datatype information.</param>\r
1012     /// <returns>Returns the data in the column</returns>\r
1013     internal override object GetValue(SqliteStatement stmt, int index, SQLiteType typ)\r
1014     {\r
1015       if (IsNull(stmt, index)) return DBNull.Value;\r
1016       TypeAffinity aff = typ.Affinity;\r
1017       Type t = null;\r
1018 \r
1019       if (typ.Type != DbType.Object)\r
1020       {\r
1021         t = SqliteConvert.SQLiteTypeToType(typ);\r
1022         aff = TypeToAffinity(t);\r
1023       }\r
1024 \r
1025       switch (aff)\r
1026       {\r
1027         case TypeAffinity.Blob:\r
1028           if (typ.Type == DbType.Guid && typ.Affinity == TypeAffinity.Text)\r
1029             return new Guid(GetText(stmt, index));\r
1030 \r
1031           int n = (int)GetBytes(stmt, index, 0, null, 0, 0);\r
1032           byte[] b = new byte[n];\r
1033           GetBytes(stmt, index, 0, b, 0, n);\r
1034 \r
1035           if (typ.Type == DbType.Guid && n == 16)\r
1036             return new Guid(b);\r
1037 \r
1038           return b;\r
1039         case TypeAffinity.DateTime:\r
1040           return GetDateTime(stmt, index);\r
1041         case TypeAffinity.Double:\r
1042           if (t == null) return GetDouble(stmt, index);\r
1043           else\r
1044             return Convert.ChangeType(GetDouble(stmt, index), t, null);\r
1045         case TypeAffinity.Int64:\r
1046           if (t == null) return GetInt64(stmt, index);\r
1047           else\r
1048             return Convert.ChangeType(GetInt64(stmt, index), t, null);\r
1049         default:\r
1050           return GetText(stmt, index);\r
1051       }\r
1052     }\r
1053 \r
1054     internal override int GetCursorForTable(SqliteStatement stmt, int db, int rootPage)\r
1055     {\r
1056 #if !SQLITE_STANDARD\r
1057       return UnsafeNativeMethods.sqlite3_table_cursor(stmt._sqlite_stmt, db, rootPage);\r
1058 #else\r
1059       return -1;\r
1060 #endif\r
1061     }\r
1062 \r
1063     internal override long GetRowIdForCursor(SqliteStatement stmt, int cursor)\r
1064     {\r
1065 #if !SQLITE_STANDARD\r
1066       long rowid;\r
1067       int rc = UnsafeNativeMethods.sqlite3_cursor_rowid(stmt._sqlite_stmt, cursor, out rowid);\r
1068       if (rc == 0) return rowid;\r
1069 \r
1070       return 0;\r
1071 #else\r
1072       return 0;\r
1073 #endif\r
1074     }\r
1075 \r
1076     internal override void GetIndexColumnExtendedInfo(string database, string index, string column, out int sortMode, out int onError, out string collationSequence)\r
1077     {\r
1078 #if !SQLITE_STANDARD\r
1079       IntPtr coll;\r
1080       int colllen;\r
1081       int rc;\r
1082 \r
1083       rc = UnsafeNativeMethods.sqlite3_index_column_info_interop(_sql, ToUTF8(database), ToUTF8(index), ToUTF8(column), out sortMode, out onError, out coll, out colllen);\r
1084       if (rc != 0) throw new SqliteException(rc, "");\r
1085 \r
1086       collationSequence = UTF8ToString(coll, colllen);\r
1087 #else\r
1088       sortMode = 0;\r
1089       onError = 2;\r
1090       collationSequence = "BINARY";\r
1091 #endif\r
1092     }\r
1093   }\r
1094 }\r