Merge pull request #4195 from lateralusX/jlorenss/win-build-dependency
[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         if (UnsafeNativeMethods.use_sqlite3_open_v2) {\r
129                 n = UnsafeNativeMethods.sqlite3_open_v2(ToUTF8(strFilename), out db, (int)flags, IntPtr.Zero);\r
130         } else {\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 #elif MONOTOUCH\r
503       throw new NotImplementedException ();\r
504 #else\r
505       return UTF8ToString(UnsafeNativeMethods.sqlite3_column_origin_name(stmt._sqlite_stmt, index), -1);\r
506 #endif\r
507     }\r
508 \r
509     internal override string ColumnDatabaseName(SqliteStatement stmt, int index)\r
510     {\r
511 #if !SQLITE_STANDARD\r
512       int len;\r
513       return UTF8ToString(UnsafeNativeMethods.sqlite3_column_database_name_interop(stmt._sqlite_stmt, index, out len), len);\r
514 #elif MONOTOUCH\r
515       throw new NotImplementedException ();\r
516 #else\r
517       return UTF8ToString(UnsafeNativeMethods.sqlite3_column_database_name(stmt._sqlite_stmt, index), -1);\r
518 #endif\r
519     }\r
520 \r
521     internal override string ColumnTableName(SqliteStatement stmt, int index)\r
522     {\r
523 #if !SQLITE_STANDARD\r
524       int len;\r
525       return UTF8ToString(UnsafeNativeMethods.sqlite3_column_table_name_interop(stmt._sqlite_stmt, index, out len), len);\r
526 #elif MONOTOUCH\r
527       throw new NotImplementedException ();\r
528 #else\r
529       return UTF8ToString(UnsafeNativeMethods.sqlite3_column_table_name(stmt._sqlite_stmt, index), -1);\r
530 #endif\r
531     }\r
532 \r
533     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
534     {\r
535       IntPtr dataTypePtr;\r
536       IntPtr collSeqPtr;\r
537       int nnotNull;\r
538       int nprimaryKey;\r
539       int nautoInc;\r
540       int n;\r
541       int dtLen;\r
542       int csLen;\r
543 \r
544 #if !SQLITE_STANDARD\r
545       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
546 #else\r
547       dtLen = -1;\r
548       csLen = -1;\r
549       n = UnsafeNativeMethods.sqlite3_table_column_metadata(_sql, ToUTF8(dataBase), ToUTF8(table), ToUTF8(column), out dataTypePtr, out collSeqPtr, out nnotNull, out nprimaryKey, out nautoInc);\r
550 #endif\r
551       if (n > 0) throw new SqliteException(n, SQLiteLastError());\r
552 \r
553       dataType = UTF8ToString(dataTypePtr, dtLen);\r
554       collateSequence = UTF8ToString(collSeqPtr, csLen);\r
555 \r
556       notNull = (nnotNull == 1);\r
557       primaryKey = (nprimaryKey == 1);\r
558       autoIncrement = (nautoInc == 1);\r
559     }\r
560 \r
561     internal override double GetDouble(SqliteStatement stmt, int index)\r
562     {\r
563       double value;\r
564 #if !PLATFORM_COMPACTFRAMEWORK\r
565       value = UnsafeNativeMethods.sqlite3_column_double(stmt._sqlite_stmt, index);\r
566 #else\r
567       UnsafeNativeMethods.sqlite3_column_double_interop(stmt._sqlite_stmt, index, out value);\r
568 #endif\r
569       return value;\r
570     }\r
571 \r
572     internal override int GetInt32(SqliteStatement stmt, int index)\r
573     {\r
574       return UnsafeNativeMethods.sqlite3_column_int(stmt._sqlite_stmt, index);\r
575     }\r
576 \r
577     internal override long GetInt64(SqliteStatement stmt, int index)\r
578     {\r
579       long value;\r
580 #if !PLATFORM_COMPACTFRAMEWORK\r
581       value = UnsafeNativeMethods.sqlite3_column_int64(stmt._sqlite_stmt, index);\r
582 #else\r
583       UnsafeNativeMethods.sqlite3_column_int64_interop(stmt._sqlite_stmt, index, out value);\r
584 #endif\r
585       return value;\r
586     }\r
587 \r
588     internal override string GetText(SqliteStatement stmt, int index)\r
589     {\r
590 #if !SQLITE_STANDARD\r
591       int len;\r
592       return UTF8ToString(UnsafeNativeMethods.sqlite3_column_text_interop(stmt._sqlite_stmt, index, out len), len);\r
593 #else\r
594       return UTF8ToString(UnsafeNativeMethods.sqlite3_column_text(stmt._sqlite_stmt, index), -1);\r
595 #endif\r
596     }\r
597 \r
598     internal override DateTime GetDateTime(SqliteStatement stmt, int index)\r
599     {\r
600 #if !SQLITE_STANDARD\r
601       int len;\r
602       return ToDateTime(UnsafeNativeMethods.sqlite3_column_text_interop(stmt._sqlite_stmt, index, out len), len);\r
603 #else\r
604       return ToDateTime(UnsafeNativeMethods.sqlite3_column_text(stmt._sqlite_stmt, index), -1);\r
605 #endif\r
606     }\r
607 \r
608     internal override long GetBytes(SqliteStatement stmt, int index, int nDataOffset, byte[] bDest, int nStart, int nLength)\r
609     {\r
610       IntPtr ptr;\r
611       int nlen;\r
612       int nCopied = nLength;\r
613 \r
614       nlen = UnsafeNativeMethods.sqlite3_column_bytes(stmt._sqlite_stmt, index);\r
615       ptr = UnsafeNativeMethods.sqlite3_column_blob(stmt._sqlite_stmt, index);\r
616 \r
617       if (bDest == null) return nlen;\r
618 \r
619       if (nCopied + nStart > bDest.Length) nCopied = bDest.Length - nStart;\r
620       if (nCopied + nDataOffset > nlen) nCopied = nlen - nDataOffset;\r
621 \r
622       unsafe {\r
623               if (nCopied > 0)\r
624                       Marshal.Copy((IntPtr)((byte*)ptr + nDataOffset), bDest, nStart, nCopied);\r
625               else nCopied = 0;\r
626       }\r
627 \r
628       return nCopied;\r
629     }\r
630 \r
631     internal override long GetChars(SqliteStatement stmt, int index, int nDataOffset, char[] bDest, int nStart, int nLength)\r
632     {\r
633       int nlen;\r
634       int nCopied = nLength;\r
635 \r
636       string str = GetText(stmt, index);\r
637       nlen = str.Length;\r
638 \r
639       if (bDest == null) return nlen;\r
640 \r
641       if (nCopied + nStart > bDest.Length) nCopied = bDest.Length - nStart;\r
642       if (nCopied + nDataOffset > nlen) nCopied = nlen - nDataOffset;\r
643 \r
644       if (nCopied > 0)\r
645         str.CopyTo(nDataOffset, bDest, nStart, nCopied);\r
646       else nCopied = 0;\r
647 \r
648       return nCopied;\r
649     }\r
650 \r
651     internal override bool IsNull(SqliteStatement stmt, int index)\r
652     {\r
653       return (ColumnAffinity(stmt, index) == TypeAffinity.Null);\r
654     }\r
655 \r
656     internal override int AggregateCount(IntPtr context)\r
657     {\r
658       return UnsafeNativeMethods.sqlite3_aggregate_count(context);\r
659     }\r
660 \r
661 #if MONOTOUCH\r
662     class FunctionData {\r
663       public SQLiteCallback Func;\r
664       public SQLiteCallback FuncStep;\r
665       public SQLiteFinalCallback FuncFinal;\r
666     }\r
667 #endif\r
668 \r
669     internal override void CreateFunction(string strFunction, int nArgs, bool needCollSeq, SQLiteCallback func, SQLiteCallback funcstep, SQLiteFinalCallback funcfinal)\r
670     {\r
671       int n;\r
672 \r
673 #if MONOTOUCH\r
674       var data = new FunctionData();\r
675       data.Func = func;\r
676       data.FuncStep = funcstep;\r
677       data.FuncFinal = funcfinal;\r
678       SQLiteCallback func_callback = func == null ? null : new SQLiteCallback(scalar_callback);\r
679       SQLiteCallback funcstep_callback = funcstep == null ? null : new SQLiteCallback(step_callback);\r
680       SQLiteFinalCallback funcfinal_callback = funcfinal == null ? null : new SQLiteFinalCallback(final_callback);\r
681 \r
682       IntPtr user_data;\r
683       user_data = GCHandle.ToIntPtr(GCHandle.Alloc(data));\r
684       n = UnsafeNativeMethods.sqlite3_create_function_v2(_sql, ToUTF8(strFunction), nArgs, 4, user_data, func_callback, funcstep_callback, funcfinal_callback, destroy_callback);\r
685 \r
686       if (n == 0) {\r
687         // sqlite3_create_function_v2 will call 'destroy_callback' if it fails, so we need to recreate the gchandle here.\r
688         user_data = GCHandle.ToIntPtr(GCHandle.Alloc(data));\r
689         n = UnsafeNativeMethods.sqlite3_create_function_v2(_sql, ToUTF8(strFunction), nArgs, 1, user_data, func_callback, funcstep_callback, funcfinal_callback, destroy_callback);\r
690       }\r
691 #elif !SQLITE_STANDARD\r
692       n = UnsafeNativeMethods.sqlite3_create_function_interop(_sql, ToUTF8(strFunction), nArgs, 4, IntPtr.Zero, func, funcstep, funcfinal, (needCollSeq == true) ? 1 : 0);\r
693       if (n == 0) n = UnsafeNativeMethods.sqlite3_create_function_interop(_sql, ToUTF8(strFunction), nArgs, 1, IntPtr.Zero, func, funcstep, funcfinal, (needCollSeq == true) ? 1 : 0);\r
694 #else\r
695       n = UnsafeNativeMethods.sqlite3_create_function(_sql, ToUTF8(strFunction), nArgs, 4, IntPtr.Zero, func, funcstep, funcfinal);\r
696       if (n == 0) n = UnsafeNativeMethods.sqlite3_create_function(_sql, ToUTF8(strFunction), nArgs, 1, IntPtr.Zero, func, funcstep, funcfinal);\r
697 #endif\r
698       if (n > 0) throw new SqliteException(n, SQLiteLastError());\r
699     }\r
700 \r
701     internal override void CreateCollation(string strCollation, SQLiteCollation func, SQLiteCollation func16, IntPtr user_data)\r
702     {\r
703       int n = UnsafeNativeMethods.sqlite3_create_collation(_sql, ToUTF8(strCollation), 2, user_data, func16);\r
704       if (n == 0) UnsafeNativeMethods.sqlite3_create_collation(_sql, ToUTF8(strCollation), 1, user_data, func);\r
705       if (n > 0) throw new SqliteException(n, SQLiteLastError());\r
706     }\r
707 \r
708 #if MONOTOUCH\r
709     [Mono.Util.MonoPInvokeCallback(typeof(SQLiteCallback))]\r
710     internal static void scalar_callback(IntPtr context, int nArgs, IntPtr argsptr)\r
711     {\r
712       var handle = GCHandle.FromIntPtr (UnsafeNativeMethods.sqlite3_user_data(context));\r
713       var func = (FunctionData)handle.Target;\r
714       func.Func(context, nArgs, argsptr);\r
715     }\r
716 \r
717     [Mono.Util.MonoPInvokeCallback(typeof(SQLiteCallback))]\r
718     internal static void step_callback(IntPtr context, int nArgs, IntPtr argsptr)\r
719     {\r
720       var handle = GCHandle.FromIntPtr(UnsafeNativeMethods.sqlite3_user_data(context));\r
721       var func = (FunctionData)handle.Target;\r
722       func.FuncStep(context, nArgs, argsptr);\r
723     }\r
724 \r
725     [Mono.Util.MonoPInvokeCallback(typeof(SQLiteFinalCallback))]\r
726     internal static void final_callback(IntPtr context)\r
727     {\r
728       var handle = GCHandle.FromIntPtr(UnsafeNativeMethods.sqlite3_user_data(context));\r
729       var func = (FunctionData)handle.Target;\r
730       func.FuncFinal(context);\r
731     }\r
732 \r
733     [Mono.Util.MonoPInvokeCallback(typeof(SQLiteFinalCallback))]\r
734     internal static void destroy_callback(IntPtr context)\r
735     {\r
736       GCHandle.FromIntPtr(context).Free();\r
737     }\r
738 #endif\r
739 \r
740     internal override int ContextCollateCompare(CollationEncodingEnum enc, IntPtr context, string s1, string s2)\r
741     {\r
742 #if !SQLITE_STANDARD\r
743       byte[] b1;\r
744       byte[] b2;\r
745       System.Text.Encoding converter = null;\r
746 \r
747       switch (enc)\r
748       {\r
749         case CollationEncodingEnum.UTF8:\r
750           converter = System.Text.Encoding.UTF8;\r
751           break;\r
752         case CollationEncodingEnum.UTF16LE:\r
753           converter = System.Text.Encoding.Unicode;\r
754           break;\r
755         case CollationEncodingEnum.UTF16BE:\r
756           converter = System.Text.Encoding.BigEndianUnicode;\r
757           break;\r
758       }\r
759 \r
760       b1 = converter.GetBytes(s1);\r
761       b2 = converter.GetBytes(s2);\r
762 \r
763       return UnsafeNativeMethods.sqlite3_context_collcompare(context, b1, b1.Length, b2, b2.Length);\r
764 #else\r
765       throw new NotImplementedException();\r
766 #endif\r
767     }\r
768 \r
769     internal override int ContextCollateCompare(CollationEncodingEnum enc, IntPtr context, char[] c1, char[] c2)\r
770     {\r
771 #if !SQLITE_STANDARD\r
772       byte[] b1;\r
773       byte[] b2;\r
774       System.Text.Encoding converter = null;\r
775 \r
776       switch (enc)\r
777       {\r
778         case CollationEncodingEnum.UTF8:\r
779           converter = System.Text.Encoding.UTF8;\r
780           break;\r
781         case CollationEncodingEnum.UTF16LE:\r
782           converter = System.Text.Encoding.Unicode;\r
783           break;\r
784         case CollationEncodingEnum.UTF16BE:\r
785           converter = System.Text.Encoding.BigEndianUnicode;\r
786           break;\r
787       }\r
788 \r
789       b1 = converter.GetBytes(c1);\r
790       b2 = converter.GetBytes(c2);\r
791 \r
792       return UnsafeNativeMethods.sqlite3_context_collcompare(context, b1, b1.Length, b2, b2.Length);\r
793 #else\r
794       throw new NotImplementedException();\r
795 #endif\r
796     }\r
797 \r
798     internal override CollationSequence GetCollationSequence(SqliteFunction func, IntPtr context)\r
799     {\r
800 #if !SQLITE_STANDARD\r
801       CollationSequence seq = new CollationSequence();\r
802       int len;\r
803       int type;\r
804       int enc;\r
805       IntPtr p = UnsafeNativeMethods.sqlite3_context_collseq(context, out type, out enc, out len);\r
806 \r
807       if (p != null) seq.Name = UTF8ToString(p, len);\r
808       seq.Type = (CollationTypeEnum)type;\r
809       seq._func = func;\r
810       seq.Encoding = (CollationEncodingEnum)enc;\r
811 \r
812       return seq;\r
813 #else\r
814       throw new NotImplementedException();\r
815 #endif\r
816     }\r
817 \r
818     internal override long GetParamValueBytes(IntPtr p, int nDataOffset, byte[] bDest, int nStart, int nLength)\r
819     {\r
820       IntPtr ptr;\r
821       int nlen;\r
822       int nCopied = nLength;\r
823 \r
824       nlen = UnsafeNativeMethods.sqlite3_value_bytes(p);\r
825       ptr = UnsafeNativeMethods.sqlite3_value_blob(p);\r
826 \r
827       if (bDest == null) return nlen;\r
828 \r
829       if (nCopied + nStart > bDest.Length) nCopied = bDest.Length - nStart;\r
830       if (nCopied + nDataOffset > nlen) nCopied = nlen - nDataOffset;\r
831 \r
832       unsafe {\r
833               if (nCopied > 0)\r
834                       Marshal.Copy((IntPtr)((byte*)ptr + nDataOffset), bDest, nStart, nCopied);\r
835               else nCopied = 0;\r
836       }\r
837 \r
838       return nCopied;\r
839     }\r
840 \r
841     internal override double GetParamValueDouble(IntPtr ptr)\r
842     {\r
843       double value;\r
844 #if !PLATFORM_COMPACTFRAMEWORK\r
845       value = UnsafeNativeMethods.sqlite3_value_double(ptr);\r
846 #else\r
847       UnsafeNativeMethods.sqlite3_value_double_interop(ptr, out value);\r
848 #endif\r
849       return value;\r
850     }\r
851 \r
852     internal override int GetParamValueInt32(IntPtr ptr)\r
853     {\r
854       return UnsafeNativeMethods.sqlite3_value_int(ptr);\r
855     }\r
856 \r
857     internal override long GetParamValueInt64(IntPtr ptr)\r
858     {\r
859       Int64 value;\r
860 #if !PLATFORM_COMPACTFRAMEWORK\r
861       value = UnsafeNativeMethods.sqlite3_value_int64(ptr);\r
862 #else\r
863       UnsafeNativeMethods.sqlite3_value_int64_interop(ptr, out value);\r
864 #endif\r
865       return value;\r
866     }\r
867 \r
868     internal override string GetParamValueText(IntPtr ptr)\r
869     {\r
870 #if !SQLITE_STANDARD\r
871       int len;\r
872       return UTF8ToString(UnsafeNativeMethods.sqlite3_value_text_interop(ptr, out len), len);\r
873 #else\r
874       return UTF8ToString(UnsafeNativeMethods.sqlite3_value_text(ptr), -1);\r
875 #endif\r
876     }\r
877 \r
878     internal override TypeAffinity GetParamValueType(IntPtr ptr)\r
879     {\r
880       return UnsafeNativeMethods.sqlite3_value_type(ptr);\r
881     }\r
882 \r
883     internal override void ReturnBlob(IntPtr context, byte[] value)\r
884     {\r
885       UnsafeNativeMethods.sqlite3_result_blob(context, value, value.Length, (IntPtr)(-1));\r
886     }\r
887 \r
888     internal override void ReturnDouble(IntPtr context, double value)\r
889     {\r
890 #if !PLATFORM_COMPACTFRAMEWORK\r
891       UnsafeNativeMethods.sqlite3_result_double(context, value);\r
892 #else\r
893       UnsafeNativeMethods.sqlite3_result_double_interop(context, ref value);\r
894 #endif\r
895     }\r
896 \r
897     internal override void ReturnError(IntPtr context, string value)\r
898     {\r
899       UnsafeNativeMethods.sqlite3_result_error(context, ToUTF8(value), value.Length);\r
900     }\r
901 \r
902     internal override void ReturnInt32(IntPtr context, int value)\r
903     {\r
904       UnsafeNativeMethods.sqlite3_result_int(context, value);\r
905     }\r
906 \r
907     internal override void ReturnInt64(IntPtr context, long value)\r
908     {\r
909 #if !PLATFORM_COMPACTFRAMEWORK\r
910       UnsafeNativeMethods.sqlite3_result_int64(context, value);\r
911 #else\r
912       UnsafeNativeMethods.sqlite3_result_int64_interop(context, ref value);\r
913 #endif\r
914     }\r
915 \r
916     internal override void ReturnNull(IntPtr context)\r
917     {\r
918       UnsafeNativeMethods.sqlite3_result_null(context);\r
919     }\r
920 \r
921     internal override void ReturnText(IntPtr context, string value)\r
922     {\r
923       byte[] b = ToUTF8(value);\r
924       UnsafeNativeMethods.sqlite3_result_text(context, ToUTF8(value), b.Length - 1, (IntPtr)(-1));\r
925     }\r
926 \r
927     internal override IntPtr AggregateContext(IntPtr context)\r
928     {\r
929       return UnsafeNativeMethods.sqlite3_aggregate_context(context, 1);\r
930     }\r
931 \r
932 #if MONOTOUCH\r
933         internal override void SetPassword(byte[] passwordBytes)\r
934         {\r
935                 throw new NotImplementedException ();\r
936         }\r
937 \r
938         internal override void ChangePassword(byte[] newPasswordBytes)\r
939         {\r
940                 throw new NotImplementedException ();\r
941         }\r
942 #else\r
943     internal override void SetPassword(byte[] passwordBytes)\r
944     {\r
945       int n = UnsafeNativeMethods.sqlite3_key(_sql, passwordBytes, passwordBytes.Length);\r
946       if (n > 0) throw new SqliteException(n, SQLiteLastError());\r
947     }\r
948 \r
949     internal override void ChangePassword(byte[] newPasswordBytes)\r
950     {\r
951       int n = UnsafeNativeMethods.sqlite3_rekey(_sql, newPasswordBytes, (newPasswordBytes == null) ? 0 : newPasswordBytes.Length);\r
952       if (n > 0) throw new SqliteException(n, SQLiteLastError());\r
953     }\r
954 #endif\r
955                 \r
956 #if MONOTOUCH\r
957     SQLiteUpdateCallback update_callback;\r
958     SQLiteCommitCallback commit_callback;\r
959     SQLiteRollbackCallback rollback_callback;\r
960                 \r
961     [Mono.Util.MonoPInvokeCallback (typeof (SQLiteUpdateCallback))]\r
962     static void update (IntPtr puser, int type, IntPtr database, IntPtr table, Int64 rowid)\r
963     {\r
964       SQLite3 instance = GCHandle.FromIntPtr (puser).Target as SQLite3;\r
965       instance.update_callback (puser, type, database, table, rowid);\r
966     }\r
967                         \r
968     internal override void SetUpdateHook (SQLiteUpdateCallback func)\r
969     {\r
970       update_callback = func;\r
971       if (func == null)\r
972         UnsafeNativeMethods.sqlite3_update_hook (_sql, null, IntPtr.Zero);\r
973       else\r
974         UnsafeNativeMethods.sqlite3_update_hook (_sql, update, GCHandle.ToIntPtr (gch));\r
975     }\r
976 \r
977     [Mono.Util.MonoPInvokeCallback (typeof (SQLiteCommitCallback))]\r
978     static int commit (IntPtr puser)\r
979     {\r
980       SQLite3 instance = GCHandle.FromIntPtr (puser).Target as SQLite3;\r
981       return instance.commit_callback (puser);\r
982     }\r
983                 \r
984     internal override void SetCommitHook (SQLiteCommitCallback func)\r
985     {\r
986       commit_callback = func;\r
987       if (func == null)\r
988         UnsafeNativeMethods.sqlite3_commit_hook (_sql, null, IntPtr.Zero);\r
989       else\r
990         UnsafeNativeMethods.sqlite3_commit_hook (_sql, commit, GCHandle.ToIntPtr (gch));\r
991     }\r
992 \r
993     [Mono.Util.MonoPInvokeCallback (typeof (SQLiteRollbackCallback))]\r
994     static void rollback (IntPtr puser)\r
995     {\r
996       SQLite3 instance = GCHandle.FromIntPtr (puser).Target as SQLite3;\r
997       instance.rollback_callback (puser);\r
998     }\r
999 \r
1000     internal override void SetRollbackHook (SQLiteRollbackCallback func)\r
1001     {\r
1002       rollback_callback = func;\r
1003       if (func == null)\r
1004         UnsafeNativeMethods.sqlite3_rollback_hook (_sql, null, IntPtr.Zero);\r
1005       else\r
1006         UnsafeNativeMethods.sqlite3_rollback_hook (_sql, rollback, GCHandle.ToIntPtr (gch));\r
1007     }\r
1008 #else\r
1009     internal override void SetUpdateHook(SQLiteUpdateCallback func)\r
1010     {\r
1011       UnsafeNativeMethods.sqlite3_update_hook(_sql, func, IntPtr.Zero);\r
1012     }\r
1013 \r
1014     internal override void SetCommitHook(SQLiteCommitCallback func)\r
1015     {\r
1016       UnsafeNativeMethods.sqlite3_commit_hook(_sql, func, IntPtr.Zero);\r
1017     }\r
1018 \r
1019     internal override void SetRollbackHook(SQLiteRollbackCallback func)\r
1020     {\r
1021       UnsafeNativeMethods.sqlite3_rollback_hook(_sql, func, IntPtr.Zero);\r
1022     }\r
1023 #endif\r
1024     /// <summary>\r
1025     /// Helper function to retrieve a column of data from an active statement.\r
1026     /// </summary>\r
1027     /// <param name="stmt">The statement being step()'d through</param>\r
1028     /// <param name="index">The column index to retrieve</param>\r
1029     /// <param name="typ">The type of data contained in the column.  If Uninitialized, this function will retrieve the datatype information.</param>\r
1030     /// <returns>Returns the data in the column</returns>\r
1031     internal override object GetValue(SqliteStatement stmt, int index, SQLiteType typ)\r
1032     {\r
1033       if (IsNull(stmt, index)) return DBNull.Value;\r
1034       TypeAffinity aff = typ.Affinity;\r
1035       Type t = null;\r
1036 \r
1037       if (typ.Type != DbType.Object)\r
1038       {\r
1039         t = SqliteConvert.SQLiteTypeToType(typ);\r
1040         aff = TypeToAffinity(t);\r
1041       }\r
1042 \r
1043       switch (aff)\r
1044       {\r
1045         case TypeAffinity.Blob:\r
1046           if (typ.Type == DbType.Guid && typ.Affinity == TypeAffinity.Text)\r
1047             return new Guid(GetText(stmt, index));\r
1048 \r
1049           int n = (int)GetBytes(stmt, index, 0, null, 0, 0);\r
1050           byte[] b = new byte[n];\r
1051           GetBytes(stmt, index, 0, b, 0, n);\r
1052 \r
1053           if (typ.Type == DbType.Guid && n == 16)\r
1054             return new Guid(b);\r
1055 \r
1056           return b;\r
1057         case TypeAffinity.DateTime:\r
1058           return GetDateTime(stmt, index);\r
1059         case TypeAffinity.Double:\r
1060           if (t == null) return GetDouble(stmt, index);\r
1061           else\r
1062             return Convert.ChangeType(GetDouble(stmt, index), t, null);\r
1063         case TypeAffinity.Int64:\r
1064           if (t == null) return GetInt64(stmt, index);\r
1065           else\r
1066             return Convert.ChangeType(GetInt64(stmt, index), t, null);\r
1067         default:\r
1068           return GetText(stmt, index);\r
1069       }\r
1070     }\r
1071 \r
1072     internal override int GetCursorForTable(SqliteStatement stmt, int db, int rootPage)\r
1073     {\r
1074 #if !SQLITE_STANDARD\r
1075       return UnsafeNativeMethods.sqlite3_table_cursor(stmt._sqlite_stmt, db, rootPage);\r
1076 #else\r
1077       return -1;\r
1078 #endif\r
1079     }\r
1080 \r
1081     internal override long GetRowIdForCursor(SqliteStatement stmt, int cursor)\r
1082     {\r
1083 #if !SQLITE_STANDARD\r
1084       long rowid;\r
1085       int rc = UnsafeNativeMethods.sqlite3_cursor_rowid(stmt._sqlite_stmt, cursor, out rowid);\r
1086       if (rc == 0) return rowid;\r
1087 \r
1088       return 0;\r
1089 #else\r
1090       return 0;\r
1091 #endif\r
1092     }\r
1093 \r
1094     internal override void GetIndexColumnExtendedInfo(string database, string index, string column, out int sortMode, out int onError, out string collationSequence)\r
1095     {\r
1096 #if !SQLITE_STANDARD\r
1097       IntPtr coll;\r
1098       int colllen;\r
1099       int rc;\r
1100 \r
1101       rc = UnsafeNativeMethods.sqlite3_index_column_info_interop(_sql, ToUTF8(database), ToUTF8(index), ToUTF8(column), out sortMode, out onError, out coll, out colllen);\r
1102       if (rc != 0) throw new SqliteException(rc, "");\r
1103 \r
1104       collationSequence = UTF8ToString(coll, colllen);\r
1105 #else\r
1106       sortMode = 0;\r
1107       onError = 2;\r
1108       collationSequence = "BINARY";\r
1109 #endif\r
1110     }\r
1111   }\r
1112 }\r