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