Add autoconf checks for platforms without IPv6
[mono.git] / mcs / class / Mono.Data.Sqlite / Mono.Data.Sqlite_2.0 / SQLiteConnection.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.Data.Common;\r
13   using System.Collections.Generic;\r
14   using System.Globalization;\r
15   using System.ComponentModel;\r
16   using System.Text;\r
17   using System.Runtime.InteropServices;\r
18   using System.IO;\r
19 \r
20   /// <summary>\r
21   /// SQLite implentation of DbConnection.\r
22   /// </summary>\r
23   /// <remarks>\r
24   /// The <see cref="ConnectionString">ConnectionString</see> property of the SqliteConnection class can contain the following parameter(s), delimited with a semi-colon:\r
25   /// <list type="table">\r
26   /// <listheader>\r
27   /// <term>Parameter</term>\r
28   /// <term>Values</term>\r
29   /// <term>Required</term>\r
30   /// <term>Default</term>\r
31   /// </listheader>\r
32   /// <item>\r
33   /// <description>Data Source</description>\r
34   /// <description>{filename}</description>\r
35   /// <description>Y</description>\r
36   /// <description></description>\r
37   /// </item>\r
38   /// <item>\r
39   /// <description>Version</description>\r
40   /// <description>3</description>\r
41   /// <description>N</description>\r
42   /// <description>3</description>\r
43   /// </item>\r
44   /// <item>\r
45   /// <description>UseUTF16Encoding</description>\r
46   /// <description><b>True</b><br/><b>False</b></description>\r
47   /// <description>N</description>\r
48   /// <description>False</description>\r
49   /// </item>\r
50   /// <item>\r
51   /// <description>DateTimeFormat</description>\r
52   /// <description><b>Ticks</b> - Use DateTime.Ticks<br/><b>ISO8601</b> - Use ISO8601 DateTime format</description>\r
53   /// <description>N</description>\r
54   /// <description>ISO8601</description>\r
55   /// </item>\r
56   /// <item>\r
57   /// <description>BinaryGUID</description>\r
58   /// <description><b>True</b> - Store GUID columns in binary form<br/><b>False</b> - Store GUID columns as text</description>\r
59   /// <description>N</description>\r
60   /// <description>True</description>\r
61   /// </item>\r
62   /// <item>\r
63   /// <description>Cache Size</description>\r
64   /// <description>{size in bytes}</description>\r
65   /// <description>N</description>\r
66   /// <description>2000</description>\r
67   /// </item>\r
68   /// <item>\r
69   /// <description>Synchronous</description>\r
70   /// <description><b>Normal</b> - Normal file flushing behavior<br/><b>Full</b> - Full flushing after all writes<br/><b>Off</b> - Underlying OS flushes I/O's</description>\r
71   /// <description>N</description>\r
72   /// <description>Normal</description>\r
73   /// </item>\r
74   /// <item>\r
75   /// <description>Page Size</description>\r
76   /// <description>{size in bytes}</description>\r
77   /// <description>N</description>\r
78   /// <description>1024</description>\r
79   /// </item>\r
80   /// <item>\r
81   /// <description>Password</description>\r
82   /// <description>{password}</description>\r
83   /// <description>N</description>\r
84   /// <description></description>\r
85   /// </item>\r
86   /// <item>\r
87   /// <description>Enlist</description>\r
88   /// <description><b>Y</b> - Automatically enlist in distributed transactions<br/><b>N</b> - No automatic enlistment</description>\r
89   /// <description>N</description>\r
90   /// <description>Y</description>\r
91   /// </item>\r
92   /// <item>\r
93   /// <description>Pooling</description>\r
94   /// <description><b>True</b> - Use connection pooling<br/><b>False</b> - Do not use connection pooling</description>\r
95   /// <description>N</description>\r
96   /// <description>False</description>\r
97   /// </item>\r
98   /// <item>\r
99   /// <description>FailIfMissing</description>\r
100   /// <description><b>True</b> - Don't create the database if it does not exist, throw an error instead<br/><b>False</b> - Automatically create the database if it does not exist</description>\r
101   /// <description>N</description>\r
102   /// <description>False</description>\r
103   /// </item>\r
104   /// <item>\r
105   /// <description>Max Page Count</description>\r
106   /// <description>{size in pages} - Limits the maximum number of pages (limits the size) of the database</description>\r
107   /// <description>N</description>\r
108   /// <description>0</description>\r
109   /// </item>\r
110   /// <item>\r
111   /// <description>Legacy Format</description>\r
112   /// <description><b>True</b> - Use the more compatible legacy 3.x database format<br/><b>False</b> - Use the newer 3.3x database format which compresses numbers more effectively</description>\r
113   /// <description>N</description>\r
114   /// <description>False</description>\r
115   /// </item>\r
116   /// <item>\r
117   /// <description>Default Timeout</description>\r
118   /// <description>{time in seconds}<br/>The default command timeout</description>\r
119   /// <description>N</description>\r
120   /// <description>30</description>\r
121   /// </item>\r
122   /// <item>\r
123   /// <description>Journal Mode</description>\r
124   /// <description><b>Delete</b> - Delete the journal file after a commit<br/><b>Persist</b> - Zero out and leave the journal file on disk after a commit<br/><b>Off</b> - Disable the rollback journal entirely</description>\r
125   /// <description>N</description>\r
126   /// <description>Delete</description>\r
127   /// </item>\r
128   /// <item>\r
129   /// <description>Read Only</description>\r
130   /// <description><b>True</b> - Open the database for read only access<br/><b>False</b> - Open the database for normal read/write access</description>\r
131   /// <description>N</description>\r
132   /// <description>False</description>\r
133   /// </item>\r
134   /// <item>\r
135   /// <description>Max Pool Size</description>\r
136   /// <description>The maximum number of connections for the given connection string that can be in the connection pool</description>\r
137   /// <description>N</description>\r
138   /// <description>100</description>\r
139   /// </item>\r
140   /// <item>\r
141   /// <description>Default IsolationLevel</description>\r
142   /// <description>The default transaciton isolation level</description>\r
143   /// <description>N</description>\r
144   /// <description>Serializable</description>\r
145   /// </item>\r
146   /// </list>\r
147   /// </remarks>\r
148   public sealed partial class SqliteConnection : DbConnection, ICloneable\r
149   {\r
150     private const string _dataDirectory = "|DataDirectory|";\r
151     private const string _masterdb = "sqlite_master";\r
152     private const string _tempmasterdb = "sqlite_temp_master";\r
153 \r
154     /// <summary>\r
155     /// State of the current connection\r
156     /// </summary>\r
157     private ConnectionState _connectionState;\r
158     /// <summary>\r
159     /// The connection string\r
160     /// </summary>\r
161     private string _connectionString;\r
162     /// <summary>\r
163     /// Nesting level of the transactions open on the connection\r
164     /// </summary>\r
165     internal int _transactionLevel;\r
166 \r
167     /// <summary>\r
168     /// The default isolation level for new transactions\r
169     /// </summary>\r
170     private IsolationLevel _defaultIsolation;\r
171 \r
172 #if !PLATFORM_COMPACTFRAMEWORK\r
173     /// <summary>\r
174     /// Whether or not the connection is enlisted in a distrubuted transaction\r
175     /// </summary>\r
176     internal SQLiteEnlistment _enlistment;\r
177 #endif\r
178     /// <summary>\r
179     /// The base SQLite object to interop with\r
180     /// </summary>\r
181     internal SQLiteBase _sql;\r
182     /// <summary>\r
183     /// The database filename minus path and extension\r
184     /// </summary>\r
185     private string _dataSource;\r
186     /// <summary>\r
187     /// Temporary password storage, emptied after the database has been opened\r
188     /// </summary>\r
189     private byte[] _password;\r
190 \r
191     /// <summary>\r
192     /// Default command timeout\r
193     /// </summary>\r
194     private int _defaultTimeout = 30;\r
195 \r
196     internal bool _binaryGuid;\r
197 \r
198     internal long _version;\r
199 \r
200     private event SQLiteUpdateEventHandler _updateHandler;\r
201     private event SQLiteCommitHandler _commitHandler;\r
202     private event EventHandler _rollbackHandler;\r
203 \r
204     private SQLiteUpdateCallback _updateCallback;\r
205     private SQLiteCommitCallback _commitCallback;\r
206     private SQLiteRollbackCallback _rollbackCallback;\r
207 \r
208     /// <summary>\r
209     /// This event is raised whenever the database is opened or closed.\r
210     /// </summary>\r
211     public override event StateChangeEventHandler StateChange;\r
212 \r
213     ///<overloads>\r
214     /// Constructs a new SqliteConnection object\r
215     /// </overloads>\r
216     /// <summary>\r
217     /// Default constructor\r
218     /// </summary>\r
219     public SqliteConnection()\r
220       : this("")\r
221     {\r
222     }\r
223 \r
224     /// <summary>\r
225     /// Initializes the connection with the specified connection string\r
226     /// </summary>\r
227     /// <param name="connectionString">The connection string to use on the connection</param>\r
228     public SqliteConnection(string connectionString)\r
229     {\r
230       _sql = null;\r
231       _connectionState = ConnectionState.Closed;\r
232       _connectionString = "";\r
233       _transactionLevel = 0;\r
234       _version = 0;\r
235       //_commandList = new List<WeakReference>();\r
236 \r
237       if (connectionString != null)\r
238         ConnectionString = connectionString;\r
239     }\r
240 \r
241     /// <summary>\r
242     /// Clones the settings and connection string from an existing connection.  If the existing connection is already open, this\r
243     /// function will open its own connection, enumerate any attached databases of the original connection, and automatically\r
244     /// attach to them.\r
245     /// </summary>\r
246     /// <param name="connection"></param>\r
247     public SqliteConnection(SqliteConnection connection)\r
248       : this(connection.ConnectionString)\r
249     {\r
250       string str;\r
251 \r
252       if (connection.State == ConnectionState.Open)\r
253       {\r
254         Open();\r
255 \r
256         // Reattach all attached databases from the existing connection\r
257         using (DataTable tbl = connection.GetSchema("Catalogs"))\r
258         {\r
259           foreach (DataRow row in tbl.Rows)\r
260           {\r
261             str = row[0].ToString();\r
262             if (String.Compare(str, "main", true, CultureInfo.InvariantCulture) != 0\r
263               && String.Compare(str, "temp", true, CultureInfo.InvariantCulture) != 0)\r
264             {\r
265               using (SqliteCommand cmd = CreateCommand())\r
266               {\r
267                 cmd.CommandText = String.Format(CultureInfo.InvariantCulture, "ATTACH DATABASE '{0}' AS [{1}]", row[1], row[0]);\r
268                 cmd.ExecuteNonQuery();\r
269               }\r
270             }\r
271           }\r
272         }\r
273       }\r
274     }\r
275 \r
276 #if PLATFORM_COMPACTFRAMEWORK\r
277     /// <summary>\r
278     /// Obsolete\r
279     /// </summary>\r
280     public override int ConnectionTimeout\r
281     {\r
282       get\r
283       {\r
284         return 30;\r
285       }\r
286     }\r
287 #endif\r
288 \r
289     /// <summary>\r
290     /// Creates a clone of the connection.  All attached databases and user-defined functions are cloned.  If the existing connection is open, the cloned connection \r
291     /// will also be opened.\r
292     /// </summary>\r
293     /// <returns></returns>\r
294     public object Clone()\r
295     {\r
296       return new SqliteConnection(this);\r
297     }\r
298 \r
299     /// <summary>\r
300     /// Disposes of the SqliteConnection, closing it if it is active.\r
301     /// </summary>\r
302     /// <param name="disposing">True if the connection is being explicitly closed.</param>\r
303     protected override void Dispose(bool disposing)\r
304     {\r
305       base.Dispose(disposing);\r
306 \r
307       if (_sql != null)\r
308         _sql.Dispose ();\r
309 \r
310       if (disposing)\r
311         Close();\r
312     }\r
313 \r
314     /// <summary>\r
315     /// Creates a database file.  This just creates a zero-byte file which SQLite\r
316     /// will turn into a database when the file is opened properly.\r
317     /// </summary>\r
318     /// <param name="databaseFileName">The file to create</param>\r
319     static public void CreateFile(string databaseFileName)\r
320     {\r
321       FileStream fs = File.Create(databaseFileName);\r
322       fs.Close();\r
323     }\r
324 \r
325 #if !SQLITE_STANDARD\r
326     /// <summary>\r
327     /// On NTFS volumes, this function turns on the compression attribute for the given file.\r
328     /// It must not be open or referenced at the time of the function call.\r
329     /// </summary>\r
330     /// <param name="databaseFileName">The file to compress</param>\r
331     [Obsolete("This functionality is being removed from a future version of the SQLite provider")]\r
332     static public void CompressFile(string databaseFileName)\r
333     {\r
334       UnsafeNativeMethods.sqlite3_compressfile(databaseFileName);\r
335     }\r
336 #endif\r
337 \r
338 #if !SQLITE_STANDARD\r
339     /// <summary>\r
340     /// On NTFS volumes, this function removes the compression attribute for the given file.\r
341     /// It must not be open or referenced at the time of the function call.\r
342     /// </summary>\r
343     /// <param name="databaseFileName">The file to decompress</param>\r
344     [Obsolete("This functionality is being removed from a future version of the SQLite provider")]\r
345     static public void DecompressFile(string databaseFileName)\r
346     {\r
347       UnsafeNativeMethods.sqlite3_decompressfile(databaseFileName);\r
348     }\r
349 #endif\r
350 \r
351     /// <summary>\r
352     /// Raises the state change event when the state of the connection changes\r
353     /// </summary>\r
354     /// <param name="newState">The new state.  If it is different from the previous state, an event is raised.</param>\r
355     internal void OnStateChange(ConnectionState newState)\r
356     {\r
357       ConnectionState oldState = _connectionState;\r
358       _connectionState = newState;\r
359 \r
360       if (StateChange != null && oldState != newState)\r
361       {\r
362         StateChangeEventArgs e = new StateChangeEventArgs(oldState, newState);\r
363         StateChange(this, e);\r
364       }\r
365     }\r
366 \r
367     /// <summary>\r
368     /// OBSOLETE.  Creates a new SqliteTransaction if one isn't already active on the connection.\r
369     /// </summary>\r
370     /// <param name="isolationLevel">This parameter is ignored.</param>\r
371     /// <param name="deferredLock">When TRUE, SQLite defers obtaining a write lock until a write operation is requested.\r
372     /// When FALSE, a writelock is obtained immediately.  The default is TRUE, but in a multi-threaded multi-writer \r
373     /// environment, one may instead choose to lock the database immediately to avoid any possible writer deadlock.</param>\r
374     /// <returns>Returns a SqliteTransaction object.</returns>\r
375     [Obsolete("Use one of the standard BeginTransaction methods, this one will be removed soon")]\r
376     public SqliteTransaction BeginTransaction(IsolationLevel isolationLevel, bool deferredLock)\r
377     {\r
378       return (SqliteTransaction)BeginDbTransaction(deferredLock == false ? IsolationLevel.Serializable : IsolationLevel.ReadCommitted);\r
379     }\r
380 \r
381     /// <summary>\r
382     /// OBSOLETE.  Creates a new SqliteTransaction if one isn't already active on the connection.\r
383     /// </summary>\r
384     /// <param name="deferredLock">When TRUE, SQLite defers obtaining a write lock until a write operation is requested.\r
385     /// When FALSE, a writelock is obtained immediately.  The default is false, but in a multi-threaded multi-writer \r
386     /// environment, one may instead choose to lock the database immediately to avoid any possible writer deadlock.</param>\r
387     /// <returns>Returns a SqliteTransaction object.</returns>\r
388     [Obsolete("Use one of the standard BeginTransaction methods, this one will be removed soon")]\r
389     public SqliteTransaction BeginTransaction(bool deferredLock)\r
390     {\r
391       return (SqliteTransaction)BeginDbTransaction(deferredLock == false ? IsolationLevel.Serializable : IsolationLevel.ReadCommitted);\r
392     }\r
393 \r
394     /// <summary>\r
395     /// Creates a new SqliteTransaction if one isn't already active on the connection.\r
396     /// </summary>\r
397     /// <param name="isolationLevel">Supported isolation levels are Serializable, ReadCommitted and Unspecified.</param>\r
398     /// <remarks>\r
399     /// Unspecified will use the default isolation level specified in the connection string.  If no isolation level is specified in the \r
400     /// connection string, Serializable is used.\r
401     /// Serializable transactions are the default.  In this mode, the engine gets an immediate lock on the database, and no other threads\r
402     /// may begin a transaction.  Other threads may read from the database, but not write.\r
403     /// With a ReadCommitted isolation level, locks are deferred and elevated as needed.  It is possible for multiple threads to start\r
404     /// a transaction in ReadCommitted mode, but if a thread attempts to commit a transaction while another thread\r
405     /// has a ReadCommitted lock, it may timeout or cause a deadlock on both threads until both threads' CommandTimeout's are reached.\r
406     /// </remarks>\r
407     /// <returns>Returns a SqliteTransaction object.</returns>\r
408     public new SqliteTransaction BeginTransaction(IsolationLevel isolationLevel)\r
409     {\r
410       return (SqliteTransaction)BeginDbTransaction(isolationLevel);\r
411     }\r
412 \r
413     /// <summary>\r
414     /// Creates a new SqliteTransaction if one isn't already active on the connection.\r
415     /// </summary>\r
416     /// <returns>Returns a SqliteTransaction object.</returns>\r
417     public new SqliteTransaction BeginTransaction()\r
418     {\r
419       return (SqliteTransaction)BeginDbTransaction(_defaultIsolation);\r
420     }\r
421 \r
422     /// <summary>\r
423     /// Forwards to the local BeginTransaction() function\r
424     /// </summary>\r
425     /// <param name="isolationLevel">Supported isolation levels are Unspecified, Serializable, and ReadCommitted</param>\r
426     /// <returns></returns>\r
427     protected override DbTransaction BeginDbTransaction(IsolationLevel isolationLevel)\r
428     {\r
429       if (_connectionState != ConnectionState.Open)\r
430         throw new InvalidOperationException();\r
431 \r
432       if (isolationLevel == IsolationLevel.Unspecified) isolationLevel = _defaultIsolation;\r
433 \r
434       if (isolationLevel != IsolationLevel.Serializable && isolationLevel != IsolationLevel.ReadCommitted)\r
435         throw new ArgumentException("isolationLevel");\r
436 \r
437       return new SqliteTransaction(this, isolationLevel != IsolationLevel.Serializable);\r
438     }\r
439 \r
440     /// <summary>\r
441     /// Not implemented\r
442     /// </summary>\r
443     /// <param name="databaseName"></param>\r
444     public override void ChangeDatabase(string databaseName)\r
445     {\r
446       throw new NotImplementedException();\r
447     }\r
448 \r
449     /// <summary>\r
450     /// When the database connection is closed, all commands linked to this connection are automatically reset.\r
451     /// </summary>\r
452     public override void Close()\r
453     {\r
454       if (_sql != null)\r
455       {\r
456 #if !PLATFORM_COMPACTFRAMEWORK\r
457         if (_enlistment != null)\r
458         {\r
459           // If the connection is enlisted in a transaction scope and the scope is still active,\r
460           // we cannot truly shut down this connection until the scope has completed.  Therefore make a \r
461           // hidden connection temporarily to hold open the connection until the scope has completed.\r
462           SqliteConnection cnn = new SqliteConnection();\r
463           cnn._sql = _sql;\r
464           cnn._transactionLevel = _transactionLevel;\r
465           cnn._enlistment = _enlistment;\r
466           cnn._connectionState = _connectionState;\r
467           cnn._version = _version;\r
468 \r
469           cnn._enlistment._transaction._cnn = cnn;\r
470           cnn._enlistment._disposeConnection = true;\r
471           _sql = null;\r
472           _enlistment = null;\r
473         }\r
474 #endif\r
475         if (_sql != null)\r
476         {\r
477           _sql.Close();\r
478         }\r
479         _sql = null;\r
480         _transactionLevel = 0;\r
481       }\r
482       OnStateChange(ConnectionState.Closed);\r
483     }\r
484 \r
485     /// <summary>\r
486     /// Clears the connection pool associated with the connection.  Any other active connections using the same database file\r
487     /// will be discarded instead of returned to the pool when they are closed.\r
488     /// </summary>\r
489     /// <param name="connection"></param>\r
490     public static void ClearPool(SqliteConnection connection)\r
491     {\r
492       if (connection._sql == null) return;\r
493       connection._sql.ClearPool();\r
494     }\r
495 \r
496     /// <summary>\r
497     /// Clears all connection pools.  Any active connections will be discarded instead of sent to the pool when they are closed.\r
498     /// </summary>\r
499     public static void ClearAllPools()\r
500     {\r
501       SqliteConnectionPool.ClearAllPools();\r
502     }\r
503 \r
504     /// <summary>\r
505     /// The connection string containing the parameters for the connection\r
506     /// </summary>\r
507     /// <remarks>\r
508     /// <list type="table">\r
509     /// <listheader>\r
510     /// <term>Parameter</term>\r
511     /// <term>Values</term>\r
512     /// <term>Required</term>\r
513     /// <term>Default</term>\r
514     /// </listheader>\r
515     /// <item>\r
516     /// <description>Data Source</description>\r
517     /// <description>{filename}</description>\r
518     /// <description>Y</description>\r
519     /// <description></description>\r
520     /// </item>\r
521     /// <item>\r
522     /// <description>Version</description>\r
523     /// <description>3</description>\r
524     /// <description>N</description>\r
525     /// <description>3</description>\r
526     /// </item>\r
527     /// <item>\r
528     /// <description>UseUTF16Encoding</description>\r
529     /// <description><b>True</b><br/><b>False</b></description>\r
530     /// <description>N</description>\r
531     /// <description>False</description>\r
532     /// </item>\r
533     /// <item>\r
534     /// <description>DateTimeFormat</description>\r
535     /// <description><b>Ticks</b> - Use DateTime.Ticks<br/><b>ISO8601</b> - Use ISO8601 DateTime format<br/><b>JulianDay</b> - Use JulianDay format</description>\r
536     /// <description>N</description>\r
537     /// <description>ISO8601</description>\r
538     /// </item>\r
539     /// <item>\r
540     /// <description>BinaryGUID</description>\r
541     /// <description><b>Yes/On/1</b> - Store GUID columns in binary form<br/><b>No/Off/0</b> - Store GUID columns as text</description>\r
542     /// <description>N</description>\r
543     /// <description>On</description>\r
544     /// </item>\r
545     /// <item>\r
546     /// <description>Cache Size</description>\r
547     /// <description>{size in bytes}</description>\r
548     /// <description>N</description>\r
549     /// <description>2000</description>\r
550     /// </item>\r
551     /// <item>\r
552     /// <description>Synchronous</description>\r
553     /// <description><b>Normal</b> - Normal file flushing behavior<br/><b>Full</b> - Full flushing after all writes<br/><b>Off</b> - Underlying OS flushes I/O's</description>\r
554     /// <description>N</description>\r
555     /// <description>Normal</description>\r
556     /// </item>\r
557     /// <item>\r
558     /// <description>Page Size</description>\r
559     /// <description>{size in bytes}</description>\r
560     /// <description>N</description>\r
561     /// <description>1024</description>\r
562     /// </item>\r
563     /// <item>\r
564     /// <description>Password</description>\r
565     /// <description>{password}</description>\r
566     /// <description>N</description>\r
567     /// <description></description>\r
568     /// </item>\r
569     /// <item>\r
570     /// <description>Enlist</description>\r
571     /// <description><B>Y</B> - Automatically enlist in distributed transactions<br/><b>N</b> - No automatic enlistment</description>\r
572     /// <description>N</description>\r
573     /// <description>Y</description>\r
574     /// </item>\r
575     /// <item>\r
576     /// <description>Pooling</description>\r
577     /// <description><b>True</b> - Use connection pooling<br/><b>False</b> - Do not use connection pooling</description>\r
578     /// <description>N</description>\r
579     /// <description>False</description>\r
580     /// </item>\r
581     /// <item>\r
582     /// <description>FailIfMissing</description>\r
583     /// <description><b>True</b> - Don't create the database if it does not exist, throw an error instead<br/><b>False</b> - Automatically create the database if it does not exist</description>\r
584     /// <description>N</description>\r
585     /// <description>False</description>\r
586     /// </item>\r
587     /// <item>\r
588     /// <description>Max Page Count</description>\r
589     /// <description>{size in pages} - Limits the maximum number of pages (limits the size) of the database</description>\r
590     /// <description>N</description>\r
591     /// <description>0</description>\r
592     /// </item>\r
593     /// <item>\r
594     /// <description>Legacy Format</description>\r
595     /// <description><b>True</b> - Use the more compatible legacy 3.x database format<br/><b>False</b> - Use the newer 3.3x database format which compresses numbers more effectively</description>\r
596     /// <description>N</description>\r
597     /// <description>False</description>\r
598     /// </item>\r
599     /// <item>\r
600     /// <description>Default Timeout</description>\r
601     /// <description>{time in seconds}<br/>The default command timeout</description>\r
602     /// <description>N</description>\r
603     /// <description>30</description>\r
604     /// </item>\r
605     /// <item>\r
606     /// <description>Journal Mode</description>\r
607     /// <description><b>Delete</b> - Delete the journal file after a commit<br/><b>Persist</b> - Zero out and leave the journal file on disk after a commit<br/><b>Off</b> - Disable the rollback journal entirely</description>\r
608     /// <description>N</description>\r
609     /// <description>Delete</description>\r
610     /// </item>\r
611     /// <item>\r
612     /// <description>Read Only</description>\r
613     /// <description><b>True</b> - Open the database for read only access<br/><b>False</b> - Open the database for normal read/write access</description>\r
614     /// <description>N</description>\r
615     /// <description>False</description>\r
616     /// </item>\r
617     /// <item>\r
618     /// <description>Max Pool Size</description>\r
619     /// <description>The maximum number of connections for the given connection string that can be in the connection pool</description>\r
620     /// <description>N</description>\r
621     /// <description>100</description>\r
622     /// </item>\r
623     /// <item>\r
624     /// <description>Default IsolationLevel</description>\r
625     /// <description>The default transaciton isolation level</description>\r
626     /// <description>N</description>\r
627     /// <description>Serializable</description>\r
628     /// </item>\r
629     /// </list>\r
630     /// </remarks>\r
631 #if !PLATFORM_COMPACTFRAMEWORK\r
632     [RefreshProperties(RefreshProperties.All), DefaultValue("")]\r
633     [Editor("SQLite.Designer.SqliteConnectionStringEditor, SQLite.Designer, Version=1.0.36.0, Culture=neutral, PublicKeyToken=db937bc2d44ff139", "System.Drawing.Design.UITypeEditor, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a")]\r
634 #endif\r
635     public override string ConnectionString\r
636     {\r
637       get\r
638       {\r
639         return _connectionString;\r
640       }\r
641       set\r
642       {\r
643         if (value == null)\r
644           throw new ArgumentNullException();\r
645 \r
646         else if (_connectionState != ConnectionState.Closed)\r
647           throw new InvalidOperationException();\r
648 \r
649         _connectionString = value;\r
650       }\r
651     }\r
652 \r
653     /// <summary>\r
654     /// Create a new SqliteCommand and associate it with this connection.\r
655     /// </summary>\r
656     /// <returns>Returns an instantiated SqliteCommand object already assigned to this connection.</returns>\r
657     public new SqliteCommand CreateCommand()\r
658     {\r
659       return new SqliteCommand(this);\r
660     }\r
661 \r
662     /// <summary>\r
663     /// Forwards to the local CreateCommand() function\r
664     /// </summary>\r
665     /// <returns></returns>\r
666     protected override DbCommand CreateDbCommand()\r
667     {\r
668       return CreateCommand();\r
669     }\r
670 \r
671     /// <summary>\r
672     /// Returns the filename without extension or path\r
673     /// </summary>\r
674 #if !PLATFORM_COMPACTFRAMEWORK\r
675     [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]\r
676 #endif\r
677     public override string DataSource\r
678     {\r
679       get\r
680       {\r
681         return _dataSource;\r
682       }\r
683     }\r
684 \r
685     /// <summary>\r
686     /// Returns an empty string\r
687     /// </summary>\r
688 #if !PLATFORM_COMPACTFRAMEWORK\r
689     [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]\r
690 #endif\r
691     public override string Database\r
692     {\r
693       get\r
694       {\r
695         return "main";\r
696       }\r
697     }\r
698 \r
699     /// <summary>\r
700     /// Maps mono-specific connection string keywords to the standard ones\r
701     /// </summary>\r
702     /// <returns>The mapped keyword name</returns>\r
703     internal static void MapMonoKeyword (string[] arPiece, SortedList<string, string> ls)\r
704     {\r
705             string keyword, value;\r
706             \r
707             switch (arPiece[0].ToLower (CultureInfo.InvariantCulture)) {\r
708                     case "uri":\r
709                             keyword = "Data Source";\r
710                             value = MapMonoUriPath (arPiece[1]);\r
711                             break;\r
712                             \r
713                     default:\r
714                             keyword = arPiece[0];\r
715                             value = arPiece[1];\r
716                             break;\r
717             }\r
718 \r
719             ls.Add(keyword, value);\r
720     }\r
721 \r
722     internal static string MapMonoUriPath (string path)\r
723     {\r
724             if (path.StartsWith ("file://")) {\r
725                     return path.Substring (7);\r
726             } else if (path.StartsWith ("file:")) {\r
727                     return path.Substring (5);\r
728             } else if (path.StartsWith ("/")) {\r
729                     return path;\r
730             } else {\r
731                     throw new InvalidOperationException ("Invalid connection string: invalid URI");\r
732             }\r
733     }\r
734 \r
735     internal static string MapUriPath(string path)\r
736     {\r
737             if (path.StartsWith ("file://"))\r
738                     return path.Substring (7);\r
739       else if (path.StartsWith ("file:"))\r
740                     return path.Substring (5);\r
741       else if (path.StartsWith ("/"))\r
742                     return path;\r
743       else\r
744                     throw new InvalidOperationException ("Invalid connection string: invalid URI");\r
745     }\r
746     \r
747     /// <summary>\r
748     /// Parses the connection string into component parts\r
749     /// </summary>\r
750     /// <param name="connectionString">The connection string to parse</param>\r
751     /// <returns>An array of key-value pairs representing each parameter of the connection string</returns>\r
752     internal static SortedList<string, string> ParseConnectionString(string connectionString)\r
753     {\r
754       string s = connectionString.Replace (',', ';'); // Mono compatibility\r
755       int n;\r
756       SortedList<string, string> ls = new SortedList<string, string>(StringComparer.OrdinalIgnoreCase);\r
757 \r
758       // First split into semi-colon delimited values.  The Split() function of SQLiteBase accounts for and properly\r
759       // skips semi-colons in quoted strings\r
760       string[] arParts = SqliteConvert.Split(s, ';');\r
761       string[] arPiece;\r
762 \r
763       int x = arParts.Length;\r
764       // For each semi-colon piece, split into key and value pairs by the presence of the = sign\r
765       for (n = 0; n < x; n++)\r
766       {\r
767         arPiece = SqliteConvert.Split(arParts[n], '=');\r
768         if (arPiece.Length == 2)\r
769         {\r
770           MapMonoKeyword (arPiece, ls);\r
771         }\r
772         else throw new ArgumentException(String.Format(CultureInfo.CurrentCulture, "Invalid ConnectionString format for parameter \"{0}\"", (arPiece.Length > 0) ? arPiece[0] : "null"));\r
773       }\r
774       return ls;\r
775     }\r
776 \r
777 #if !PLATFORM_COMPACTFRAMEWORK\r
778     /// <summary>\r
779     /// Manual distributed transaction enlistment support\r
780     /// </summary>\r
781     /// <param name="transaction">The distributed transaction to enlist in</param>\r
782     public override void EnlistTransaction(System.Transactions.Transaction transaction)\r
783     {\r
784       if (_transactionLevel > 0 && transaction != null)\r
785         throw new ArgumentException("Unable to enlist in transaction, a local transaction already exists");\r
786 \r
787       if (_enlistment != null && transaction != _enlistment._scope)\r
788         throw new ArgumentException("Already enlisted in a transaction");\r
789 \r
790       _enlistment = new SQLiteEnlistment(this, transaction);\r
791     }\r
792 #endif\r
793 \r
794     /// <summary>\r
795     /// Looks for a key in the array of key/values of the parameter string.  If not found, return the specified default value\r
796     /// </summary>\r
797     /// <param name="items">The list to look in</param>\r
798     /// <param name="key">The key to find</param>\r
799     /// <param name="defValue">The default value to return if the key is not found</param>\r
800     /// <returns>The value corresponding to the specified key, or the default value if not found.</returns>\r
801     static internal string FindKey(SortedList<string, string> items, string key, string defValue)\r
802     {\r
803       string ret;\r
804 \r
805       if (items.TryGetValue(key, out ret)) return ret;\r
806 \r
807       return defValue;\r
808     }\r
809 \r
810     /// <summary>\r
811     /// Opens the connection using the parameters found in the <see cref="ConnectionString">ConnectionString</see>\r
812     /// </summary>\r
813     public override void Open()\r
814     {\r
815       if (_connectionState != ConnectionState.Closed)\r
816         throw new InvalidOperationException();\r
817 \r
818       Close();\r
819 \r
820       SortedList<string, string> opts = ParseConnectionString(_connectionString);\r
821       string fileName;\r
822 \r
823       if (Convert.ToInt32(FindKey(opts, "Version", "3"), CultureInfo.InvariantCulture) != 3)\r
824         throw new NotSupportedException("Only SQLite Version 3 is supported at this time");\r
825 \r
826       fileName = FindKey(opts, "Data Source", "");\r
827 \r
828       if (String.IsNullOrEmpty(fileName))\r
829       {\r
830         fileName = FindKey(opts, "Uri", "");\r
831         if (String.IsNullOrEmpty(fileName))\r
832           throw new ArgumentException("Data Source cannot be empty.  Use :memory: to open an in-memory database");\r
833         else\r
834           fileName = MapUriPath(fileName);\r
835       }\r
836 \r
837       if (String.Compare(fileName, ":MEMORY:", true, CultureInfo.InvariantCulture) == 0)\r
838         fileName = ":memory:";\r
839       else\r
840       {\r
841 #if PLATFORM_COMPACTFRAMEWORK\r
842        if (fileName.StartsWith(".\\"))\r
843          fileName = Path.GetDirectoryName(System.Reflection.Assembly.GetCallingAssembly().GetName().CodeBase) + fileName.Substring(1);\r
844 #endif\r
845        fileName = ExpandFileName(fileName);\r
846       }\r
847       try\r
848       {\r
849         bool usePooling = (SqliteConvert.ToBoolean(FindKey(opts, "Pooling", Boolean.FalseString)) == true);\r
850         bool bUTF16 = (SqliteConvert.ToBoolean(FindKey(opts, "UseUTF16Encoding", Boolean.FalseString)) == true);\r
851         int maxPoolSize = Convert.ToInt32(FindKey(opts, "Max Pool Size", "100"));\r
852 \r
853         _defaultTimeout = Convert.ToInt32(FindKey(opts, "Default Timeout", "30"), CultureInfo.CurrentCulture);\r
854 \r
855         _defaultIsolation = (IsolationLevel)Enum.Parse(typeof(IsolationLevel), FindKey(opts, "Default IsolationLevel", "Serializable"), true);\r
856         if (_defaultIsolation != IsolationLevel.Serializable && _defaultIsolation != IsolationLevel.ReadCommitted)\r
857           throw new NotSupportedException("Invalid Default IsolationLevel specified");\r
858 \r
859         SQLiteDateFormats dateFormat = (SQLiteDateFormats)Enum.Parse(typeof(SQLiteDateFormats), FindKey(opts, "DateTimeFormat", "ISO8601"), true);\r
860         //string temp = FindKey(opts, "DateTimeFormat", "ISO8601");\r
861         //if (String.Compare(temp, "ticks", true, CultureInfo.InvariantCulture) == 0) dateFormat = SQLiteDateFormats.Ticks;\r
862         //else if (String.Compare(temp, "julianday", true, CultureInfo.InvariantCulture) == 0) dateFormat = SQLiteDateFormats.JulianDay;\r
863 \r
864         if (bUTF16) // SQLite automatically sets the encoding of the database to UTF16 if called from sqlite3_open16()\r
865           _sql = new SQLite3_UTF16(dateFormat);\r
866         else\r
867           _sql = new SQLite3(dateFormat);\r
868 \r
869         SQLiteOpenFlagsEnum flags = SQLiteOpenFlagsEnum.None;\r
870 \r
871         if (SqliteConvert.ToBoolean(FindKey(opts, "Read Only", Boolean.FalseString)) == true)\r
872           flags |= SQLiteOpenFlagsEnum.ReadOnly;\r
873         else {\r
874           flags |= SQLiteOpenFlagsEnum.ReadWrite;\r
875           if (SqliteConvert.ToBoolean(FindKey(opts, "FailIfMissing", Boolean.FalseString)) == false)\r
876             flags |= SQLiteOpenFlagsEnum.Create;\r
877         }\r
878         if (SqliteConvert.ToBoolean (FindKey (opts, "FileProtectionComplete", Boolean.FalseString)))\r
879                 flags |= SQLiteOpenFlagsEnum.FileProtectionComplete;\r
880         if (SqliteConvert.ToBoolean (FindKey (opts, "FileProtectionCompleteUnlessOpen", Boolean.FalseString)))\r
881                 flags |= SQLiteOpenFlagsEnum.FileProtectionCompleteUnlessOpen;\r
882         if (SqliteConvert.ToBoolean (FindKey (opts, "FileProtectionCompleteUntilFirstUserAuthentication", Boolean.FalseString)))\r
883                 flags |= SQLiteOpenFlagsEnum.FileProtectionCompleteUntilFirstUserAuthentication;\r
884         if (SqliteConvert.ToBoolean (FindKey (opts, "FileProtectionNone", Boolean.FalseString)))\r
885                 flags |= SQLiteOpenFlagsEnum.FileProtectionNone;\r
886         \r
887                                 \r
888         _sql.Open(fileName, flags, maxPoolSize, usePooling);\r
889 \r
890         _binaryGuid = (SqliteConvert.ToBoolean(FindKey(opts, "BinaryGUID", Boolean.TrueString)) == true);\r
891 \r
892         string password = FindKey(opts, "Password", null);\r
893 \r
894         if (String.IsNullOrEmpty(password) == false)\r
895           _sql.SetPassword(System.Text.UTF8Encoding.UTF8.GetBytes(password));\r
896         else if (_password != null)\r
897           _sql.SetPassword(_password);\r
898         _password = null;\r
899 \r
900         _dataSource = Path.GetFileNameWithoutExtension(fileName);\r
901 \r
902         OnStateChange(ConnectionState.Open);\r
903         _version++;\r
904 \r
905         using (SqliteCommand cmd = CreateCommand())\r
906         {\r
907           string defValue;\r
908 \r
909           if (fileName != ":memory:")\r
910           {\r
911             defValue = FindKey(opts, "Page Size", "1024");\r
912             if (Convert.ToInt32(defValue, CultureInfo.InvariantCulture) != 1024)\r
913             {\r
914               cmd.CommandText = String.Format(CultureInfo.InvariantCulture, "PRAGMA page_size={0}", defValue);\r
915               cmd.ExecuteNonQuery();\r
916             }\r
917           }\r
918 \r
919           defValue = FindKey(opts, "Max Page Count", "0");\r
920           if (Convert.ToInt32(defValue, CultureInfo.InvariantCulture) != 0)\r
921           {\r
922             cmd.CommandText = String.Format(CultureInfo.InvariantCulture, "PRAGMA max_page_count={0}", defValue);\r
923             cmd.ExecuteNonQuery();\r
924           }\r
925 \r
926           defValue = FindKey(opts, "Legacy Format", Boolean.FalseString);\r
927           cmd.CommandText = String.Format(CultureInfo.InvariantCulture, "PRAGMA legacy_file_format={0}", SqliteConvert.ToBoolean(defValue) == true ? "ON" : "OFF");\r
928           cmd.ExecuteNonQuery();\r
929 \r
930           defValue = FindKey(opts, "Synchronous", "Normal");\r
931           if (String.Compare(defValue, "Full", StringComparison.OrdinalIgnoreCase) != 0)\r
932           {\r
933             cmd.CommandText = String.Format(CultureInfo.InvariantCulture, "PRAGMA synchronous={0}", defValue);\r
934             cmd.ExecuteNonQuery();\r
935           }\r
936 \r
937           defValue = FindKey(opts, "Cache Size", "2000");\r
938           if (Convert.ToInt32(defValue, CultureInfo.InvariantCulture) != 2000)\r
939           {\r
940             cmd.CommandText = String.Format(CultureInfo.InvariantCulture, "PRAGMA cache_size={0}", defValue);\r
941             cmd.ExecuteNonQuery();\r
942           }\r
943 \r
944           defValue = FindKey(opts, "Journal Mode", "Delete");\r
945           if (String.Compare(defValue, "Default", StringComparison.OrdinalIgnoreCase) != 0)\r
946           {\r
947             cmd.CommandText = String.Format(CultureInfo.InvariantCulture, "PRAGMA journal_mode={0}", defValue);\r
948             cmd.ExecuteNonQuery();\r
949           }\r
950         }\r
951 \r
952         if (_commitHandler != null)\r
953           _sql.SetCommitHook(_commitCallback);\r
954 \r
955         if (_updateHandler != null)\r
956           _sql.SetUpdateHook(_updateCallback);\r
957 \r
958         if (_rollbackHandler != null)\r
959           _sql.SetRollbackHook(_rollbackCallback);\r
960 \r
961 #if !PLATFORM_COMPACTFRAMEWORK\r
962         if (global::System.Transactions.Transaction.Current != null && SqliteConvert.ToBoolean(FindKey(opts, "Enlist", Boolean.TrueString)) == true)\r
963                 EnlistTransaction(global::System.Transactions.Transaction.Current);\r
964 #endif\r
965       }\r
966       catch (SqliteException)\r
967       {\r
968         Close();\r
969         throw;\r
970       }\r
971     }\r
972 \r
973     /// <summary>\r
974     /// Gets/sets the default command timeout for newly-created commands.  This is especially useful for \r
975     /// commands used internally such as inside a SqliteTransaction, where setting the timeout is not possible.\r
976     /// This can also be set in the ConnectionString with "Default Timeout"\r
977     /// </summary>\r
978     public int DefaultTimeout\r
979     {\r
980       get { return _defaultTimeout; }\r
981       set { _defaultTimeout = value; }\r
982     }\r
983 \r
984     /// <summary>\r
985     /// Returns the version of the underlying SQLite database engine\r
986     /// </summary>\r
987 #if !PLATFORM_COMPACTFRAMEWORK\r
988     [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]\r
989 #endif\r
990     public override string ServerVersion\r
991     {\r
992       get\r
993       {\r
994         if (_connectionState != ConnectionState.Open)\r
995           throw new InvalidOperationException();\r
996 \r
997         return _sql.Version;\r
998       }\r
999     }\r
1000 \r
1001     /// <summary>\r
1002     /// Returns the version of the underlying SQLite database engine\r
1003     /// </summary>\r
1004     public static string SQLiteVersion\r
1005     {\r
1006       get { return SQLite3.SQLiteVersion; }\r
1007     }\r
1008 \r
1009     /// <summary>\r
1010     /// Returns the state of the connection.\r
1011     /// </summary>\r
1012 #if !PLATFORM_COMPACTFRAMEWORK\r
1013     [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]\r
1014 #endif\r
1015     public override ConnectionState State\r
1016     {\r
1017       get\r
1018       {\r
1019         return _connectionState;\r
1020       }\r
1021     }\r
1022 \r
1023     /// <summary>\r
1024     /// Change the password (or assign a password) to an open database.\r
1025     /// </summary>\r
1026     /// <remarks>\r
1027     /// No readers or writers may be active for this process.  The database must already be open\r
1028     /// and if it already was password protected, the existing password must already have been supplied.\r
1029     /// </remarks>\r
1030     /// <param name="newPassword">The new password to assign to the database</param>\r
1031     public void ChangePassword(string newPassword)\r
1032     {\r
1033       ChangePassword(String.IsNullOrEmpty(newPassword) ? null : System.Text.UTF8Encoding.UTF8.GetBytes(newPassword));\r
1034     }\r
1035 \r
1036     /// <summary>\r
1037     /// Change the password (or assign a password) to an open database.\r
1038     /// </summary>\r
1039     /// <remarks>\r
1040     /// No readers or writers may be active for this process.  The database must already be open\r
1041     /// and if it already was password protected, the existing password must already have been supplied.\r
1042     /// </remarks>\r
1043     /// <param name="newPassword">The new password to assign to the database</param>\r
1044     public void ChangePassword(byte[] newPassword)\r
1045     {\r
1046       if (_connectionState != ConnectionState.Open)\r
1047         throw new InvalidOperationException("Database must be opened before changing the password.");\r
1048 \r
1049       _sql.ChangePassword(newPassword);\r
1050     }\r
1051 \r
1052     /// <summary>\r
1053     /// Sets the password for a password-protected database.  A password-protected database is\r
1054     /// unusable for any operation until the password has been set.\r
1055     /// </summary>\r
1056     /// <param name="databasePassword">The password for the database</param>\r
1057     public void SetPassword(string databasePassword)\r
1058     {\r
1059       SetPassword(String.IsNullOrEmpty(databasePassword) ? null : System.Text.UTF8Encoding.UTF8.GetBytes(databasePassword));\r
1060     }\r
1061 \r
1062     /// <summary>\r
1063     /// Sets the password for a password-protected database.  A password-protected database is\r
1064     /// unusable for any operation until the password has been set.\r
1065     /// </summary>\r
1066     /// <param name="databasePassword">The password for the database</param>\r
1067     public void SetPassword(byte[] databasePassword)\r
1068     {\r
1069       if (_connectionState != ConnectionState.Closed)\r
1070         throw new InvalidOperationException("Password can only be set before the database is opened.");\r
1071 \r
1072       if (databasePassword != null)\r
1073         if (databasePassword.Length == 0) databasePassword = null;\r
1074 \r
1075       _password = databasePassword;\r
1076     }\r
1077 \r
1078     /// <summary>\r
1079     /// Expand the filename of the data source, resolving the |DataDirectory| macro as appropriate.\r
1080     /// </summary>\r
1081     /// <param name="sourceFile">The database filename to expand</param>\r
1082     /// <returns>The expanded path and filename of the filename</returns>\r
1083     private string ExpandFileName(string sourceFile)\r
1084     {\r
1085       if (String.IsNullOrEmpty(sourceFile)) return sourceFile;\r
1086 \r
1087       if (sourceFile.StartsWith(_dataDirectory, StringComparison.OrdinalIgnoreCase))\r
1088       {\r
1089         string dataDirectory;\r
1090 \r
1091 #if PLATFORM_COMPACTFRAMEWORK\r
1092         dataDirectory = Path.GetDirectoryName(System.Reflection.Assembly.GetCallingAssembly().GetName().CodeBase);\r
1093 #else\r
1094         dataDirectory = AppDomain.CurrentDomain.GetData("DataDirectory") as string;\r
1095         if (String.IsNullOrEmpty(dataDirectory))\r
1096           dataDirectory = AppDomain.CurrentDomain.BaseDirectory;\r
1097 #endif\r
1098 \r
1099         if (sourceFile.Length > _dataDirectory.Length)\r
1100         {\r
1101           if (sourceFile[_dataDirectory.Length] == Path.DirectorySeparatorChar ||\r
1102               sourceFile[_dataDirectory.Length] == Path.AltDirectorySeparatorChar)\r
1103             sourceFile = sourceFile.Remove(_dataDirectory.Length, 1);\r
1104         }\r
1105         sourceFile = Path.Combine(dataDirectory, sourceFile.Substring(_dataDirectory.Length));\r
1106       }\r
1107 \r
1108 #if !PLATFORM_COMPACTFRAMEWORK\r
1109       sourceFile = Path.GetFullPath(sourceFile);\r
1110 #endif\r
1111 \r
1112       return sourceFile;\r
1113     }\r
1114 \r
1115     ///<overloads>\r
1116     /// The following commands are used to extract schema information out of the database.  Valid schema types are:\r
1117     /// <list type="bullet">\r
1118     /// <item>\r
1119     /// <description>MetaDataCollections</description>\r
1120     /// </item>\r
1121     /// <item>\r
1122     /// <description>DataSourceInformation</description>\r
1123     /// </item>\r
1124     /// <item>\r
1125     /// <description>Catalogs</description>\r
1126     /// </item>\r
1127     /// <item>\r
1128     /// <description>Columns</description>\r
1129     /// </item>\r
1130     /// <item>\r
1131     /// <description>ForeignKeys</description>\r
1132     /// </item>\r
1133     /// <item>\r
1134     /// <description>Indexes</description>\r
1135     /// </item>\r
1136     /// <item>\r
1137     /// <description>IndexColumns</description>\r
1138     /// </item>\r
1139     /// <item>\r
1140     /// <description>Tables</description>\r
1141     /// </item>\r
1142     /// <item>\r
1143     /// <description>Views</description>\r
1144     /// </item>\r
1145     /// <item>\r
1146     /// <description>ViewColumns</description>\r
1147     /// </item>\r
1148     /// </list>\r
1149     /// </overloads>\r
1150     /// <summary>\r
1151     /// Returns the MetaDataCollections schema\r
1152     /// </summary>\r
1153     /// <returns>A DataTable of the MetaDataCollections schema</returns>\r
1154     public override DataTable GetSchema()\r
1155     {\r
1156       return GetSchema("MetaDataCollections", null);\r
1157     }\r
1158 \r
1159     /// <summary>\r
1160     /// Returns schema information of the specified collection\r
1161     /// </summary>\r
1162     /// <param name="collectionName">The schema collection to retrieve</param>\r
1163     /// <returns>A DataTable of the specified collection</returns>\r
1164     public override DataTable GetSchema(string collectionName)\r
1165     {\r
1166       return GetSchema(collectionName, new string[0]);\r
1167     }\r
1168 \r
1169     /// <summary>\r
1170     /// Retrieves schema information using the specified constraint(s) for the specified collection\r
1171     /// </summary>\r
1172     /// <param name="collectionName">The collection to retrieve</param>\r
1173     /// <param name="restrictionValues">The restrictions to impose</param>\r
1174     /// <returns>A DataTable of the specified collection</returns>\r
1175     public override DataTable GetSchema(string collectionName, string[] restrictionValues)\r
1176     {\r
1177       if (_connectionState != ConnectionState.Open)\r
1178         throw new InvalidOperationException();\r
1179 \r
1180       string[] parms = new string[5];\r
1181 \r
1182       if (restrictionValues == null) restrictionValues = new string[0];\r
1183       restrictionValues.CopyTo(parms, 0);\r
1184 \r
1185       switch (collectionName.ToUpper(CultureInfo.InvariantCulture))\r
1186       {\r
1187         case "METADATACOLLECTIONS":\r
1188           return Schema_MetaDataCollections();\r
1189         case "DATASOURCEINFORMATION":\r
1190           return Schema_DataSourceInformation();\r
1191         case "DATATYPES":\r
1192           return Schema_DataTypes();\r
1193         case "COLUMNS":\r
1194         case "TABLECOLUMNS":\r
1195           return Schema_Columns(parms[0], parms[2], parms[3]);\r
1196         case "INDEXES":\r
1197           return Schema_Indexes(parms[0], parms[2], parms[3]);\r
1198         case "TRIGGERS":\r
1199           return Schema_Triggers(parms[0], parms[2], parms[3]);\r
1200         case "INDEXCOLUMNS":\r
1201           return Schema_IndexColumns(parms[0], parms[2], parms[3], parms[4]);\r
1202         case "TABLES":\r
1203           return Schema_Tables(parms[0], parms[2], parms[3]);\r
1204         case "VIEWS":\r
1205           return Schema_Views(parms[0], parms[2]);\r
1206         case "VIEWCOLUMNS":\r
1207           return Schema_ViewColumns(parms[0], parms[2], parms[3]);\r
1208         case "FOREIGNKEYS":\r
1209           return Schema_ForeignKeys(parms[0], parms[2], parms[3]);\r
1210         case "CATALOGS":\r
1211           return Schema_Catalogs(parms[0]);\r
1212         case "RESERVEDWORDS":\r
1213           return Schema_ReservedWords();\r
1214       }\r
1215       throw new NotSupportedException();\r
1216     }\r
1217 \r
1218     private static DataTable Schema_ReservedWords()\r
1219     {\r
1220       DataTable tbl = new DataTable("MetaDataCollections");\r
1221 \r
1222       tbl.Locale = CultureInfo.InvariantCulture;\r
1223       tbl.Columns.Add("ReservedWord", typeof(string));\r
1224       tbl.Columns.Add("MaximumVersion", typeof(string));\r
1225       tbl.Columns.Add("MinimumVersion", typeof(string));\r
1226 \r
1227       tbl.BeginLoadData();\r
1228       DataRow row;\r
1229       foreach (string word in SR.Keywords.Split(new char[] { ',' }))\r
1230       {\r
1231         row = tbl.NewRow();\r
1232         row[0] = word;\r
1233         tbl.Rows.Add(row);\r
1234       }\r
1235 \r
1236       tbl.AcceptChanges();\r
1237       tbl.EndLoadData();\r
1238 \r
1239       return tbl;\r
1240     }\r
1241 \r
1242     /// <summary>\r
1243     /// Builds a MetaDataCollections schema datatable\r
1244     /// </summary>\r
1245     /// <returns>DataTable</returns>\r
1246     private static DataTable Schema_MetaDataCollections()\r
1247     {\r
1248       DataTable tbl = new DataTable("MetaDataCollections");\r
1249 \r
1250       tbl.Locale = CultureInfo.InvariantCulture;\r
1251       tbl.Columns.Add("CollectionName", typeof(string));\r
1252       tbl.Columns.Add("NumberOfRestrictions", typeof(int));\r
1253       tbl.Columns.Add("NumberOfIdentifierParts", typeof(int));\r
1254 \r
1255       tbl.BeginLoadData();\r
1256 \r
1257       StringReader reader = new StringReader(SR.MetaDataCollections);\r
1258       tbl.ReadXml(reader);\r
1259       reader.Close();\r
1260 \r
1261       tbl.AcceptChanges();\r
1262       tbl.EndLoadData();\r
1263 \r
1264       return tbl;\r
1265     }\r
1266 \r
1267     /// <summary>\r
1268     /// Builds a DataSourceInformation datatable\r
1269     /// </summary>\r
1270     /// <returns>DataTable</returns>\r
1271     private DataTable Schema_DataSourceInformation()\r
1272     {\r
1273       DataTable tbl = new DataTable("DataSourceInformation");\r
1274       DataRow row;\r
1275 \r
1276       tbl.Locale = CultureInfo.InvariantCulture;\r
1277       tbl.Columns.Add(DbMetaDataColumnNames.CompositeIdentifierSeparatorPattern, typeof(string));\r
1278       tbl.Columns.Add(DbMetaDataColumnNames.DataSourceProductName, typeof(string));\r
1279       tbl.Columns.Add(DbMetaDataColumnNames.DataSourceProductVersion, typeof(string));\r
1280       tbl.Columns.Add(DbMetaDataColumnNames.DataSourceProductVersionNormalized, typeof(string));\r
1281       tbl.Columns.Add(DbMetaDataColumnNames.GroupByBehavior, typeof(int));\r
1282       tbl.Columns.Add(DbMetaDataColumnNames.IdentifierPattern, typeof(string));\r
1283       tbl.Columns.Add(DbMetaDataColumnNames.IdentifierCase, typeof(int));\r
1284       tbl.Columns.Add(DbMetaDataColumnNames.OrderByColumnsInSelect, typeof(bool));\r
1285       tbl.Columns.Add(DbMetaDataColumnNames.ParameterMarkerFormat, typeof(string));\r
1286       tbl.Columns.Add(DbMetaDataColumnNames.ParameterMarkerPattern, typeof(string));\r
1287       tbl.Columns.Add(DbMetaDataColumnNames.ParameterNameMaxLength, typeof(int));\r
1288       tbl.Columns.Add(DbMetaDataColumnNames.ParameterNamePattern, typeof(string));\r
1289       tbl.Columns.Add(DbMetaDataColumnNames.QuotedIdentifierPattern, typeof(string));\r
1290       tbl.Columns.Add(DbMetaDataColumnNames.QuotedIdentifierCase, typeof(int));\r
1291       tbl.Columns.Add(DbMetaDataColumnNames.StatementSeparatorPattern, typeof(string));\r
1292       tbl.Columns.Add(DbMetaDataColumnNames.StringLiteralPattern, typeof(string));\r
1293       tbl.Columns.Add(DbMetaDataColumnNames.SupportedJoinOperators, typeof(int));\r
1294 \r
1295       tbl.BeginLoadData();\r
1296 \r
1297       row = tbl.NewRow();\r
1298       row.ItemArray = new object[] {\r
1299         null,\r
1300         "SQLite",\r
1301         _sql.Version,\r
1302         _sql.Version,\r
1303         3,\r
1304         @"(^\[\p{Lo}\p{Lu}\p{Ll}_@#][\p{Lo}\p{Lu}\p{Ll}\p{Nd}@$#_]*$)|(^\[[^\]\0]|\]\]+\]$)|(^\""[^\""\0]|\""\""+\""$)",\r
1305         1,\r
1306         false,\r
1307         "{0}",\r
1308         @"@[\p{Lo}\p{Lu}\p{Ll}\p{Lm}_@#][\p{Lo}\p{Lu}\p{Ll}\p{Lm}\p{Nd}\uff3f_@#\$]*(?=\s+|$)",\r
1309         255,\r
1310         @"^[\p{Lo}\p{Lu}\p{Ll}\p{Lm}_@#][\p{Lo}\p{Lu}\p{Ll}\p{Lm}\p{Nd}\uff3f_@#\$]*(?=\s+|$)",\r
1311         @"(([^\[]|\]\])*)",\r
1312         1,\r
1313         ";",\r
1314         @"'(([^']|'')*)'",\r
1315         15\r
1316       };\r
1317       tbl.Rows.Add(row);\r
1318 \r
1319       tbl.AcceptChanges();\r
1320       tbl.EndLoadData();\r
1321 \r
1322       return tbl;\r
1323     }\r
1324 \r
1325     /// <summary>\r
1326     /// Build a Columns schema\r
1327     /// </summary>\r
1328     /// <param name="strCatalog">The catalog (attached database) to query, can be null</param>\r
1329     /// <param name="strTable">The table to retrieve schema information for, must not be null</param>\r
1330     /// <param name="strColumn">The column to retrieve schema information for, can be null</param>\r
1331     /// <returns>DataTable</returns>\r
1332     private DataTable Schema_Columns(string strCatalog, string strTable, string strColumn)\r
1333     {\r
1334       DataTable tbl = new DataTable("Columns");\r
1335       DataRow row;\r
1336 \r
1337       tbl.Locale = CultureInfo.InvariantCulture;\r
1338       tbl.Columns.Add("TABLE_CATALOG", typeof(string));\r
1339       tbl.Columns.Add("TABLE_SCHEMA", typeof(string));\r
1340       tbl.Columns.Add("TABLE_NAME", typeof(string));\r
1341       tbl.Columns.Add("COLUMN_NAME", typeof(string));\r
1342       tbl.Columns.Add("COLUMN_GUID", typeof(Guid));\r
1343       tbl.Columns.Add("COLUMN_PROPID", typeof(long));\r
1344       tbl.Columns.Add("ORDINAL_POSITION", typeof(int));\r
1345       tbl.Columns.Add("COLUMN_HASDEFAULT", typeof(bool));\r
1346       tbl.Columns.Add("COLUMN_DEFAULT", typeof(string));\r
1347       tbl.Columns.Add("COLUMN_FLAGS", typeof(long));\r
1348       tbl.Columns.Add("IS_NULLABLE", typeof(bool));\r
1349       tbl.Columns.Add("DATA_TYPE", typeof(string));\r
1350       tbl.Columns.Add("TYPE_GUID", typeof(Guid));\r
1351       tbl.Columns.Add("CHARACTER_MAXIMUM_LENGTH", typeof(int));\r
1352       tbl.Columns.Add("CHARACTER_OCTET_LENGTH", typeof(int));\r
1353       tbl.Columns.Add("NUMERIC_PRECISION", typeof(int));\r
1354       tbl.Columns.Add("NUMERIC_SCALE", typeof(int));\r
1355       tbl.Columns.Add("DATETIME_PRECISION", typeof(long));\r
1356       tbl.Columns.Add("CHARACTER_SET_CATALOG", typeof(string));\r
1357       tbl.Columns.Add("CHARACTER_SET_SCHEMA", typeof(string));\r
1358       tbl.Columns.Add("CHARACTER_SET_NAME", typeof(string));\r
1359       tbl.Columns.Add("COLLATION_CATALOG", typeof(string));\r
1360       tbl.Columns.Add("COLLATION_SCHEMA", typeof(string));\r
1361       tbl.Columns.Add("COLLATION_NAME", typeof(string));\r
1362       tbl.Columns.Add("DOMAIN_CATALOG", typeof(string));\r
1363       tbl.Columns.Add("DOMAIN_NAME", typeof(string));\r
1364       tbl.Columns.Add("DESCRIPTION", typeof(string));\r
1365       tbl.Columns.Add("PRIMARY_KEY", typeof(bool));\r
1366       tbl.Columns.Add("EDM_TYPE", typeof(string));\r
1367       tbl.Columns.Add("AUTOINCREMENT", typeof(bool));\r
1368       tbl.Columns.Add("UNIQUE", typeof(bool));\r
1369 \r
1370       tbl.BeginLoadData();\r
1371 \r
1372       if (String.IsNullOrEmpty(strCatalog)) strCatalog = "main";\r
1373 \r
1374       string master = (String.Compare(strCatalog, "temp", true, CultureInfo.InvariantCulture) == 0) ? _tempmasterdb : _masterdb;\r
1375 \r
1376       using (SqliteCommand cmdTables = new SqliteCommand(String.Format(CultureInfo.InvariantCulture, "SELECT * FROM [{0}].[{1}] WHERE [type] LIKE 'table' OR [type] LIKE 'view'", strCatalog, master), this))\r
1377       using (SqliteDataReader rdTables = cmdTables.ExecuteReader())\r
1378       {\r
1379         while (rdTables.Read())\r
1380         {\r
1381           if (String.IsNullOrEmpty(strTable) || String.Compare(strTable, rdTables.GetString(2), true, CultureInfo.InvariantCulture) == 0)\r
1382           {\r
1383             try\r
1384             {\r
1385               using (SqliteCommand cmd = new SqliteCommand(String.Format(CultureInfo.InvariantCulture, "SELECT * FROM [{0}].[{1}]", strCatalog, rdTables.GetString(2)), this))\r
1386               using (SqliteDataReader rd = (SqliteDataReader)cmd.ExecuteReader(CommandBehavior.SchemaOnly))\r
1387               using (DataTable tblSchema = rd.GetSchemaTable(true, true))\r
1388               {\r
1389                 foreach (DataRow schemaRow in tblSchema.Rows)\r
1390                 {\r
1391                   if (String.Compare(schemaRow[SchemaTableColumn.ColumnName].ToString(), strColumn, true, CultureInfo.InvariantCulture) == 0\r
1392                     || strColumn == null)\r
1393                   {\r
1394                     row = tbl.NewRow();\r
1395 \r
1396                     row["NUMERIC_PRECISION"] = schemaRow[SchemaTableColumn.NumericPrecision];\r
1397                     row["NUMERIC_SCALE"] = schemaRow[SchemaTableColumn.NumericScale];\r
1398                     row["TABLE_NAME"] = rdTables.GetString(2);\r
1399                     row["COLUMN_NAME"] = schemaRow[SchemaTableColumn.ColumnName];\r
1400                     row["TABLE_CATALOG"] = strCatalog;\r
1401                     row["ORDINAL_POSITION"] = schemaRow[SchemaTableColumn.ColumnOrdinal];\r
1402                     row["COLUMN_HASDEFAULT"] = (schemaRow[SchemaTableOptionalColumn.DefaultValue] != DBNull.Value);\r
1403                     row["COLUMN_DEFAULT"] = schemaRow[SchemaTableOptionalColumn.DefaultValue];\r
1404                     row["IS_NULLABLE"] = schemaRow[SchemaTableColumn.AllowDBNull];\r
1405                     row["DATA_TYPE"] = schemaRow["DataTypeName"].ToString().ToLower(CultureInfo.InvariantCulture);\r
1406                     row["EDM_TYPE"] = SqliteConvert.DbTypeToTypeName((DbType)schemaRow[SchemaTableColumn.ProviderType]).ToString().ToLower(CultureInfo.InvariantCulture);\r
1407                     row["CHARACTER_MAXIMUM_LENGTH"] = schemaRow[SchemaTableColumn.ColumnSize];\r
1408                     row["TABLE_SCHEMA"] = schemaRow[SchemaTableColumn.BaseSchemaName];\r
1409                     row["PRIMARY_KEY"] = schemaRow[SchemaTableColumn.IsKey];\r
1410                     row["AUTOINCREMENT"] = schemaRow[SchemaTableOptionalColumn.IsAutoIncrement];\r
1411                     row["COLLATION_NAME"] = schemaRow["CollationType"];\r
1412                     row["UNIQUE"] = schemaRow[SchemaTableColumn.IsUnique];\r
1413                     tbl.Rows.Add(row);\r
1414                   }\r
1415                 }\r
1416               }\r
1417             }\r
1418             catch(SqliteException)\r
1419             {\r
1420             }\r
1421           }\r
1422         }\r
1423       }\r
1424 \r
1425       tbl.AcceptChanges();\r
1426       tbl.EndLoadData();\r
1427 \r
1428       return tbl;\r
1429     }\r
1430 \r
1431     /// <summary>\r
1432     /// Returns index information for the given database and catalog\r
1433     /// </summary>\r
1434     /// <param name="strCatalog">The catalog (attached database) to query, can be null</param>\r
1435     /// <param name="strIndex">The name of the index to retrieve information for, can be null</param>\r
1436     /// <param name="strTable">The table to retrieve index information for, can be null</param>\r
1437     /// <returns>DataTable</returns>\r
1438     private DataTable Schema_Indexes(string strCatalog, string strTable, string strIndex)\r
1439     {\r
1440       DataTable tbl = new DataTable("Indexes");\r
1441       DataRow row;\r
1442       List<int> primaryKeys = new List<int>();\r
1443       bool maybeRowId;\r
1444 \r
1445       tbl.Locale = CultureInfo.InvariantCulture;\r
1446       tbl.Columns.Add("TABLE_CATALOG", typeof(string));\r
1447       tbl.Columns.Add("TABLE_SCHEMA", typeof(string));\r
1448       tbl.Columns.Add("TABLE_NAME", typeof(string));\r
1449       tbl.Columns.Add("INDEX_CATALOG", typeof(string));\r
1450       tbl.Columns.Add("INDEX_SCHEMA", typeof(string));\r
1451       tbl.Columns.Add("INDEX_NAME", typeof(string));\r
1452       tbl.Columns.Add("PRIMARY_KEY", typeof(bool));\r
1453       tbl.Columns.Add("UNIQUE", typeof(bool));\r
1454       tbl.Columns.Add("CLUSTERED", typeof(bool));\r
1455       tbl.Columns.Add("TYPE", typeof(int));\r
1456       tbl.Columns.Add("FILL_FACTOR", typeof(int));\r
1457       tbl.Columns.Add("INITIAL_SIZE", typeof(int));\r
1458       tbl.Columns.Add("NULLS", typeof(int));\r
1459       tbl.Columns.Add("SORT_BOOKMARKS", typeof(bool));\r
1460       tbl.Columns.Add("AUTO_UPDATE", typeof(bool));\r
1461       tbl.Columns.Add("NULL_COLLATION", typeof(int));\r
1462       tbl.Columns.Add("ORDINAL_POSITION", typeof(int));\r
1463       tbl.Columns.Add("COLUMN_NAME", typeof(string));\r
1464       tbl.Columns.Add("COLUMN_GUID", typeof(Guid));\r
1465       tbl.Columns.Add("COLUMN_PROPID", typeof(long));\r
1466       tbl.Columns.Add("COLLATION", typeof(short));\r
1467       tbl.Columns.Add("CARDINALITY", typeof(Decimal));\r
1468       tbl.Columns.Add("PAGES", typeof(int));\r
1469       tbl.Columns.Add("FILTER_CONDITION", typeof(string));\r
1470       tbl.Columns.Add("INTEGRATED", typeof(bool));\r
1471       tbl.Columns.Add("INDEX_DEFINITION", typeof(string));\r
1472 \r
1473       tbl.BeginLoadData();\r
1474 \r
1475       if (String.IsNullOrEmpty(strCatalog)) strCatalog = "main";\r
1476 \r
1477       string master = (String.Compare(strCatalog, "temp", true, CultureInfo.InvariantCulture) == 0) ? _tempmasterdb : _masterdb;\r
1478       \r
1479       using (SqliteCommand cmdTables = new SqliteCommand(String.Format(CultureInfo.InvariantCulture, "SELECT * FROM [{0}].[{1}] WHERE [type] LIKE 'table'", strCatalog, master), this))\r
1480       using (SqliteDataReader rdTables = cmdTables.ExecuteReader())\r
1481       {\r
1482         while (rdTables.Read())\r
1483         {\r
1484           maybeRowId = false;\r
1485           primaryKeys.Clear();\r
1486           if (String.IsNullOrEmpty(strTable) || String.Compare(rdTables.GetString(2), strTable, true, CultureInfo.InvariantCulture) == 0)\r
1487           {\r
1488             // First, look for any rowid indexes -- which sqlite defines are INTEGER PRIMARY KEY columns.\r
1489             // Such indexes are not listed in the indexes list but count as indexes just the same.\r
1490             try\r
1491             {\r
1492               using (SqliteCommand cmdTable = new SqliteCommand(String.Format(CultureInfo.InvariantCulture, "PRAGMA [{0}].table_info([{1}])", strCatalog, rdTables.GetString(2)), this))\r
1493               using (SqliteDataReader rdTable = cmdTable.ExecuteReader())\r
1494               {\r
1495                 while (rdTable.Read())\r
1496                 {\r
1497                   if (rdTable.GetInt32(5) == 1)\r
1498                   {\r
1499                     primaryKeys.Add(rdTable.GetInt32(0));\r
1500 \r
1501                     // If the primary key is of type INTEGER, then its a rowid and we need to make a fake index entry for it.\r
1502                     if (String.Compare(rdTable.GetString(2), "INTEGER", true, CultureInfo.InvariantCulture) == 0)\r
1503                       maybeRowId = true;\r
1504                   }\r
1505                 }\r
1506               }\r
1507             }\r
1508             catch (SqliteException)\r
1509             {\r
1510             }\r
1511             if (primaryKeys.Count == 1 && maybeRowId == true)\r
1512             {\r
1513               row = tbl.NewRow();\r
1514 \r
1515               row["TABLE_CATALOG"] = strCatalog;\r
1516               row["TABLE_NAME"] = rdTables.GetString(2);\r
1517               row["INDEX_CATALOG"] = strCatalog;\r
1518               row["PRIMARY_KEY"] = true;\r
1519               row["INDEX_NAME"] = String.Format(CultureInfo.InvariantCulture, "{1}_PK_{0}", rdTables.GetString(2), master);\r
1520               row["UNIQUE"] = true;\r
1521 \r
1522               if (String.Compare((string)row["INDEX_NAME"], strIndex, true, CultureInfo.InvariantCulture) == 0\r
1523               || strIndex == null)\r
1524               {\r
1525                 tbl.Rows.Add(row);\r
1526               }\r
1527 \r
1528               primaryKeys.Clear();\r
1529             }\r
1530 \r
1531             // Now fetch all the rest of the indexes.\r
1532             try\r
1533             {\r
1534               using (SqliteCommand cmd = new SqliteCommand(String.Format(CultureInfo.InvariantCulture, "PRAGMA [{0}].index_list([{1}])", strCatalog, rdTables.GetString(2)), this))\r
1535               using (SqliteDataReader rd = (SqliteDataReader)cmd.ExecuteReader())\r
1536               {\r
1537                 while (rd.Read())\r
1538                 {\r
1539                   if (String.Compare(rd.GetString(1), strIndex, true, CultureInfo.InvariantCulture) == 0\r
1540                   || strIndex == null)\r
1541                   {\r
1542                     row = tbl.NewRow();\r
1543 \r
1544                     row["TABLE_CATALOG"] = strCatalog;\r
1545                     row["TABLE_NAME"] = rdTables.GetString(2);\r
1546                     row["INDEX_CATALOG"] = strCatalog;\r
1547                     row["INDEX_NAME"] = rd.GetString(1);\r
1548                     row["UNIQUE"] = rd.GetBoolean(2);\r
1549                     row["PRIMARY_KEY"] = false;\r
1550 \r
1551                     // get the index definition\r
1552                     using (SqliteCommand cmdIndexes = new SqliteCommand(String.Format(CultureInfo.InvariantCulture, "SELECT * FROM [{0}].[{2}] WHERE [type] LIKE 'index' AND [name] LIKE '{1}'", strCatalog, rd.GetString(1).Replace("'", "''"), master), this))\r
1553                     using (SqliteDataReader rdIndexes = cmdIndexes.ExecuteReader())\r
1554                     {\r
1555                       while (rdIndexes.Read())\r
1556                       {\r
1557                         if (rdIndexes.IsDBNull(4) == false)\r
1558                           row["INDEX_DEFINITION"] = rdIndexes.GetString(4);\r
1559                         break;\r
1560                       }\r
1561                     }\r
1562 \r
1563                     // Now for the really hard work.  Figure out which index is the primary key index.\r
1564                     // The only way to figure it out is to check if the index was an autoindex and if we have a non-rowid\r
1565                     // primary key, and all the columns in the given index match the primary key columns\r
1566                     if (primaryKeys.Count > 0 && rd.GetString(1).StartsWith("sqlite_autoindex_" + rdTables.GetString(2), StringComparison.InvariantCultureIgnoreCase) == true)\r
1567                     {\r
1568                       using (SqliteCommand cmdDetails = new SqliteCommand(String.Format(CultureInfo.InvariantCulture, "PRAGMA [{0}].index_info([{1}])", strCatalog, rd.GetString(1)), this))\r
1569                       using (SqliteDataReader rdDetails = cmdDetails.ExecuteReader())\r
1570                       {\r
1571                         int nMatches = 0;\r
1572                         while (rdDetails.Read())\r
1573                         {\r
1574                           if (primaryKeys.Contains(rdDetails.GetInt32(1)) == false)\r
1575                           {\r
1576                             nMatches = 0;\r
1577                             break;\r
1578                           }\r
1579                           nMatches++;\r
1580                         }\r
1581                         if (nMatches == primaryKeys.Count)\r
1582                         {\r
1583                           row["PRIMARY_KEY"] = true;\r
1584                           primaryKeys.Clear();\r
1585                         }\r
1586                       }\r
1587                     }\r
1588 \r
1589                     tbl.Rows.Add(row);\r
1590                   }\r
1591                 }\r
1592               }\r
1593             }\r
1594             catch (SqliteException)\r
1595             {\r
1596             }\r
1597           }\r
1598         }\r
1599       }\r
1600 \r
1601       tbl.AcceptChanges();\r
1602       tbl.EndLoadData();\r
1603 \r
1604       return tbl;\r
1605     }\r
1606 \r
1607     private DataTable Schema_Triggers(string catalog, string table, string triggerName)\r
1608     {\r
1609       DataTable tbl = new DataTable("Triggers");\r
1610       DataRow row;\r
1611 \r
1612       tbl.Locale = CultureInfo.InvariantCulture;\r
1613       tbl.Columns.Add("TABLE_CATALOG", typeof(string));\r
1614       tbl.Columns.Add("TABLE_SCHEMA", typeof(string));\r
1615       tbl.Columns.Add("TABLE_NAME", typeof(string));\r
1616       tbl.Columns.Add("TRIGGER_NAME", typeof(string));\r
1617       tbl.Columns.Add("TRIGGER_DEFINITION", typeof(string));\r
1618 \r
1619       tbl.BeginLoadData();\r
1620 \r
1621       if (String.IsNullOrEmpty(table)) table = null;\r
1622       if (String.IsNullOrEmpty(catalog)) catalog = "main";\r
1623       string master = (String.Compare(catalog, "temp", true, CultureInfo.InvariantCulture) == 0) ? _tempmasterdb : _masterdb;\r
1624 \r
1625       using (SqliteCommand cmd = new SqliteCommand(String.Format(CultureInfo.InvariantCulture, "SELECT [type], [name], [tbl_name], [rootpage], [sql], [rowid] FROM [{0}].[{1}] WHERE [type] LIKE 'trigger'", catalog, master), this))\r
1626       using (SqliteDataReader rd = (SqliteDataReader)cmd.ExecuteReader())\r
1627       {\r
1628         while (rd.Read())\r
1629         {\r
1630           if (String.Compare(rd.GetString(1), triggerName, true, CultureInfo.InvariantCulture) == 0\r
1631             || triggerName == null)\r
1632           {\r
1633             if (table == null || String.Compare(table, rd.GetString(2), true, CultureInfo.InvariantCulture) == 0)\r
1634             {\r
1635               row = tbl.NewRow();\r
1636 \r
1637               row["TABLE_CATALOG"] = catalog;\r
1638               row["TABLE_NAME"] = rd.GetString(2);\r
1639               row["TRIGGER_NAME"] = rd.GetString(1);\r
1640               row["TRIGGER_DEFINITION"] = rd.GetString(4);\r
1641 \r
1642               tbl.Rows.Add(row);\r
1643             }\r
1644           }\r
1645         }\r
1646       }\r
1647       tbl.AcceptChanges();\r
1648       tbl.EndLoadData();\r
1649 \r
1650       return tbl;\r
1651     }\r
1652 \r
1653     /// <summary>\r
1654     /// Retrieves table schema information for the database and catalog\r
1655     /// </summary>\r
1656     /// <param name="strCatalog">The catalog (attached database) to retrieve tables on</param>\r
1657     /// <param name="strTable">The table to retrieve, can be null</param>\r
1658     /// <param name="strType">The table type, can be null</param>\r
1659     /// <returns>DataTable</returns>\r
1660     private DataTable Schema_Tables(string strCatalog, string strTable, string strType)\r
1661     {\r
1662       DataTable tbl = new DataTable("Tables");\r
1663       DataRow row;\r
1664       string strItem;\r
1665 \r
1666       tbl.Locale = CultureInfo.InvariantCulture;\r
1667       tbl.Columns.Add("TABLE_CATALOG", typeof(string));\r
1668       tbl.Columns.Add("TABLE_SCHEMA", typeof(string));\r
1669       tbl.Columns.Add("TABLE_NAME", typeof(string));\r
1670       tbl.Columns.Add("TABLE_TYPE", typeof(string));\r
1671       tbl.Columns.Add("TABLE_ID", typeof(long));\r
1672       tbl.Columns.Add("TABLE_ROOTPAGE", typeof(int));\r
1673       tbl.Columns.Add("TABLE_DEFINITION", typeof(string));\r
1674       tbl.BeginLoadData();\r
1675 \r
1676       if (String.IsNullOrEmpty(strCatalog)) strCatalog = "main";\r
1677 \r
1678       string master = (String.Compare(strCatalog, "temp", true, CultureInfo.InvariantCulture) == 0) ? _tempmasterdb : _masterdb;\r
1679 \r
1680       using (SqliteCommand cmd = new SqliteCommand(String.Format(CultureInfo.InvariantCulture, "SELECT [type], [name], [tbl_name], [rootpage], [sql], [rowid] FROM [{0}].[{1}] WHERE [type] LIKE 'table'", strCatalog, master), this))\r
1681       using (SqliteDataReader rd = (SqliteDataReader)cmd.ExecuteReader())\r
1682       {\r
1683         while (rd.Read())\r
1684         {\r
1685           strItem = rd.GetString(0);\r
1686           if (String.Compare(rd.GetString(2), 0, "SQLITE_", 0, 7, true, CultureInfo.InvariantCulture) == 0)\r
1687             strItem = "SYSTEM_TABLE";\r
1688 \r
1689           if (String.Compare(strType, strItem, true, CultureInfo.InvariantCulture) == 0\r
1690             || strType == null)\r
1691           {\r
1692             if (String.Compare(rd.GetString(2), strTable, true, CultureInfo.InvariantCulture) == 0\r
1693               || strTable == null)\r
1694             {\r
1695               row = tbl.NewRow();\r
1696 \r
1697               row["TABLE_CATALOG"] = strCatalog;\r
1698               row["TABLE_NAME"] = rd.GetString(2);\r
1699               row["TABLE_TYPE"] = strItem;\r
1700               row["TABLE_ID"] = rd.GetInt64(5);\r
1701               row["TABLE_ROOTPAGE"] = rd.GetInt32(3);\r
1702               row["TABLE_DEFINITION"] = rd.GetString(4);\r
1703 \r
1704               tbl.Rows.Add(row);\r
1705             }\r
1706           }\r
1707         }\r
1708       }\r
1709 \r
1710       tbl.AcceptChanges();\r
1711       tbl.EndLoadData();\r
1712 \r
1713       return tbl;\r
1714     }\r
1715 \r
1716     /// <summary>\r
1717     /// Retrieves view schema information for the database\r
1718     /// </summary>\r
1719     /// <param name="strCatalog">The catalog (attached database) to retrieve views on</param>\r
1720     /// <param name="strView">The view name, can be null</param>\r
1721     /// <returns>DataTable</returns>\r
1722     private DataTable Schema_Views(string strCatalog, string strView)\r
1723     {\r
1724       DataTable tbl = new DataTable("Views");\r
1725       DataRow row;\r
1726       string strItem;\r
1727       int nPos;\r
1728 \r
1729       tbl.Locale = CultureInfo.InvariantCulture;\r
1730       tbl.Columns.Add("TABLE_CATALOG", typeof(string));\r
1731       tbl.Columns.Add("TABLE_SCHEMA", typeof(string));\r
1732       tbl.Columns.Add("TABLE_NAME", typeof(string));\r
1733       tbl.Columns.Add("VIEW_DEFINITION", typeof(string));\r
1734       tbl.Columns.Add("CHECK_OPTION", typeof(bool));\r
1735       tbl.Columns.Add("IS_UPDATABLE", typeof(bool));\r
1736       tbl.Columns.Add("DESCRIPTION", typeof(string));\r
1737       tbl.Columns.Add("DATE_CREATED", typeof(DateTime));\r
1738       tbl.Columns.Add("DATE_MODIFIED", typeof(DateTime));\r
1739 \r
1740       tbl.BeginLoadData();\r
1741 \r
1742       if (String.IsNullOrEmpty(strCatalog)) strCatalog = "main";\r
1743 \r
1744       string master = (String.Compare(strCatalog, "temp", true, CultureInfo.InvariantCulture) == 0) ? _tempmasterdb : _masterdb;\r
1745 \r
1746       using (SqliteCommand cmd = new SqliteCommand(String.Format(CultureInfo.InvariantCulture, "SELECT * FROM [{0}].[{1}] WHERE [type] LIKE 'view'", strCatalog, master), this))\r
1747       using (SqliteDataReader rd = (SqliteDataReader)cmd.ExecuteReader())\r
1748       {\r
1749         while (rd.Read())\r
1750         {\r
1751           if (String.Compare(rd.GetString(1), strView, true, CultureInfo.InvariantCulture) == 0\r
1752             || String.IsNullOrEmpty(strView))\r
1753           {\r
1754             strItem = rd.GetString(4).Replace('\r', ' ').Replace('\n', ' ').Replace('\t', ' ');\r
1755             nPos = CultureInfo.InvariantCulture.CompareInfo.IndexOf(strItem, " AS ", CompareOptions.IgnoreCase);\r
1756             if (nPos > -1)\r
1757             {\r
1758               strItem = strItem.Substring(nPos + 4).Trim();\r
1759               row = tbl.NewRow();\r
1760 \r
1761               row["TABLE_CATALOG"] = strCatalog;\r
1762               row["TABLE_NAME"] = rd.GetString(2);\r
1763               row["IS_UPDATABLE"] = false;\r
1764               row["VIEW_DEFINITION"] = strItem;\r
1765 \r
1766               tbl.Rows.Add(row);\r
1767             }\r
1768           }\r
1769         }\r
1770       }\r
1771 \r
1772       tbl.AcceptChanges();\r
1773       tbl.EndLoadData();\r
1774 \r
1775       return tbl;\r
1776     }\r
1777 \r
1778     /// <summary>\r
1779     /// Retrieves catalog (attached databases) schema information for the database\r
1780     /// </summary>\r
1781     /// <param name="strCatalog">The catalog to retrieve, can be null</param>\r
1782     /// <returns>DataTable</returns>\r
1783     private DataTable Schema_Catalogs(string strCatalog)\r
1784     {\r
1785       DataTable tbl = new DataTable("Catalogs");\r
1786       DataRow row;\r
1787 \r
1788       tbl.Locale = CultureInfo.InvariantCulture;\r
1789       tbl.Columns.Add("CATALOG_NAME", typeof(string));\r
1790       tbl.Columns.Add("DESCRIPTION", typeof(string));\r
1791       tbl.Columns.Add("ID", typeof(long));\r
1792 \r
1793       tbl.BeginLoadData();\r
1794 \r
1795       using (SqliteCommand cmd = new SqliteCommand("PRAGMA database_list", this))\r
1796       using (SqliteDataReader rd = (SqliteDataReader)cmd.ExecuteReader())\r
1797       {\r
1798         while (rd.Read())\r
1799         {\r
1800           if (String.Compare(rd.GetString(1), strCatalog, true, CultureInfo.InvariantCulture) == 0\r
1801             || strCatalog == null)\r
1802           {\r
1803             row = tbl.NewRow();\r
1804 \r
1805             row["CATALOG_NAME"] = rd.GetString(1);\r
1806             row["DESCRIPTION"] = rd.GetString(2);\r
1807             row["ID"] = rd.GetInt64(0);\r
1808 \r
1809             tbl.Rows.Add(row);\r
1810           }\r
1811         }\r
1812       }\r
1813 \r
1814       tbl.AcceptChanges();\r
1815       tbl.EndLoadData();\r
1816 \r
1817       return tbl;\r
1818     }\r
1819 \r
1820     private DataTable Schema_DataTypes()\r
1821     {\r
1822       DataTable tbl = new DataTable("DataTypes");\r
1823 \r
1824       tbl.Locale = CultureInfo.InvariantCulture;\r
1825       tbl.Columns.Add("TypeName", typeof(String));\r
1826       tbl.Columns.Add("ProviderDbType", typeof(int));\r
1827       tbl.Columns.Add("ColumnSize", typeof(long));\r
1828       tbl.Columns.Add("CreateFormat", typeof(String));\r
1829       tbl.Columns.Add("CreateParameters", typeof(String));\r
1830       tbl.Columns.Add("DataType", typeof(String));\r
1831       tbl.Columns.Add("IsAutoIncrementable", typeof(bool));\r
1832       tbl.Columns.Add("IsBestMatch", typeof(bool));\r
1833       tbl.Columns.Add("IsCaseSensitive", typeof(bool));\r
1834       tbl.Columns.Add("IsFixedLength", typeof(bool));\r
1835       tbl.Columns.Add("IsFixedPrecisionScale", typeof(bool));\r
1836       tbl.Columns.Add("IsLong", typeof(bool));\r
1837       tbl.Columns.Add("IsNullable", typeof(bool));\r
1838       tbl.Columns.Add("IsSearchable", typeof(bool));\r
1839       tbl.Columns.Add("IsSearchableWithLike", typeof(bool));\r
1840       tbl.Columns.Add("IsLiteralSupported", typeof(bool));\r
1841       tbl.Columns.Add("LiteralPrefix", typeof(String));\r
1842       tbl.Columns.Add("LiteralSuffix", typeof(String));\r
1843       tbl.Columns.Add("IsUnsigned", typeof(bool));\r
1844       tbl.Columns.Add("MaximumScale", typeof(short));\r
1845       tbl.Columns.Add("MinimumScale", typeof(short));\r
1846       tbl.Columns.Add("IsConcurrencyType", typeof(bool));\r
1847 \r
1848       tbl.BeginLoadData();\r
1849 \r
1850       StringReader reader = new StringReader(SR.DataTypes);\r
1851       tbl.ReadXml(reader);\r
1852       reader.Close();\r
1853 \r
1854       tbl.AcceptChanges();\r
1855       tbl.EndLoadData();\r
1856 \r
1857       return tbl;\r
1858     }\r
1859 \r
1860     /// <summary>\r
1861     /// Returns the base column information for indexes in a database\r
1862     /// </summary>\r
1863     /// <param name="strCatalog">The catalog to retrieve indexes for (can be null)</param>\r
1864     /// <param name="strTable">The table to restrict index information by (can be null)</param>\r
1865     /// <param name="strIndex">The index to restrict index information by (can be null)</param>\r
1866     /// <param name="strColumn">The source column to restrict index information by (can be null)</param>\r
1867     /// <returns>A DataTable containing the results</returns>\r
1868     private DataTable Schema_IndexColumns(string strCatalog, string strTable, string strIndex, string strColumn)\r
1869     {\r
1870       DataTable tbl = new DataTable("IndexColumns");\r
1871       DataRow row;\r
1872       List<KeyValuePair<int, string>> primaryKeys = new List<KeyValuePair<int, string>>();\r
1873       bool maybeRowId;\r
1874 \r
1875       tbl.Locale = CultureInfo.InvariantCulture;\r
1876       tbl.Columns.Add("CONSTRAINT_CATALOG", typeof(string));\r
1877       tbl.Columns.Add("CONSTRAINT_SCHEMA", typeof(string));\r
1878       tbl.Columns.Add("CONSTRAINT_NAME", typeof(string));\r
1879       tbl.Columns.Add("TABLE_CATALOG", typeof(string));\r
1880       tbl.Columns.Add("TABLE_SCHEMA", typeof(string));\r
1881       tbl.Columns.Add("TABLE_NAME", typeof(string));\r
1882       tbl.Columns.Add("COLUMN_NAME", typeof(string));\r
1883       tbl.Columns.Add("ORDINAL_POSITION", typeof(int));\r
1884       tbl.Columns.Add("INDEX_NAME", typeof(string));\r
1885       tbl.Columns.Add("COLLATION_NAME", typeof(string));\r
1886       tbl.Columns.Add("SORT_MODE", typeof(string));\r
1887       tbl.Columns.Add("CONFLICT_OPTION", typeof(int));\r
1888 \r
1889       if (String.IsNullOrEmpty(strCatalog)) strCatalog = "main";\r
1890 \r
1891       string master = (String.Compare(strCatalog, "temp", true, CultureInfo.InvariantCulture) == 0) ? _tempmasterdb : _masterdb;\r
1892 \r
1893       tbl.BeginLoadData();\r
1894 \r
1895       using (SqliteCommand cmdTables = new SqliteCommand(String.Format(CultureInfo.InvariantCulture, "SELECT * FROM [{0}].[{1}] WHERE [type] LIKE 'table'", strCatalog, master), this))\r
1896       using (SqliteDataReader rdTables = cmdTables.ExecuteReader())\r
1897       {\r
1898         while (rdTables.Read())\r
1899         {\r
1900           maybeRowId = false;\r
1901           primaryKeys.Clear();\r
1902           if (String.IsNullOrEmpty(strTable) || String.Compare(rdTables.GetString(2), strTable, true, CultureInfo.InvariantCulture) == 0)\r
1903           {\r
1904             try\r
1905             {\r
1906               using (SqliteCommand cmdTable = new SqliteCommand(String.Format(CultureInfo.InvariantCulture, "PRAGMA [{0}].table_info([{1}])", strCatalog, rdTables.GetString(2)), this))\r
1907               using (SqliteDataReader rdTable = cmdTable.ExecuteReader())\r
1908               {\r
1909                 while (rdTable.Read())\r
1910                 {\r
1911                   if (rdTable.GetInt32(5) == 1) // is a primary key\r
1912                   {\r
1913                     primaryKeys.Add(new KeyValuePair<int, string>(rdTable.GetInt32(0), rdTable.GetString(1)));\r
1914                     // Is an integer -- could be a rowid if no other primary keys exist in the table\r
1915                     if (String.Compare(rdTable.GetString(2), "INTEGER", true, CultureInfo.InvariantCulture) == 0)\r
1916                       maybeRowId = true;\r
1917                   }\r
1918                 }\r
1919               }\r
1920             }\r
1921             catch (SqliteException)\r
1922             {\r
1923             }\r
1924             // This is a rowid row\r
1925             if (primaryKeys.Count == 1 && maybeRowId == true)\r
1926             {\r
1927               row = tbl.NewRow();\r
1928               row["CONSTRAINT_CATALOG"] = strCatalog;\r
1929               row["CONSTRAINT_NAME"] = String.Format(CultureInfo.InvariantCulture, "{1}_PK_{0}", rdTables.GetString(2), master);\r
1930               row["TABLE_CATALOG"] = strCatalog;\r
1931               row["TABLE_NAME"] = rdTables.GetString(2);\r
1932               row["COLUMN_NAME"] = primaryKeys[0].Value;\r
1933               row["INDEX_NAME"] = row["CONSTRAINT_NAME"];\r
1934               row["ORDINAL_POSITION"] = 0; // primaryKeys[0].Key;\r
1935               row["COLLATION_NAME"] = "BINARY";\r
1936               row["SORT_MODE"] = "ASC";\r
1937               row["CONFLICT_OPTION"] = 2;\r
1938 \r
1939               if (String.IsNullOrEmpty(strIndex) || String.Compare(strIndex, (string)row["INDEX_NAME"], true, CultureInfo.InvariantCulture) == 0)\r
1940                 tbl.Rows.Add(row);\r
1941             }\r
1942 \r
1943             using (SqliteCommand cmdIndexes = new SqliteCommand(String.Format(CultureInfo.InvariantCulture, "SELECT * FROM [{0}].[{2}] WHERE [type] LIKE 'index' AND [tbl_name] LIKE '{1}'", strCatalog, rdTables.GetString(2).Replace("'", "''"), master), this))\r
1944             using (SqliteDataReader rdIndexes = cmdIndexes.ExecuteReader())\r
1945             {\r
1946               while (rdIndexes.Read())\r
1947               {\r
1948                 int ordinal = 0;\r
1949                 if (String.IsNullOrEmpty(strIndex) || String.Compare(strIndex, rdIndexes.GetString(1), true, CultureInfo.InvariantCulture) == 0)\r
1950                 {\r
1951                   try\r
1952                   {\r
1953                     using (SqliteCommand cmdIndex = new SqliteCommand(String.Format(CultureInfo.InvariantCulture, "PRAGMA [{0}].index_info([{1}])", strCatalog, rdIndexes.GetString(1)), this))\r
1954                     using (SqliteDataReader rdIndex = cmdIndex.ExecuteReader())\r
1955                     {\r
1956                       while (rdIndex.Read())\r
1957                       {\r
1958                         row = tbl.NewRow();\r
1959                         row["CONSTRAINT_CATALOG"] = strCatalog;\r
1960                         row["CONSTRAINT_NAME"] = rdIndexes.GetString(1);\r
1961                         row["TABLE_CATALOG"] = strCatalog;\r
1962                         row["TABLE_NAME"] = rdIndexes.GetString(2);\r
1963                         row["COLUMN_NAME"] = rdIndex.GetString(2);\r
1964                         row["INDEX_NAME"] = rdIndexes.GetString(1);\r
1965                         row["ORDINAL_POSITION"] = ordinal; // rdIndex.GetInt32(1);\r
1966 \r
1967                         string collationSequence;\r
1968                         int sortMode;\r
1969                         int onError;\r
1970                         _sql.GetIndexColumnExtendedInfo(strCatalog, rdIndexes.GetString(1), rdIndex.GetString(2), out sortMode, out onError, out collationSequence);\r
1971 \r
1972                         if (String.IsNullOrEmpty(collationSequence) == false)\r
1973                           row["COLLATION_NAME"] = collationSequence;\r
1974 \r
1975                         row["SORT_MODE"] = (sortMode == 0) ? "ASC" : "DESC";\r
1976                         row["CONFLICT_OPTION"] = onError;\r
1977 \r
1978                         ordinal++;\r
1979 \r
1980                         if (String.IsNullOrEmpty(strColumn) || String.Compare(strColumn, row["COLUMN_NAME"].ToString(), true, CultureInfo.InvariantCulture) == 0)\r
1981                           tbl.Rows.Add(row);\r
1982                       }\r
1983                     }\r
1984                   }\r
1985                   catch (SqliteException)\r
1986                   {\r
1987                   }\r
1988                 }\r
1989               }\r
1990             }\r
1991           }\r
1992         }\r
1993       }\r
1994 \r
1995       tbl.EndLoadData();\r
1996       tbl.AcceptChanges();\r
1997 \r
1998       return tbl;\r
1999     }\r
2000 \r
2001     /// <summary>\r
2002     /// Returns detailed column information for a specified view\r
2003     /// </summary>\r
2004     /// <param name="strCatalog">The catalog to retrieve columns for (can be null)</param>\r
2005     /// <param name="strView">The view to restrict column information by (can be null)</param>\r
2006     /// <param name="strColumn">The source column to restrict column information by (can be null)</param>\r
2007     /// <returns>A DataTable containing the results</returns>\r
2008     private DataTable Schema_ViewColumns(string strCatalog, string strView, string strColumn)\r
2009     {\r
2010       DataTable tbl = new DataTable("ViewColumns");\r
2011       DataRow row;\r
2012       string strSql;\r
2013       int n;\r
2014       DataRow schemaRow;\r
2015       DataRow viewRow;\r
2016 \r
2017       tbl.Locale = CultureInfo.InvariantCulture;\r
2018       tbl.Columns.Add("VIEW_CATALOG", typeof(string));\r
2019       tbl.Columns.Add("VIEW_SCHEMA", typeof(string));\r
2020       tbl.Columns.Add("VIEW_NAME", typeof(string));\r
2021       tbl.Columns.Add("VIEW_COLUMN_NAME", typeof(String));\r
2022       tbl.Columns.Add("TABLE_CATALOG", typeof(string));\r
2023       tbl.Columns.Add("TABLE_SCHEMA", typeof(string));\r
2024       tbl.Columns.Add("TABLE_NAME", typeof(string));\r
2025       tbl.Columns.Add("COLUMN_NAME", typeof(string));\r
2026       tbl.Columns.Add("ORDINAL_POSITION", typeof(int));\r
2027       tbl.Columns.Add("COLUMN_HASDEFAULT", typeof(bool));\r
2028       tbl.Columns.Add("COLUMN_DEFAULT", typeof(string));\r
2029       tbl.Columns.Add("COLUMN_FLAGS", typeof(long));\r
2030       tbl.Columns.Add("IS_NULLABLE", typeof(bool));\r
2031       tbl.Columns.Add("DATA_TYPE", typeof(string));\r
2032       tbl.Columns.Add("CHARACTER_MAXIMUM_LENGTH", typeof(int));\r
2033       tbl.Columns.Add("NUMERIC_PRECISION", typeof(int));\r
2034       tbl.Columns.Add("NUMERIC_SCALE", typeof(int));\r
2035       tbl.Columns.Add("DATETIME_PRECISION", typeof(long));\r
2036       tbl.Columns.Add("CHARACTER_SET_CATALOG", typeof(string));\r
2037       tbl.Columns.Add("CHARACTER_SET_SCHEMA", typeof(string));\r
2038       tbl.Columns.Add("CHARACTER_SET_NAME", typeof(string));\r
2039       tbl.Columns.Add("COLLATION_CATALOG", typeof(string));\r
2040       tbl.Columns.Add("COLLATION_SCHEMA", typeof(string));\r
2041       tbl.Columns.Add("COLLATION_NAME", typeof(string));\r
2042       tbl.Columns.Add("PRIMARY_KEY", typeof(bool));\r
2043       tbl.Columns.Add("EDM_TYPE", typeof(string));\r
2044       tbl.Columns.Add("AUTOINCREMENT", typeof(bool));\r
2045       tbl.Columns.Add("UNIQUE", typeof(bool));\r
2046 \r
2047       if (String.IsNullOrEmpty(strCatalog)) strCatalog = "main";\r
2048 \r
2049       string master = (String.Compare(strCatalog, "temp", true, CultureInfo.InvariantCulture) == 0) ? _tempmasterdb : _masterdb;\r
2050       \r
2051       tbl.BeginLoadData();\r
2052 \r
2053       using (SqliteCommand cmdViews = new SqliteCommand(String.Format(CultureInfo.InvariantCulture, "SELECT * FROM [{0}].[{1}] WHERE [type] LIKE 'view'", strCatalog, master), this))\r
2054       using (SqliteDataReader rdViews = cmdViews.ExecuteReader())\r
2055       {\r
2056         while (rdViews.Read())\r
2057         {\r
2058           if (String.IsNullOrEmpty(strView) || String.Compare(strView, rdViews.GetString(2), true, CultureInfo.InvariantCulture) == 0)\r
2059           {\r
2060             using (SqliteCommand cmdViewSelect = new SqliteCommand(String.Format(CultureInfo.InvariantCulture, "SELECT * FROM [{0}].[{1}]", strCatalog, rdViews.GetString(2)), this))\r
2061             {\r
2062               strSql = rdViews.GetString(4).Replace('\r', ' ').Replace('\n', ' ').Replace('\t', ' ');\r
2063               n = CultureInfo.InvariantCulture.CompareInfo.IndexOf(strSql, " AS ", CompareOptions.IgnoreCase);\r
2064               if (n < 0)\r
2065                 continue;\r
2066 \r
2067               strSql = strSql.Substring(n + 4);\r
2068 \r
2069               using (SqliteCommand cmd = new SqliteCommand(strSql, this))\r
2070               using (SqliteDataReader rdViewSelect = cmdViewSelect.ExecuteReader(CommandBehavior.SchemaOnly))\r
2071               using (SqliteDataReader rd = (SqliteDataReader)cmd.ExecuteReader(CommandBehavior.SchemaOnly))\r
2072               using (DataTable tblSchemaView = rdViewSelect.GetSchemaTable(false, false))\r
2073               using (DataTable tblSchema = rd.GetSchemaTable(false, false))\r
2074               {\r
2075                 for (n = 0; n < tblSchema.Rows.Count; n++)\r
2076                 {\r
2077                   viewRow = tblSchemaView.Rows[n];\r
2078                   schemaRow = tblSchema.Rows[n];\r
2079 \r
2080                   if (String.Compare(viewRow[SchemaTableColumn.ColumnName].ToString(), strColumn, true, CultureInfo.InvariantCulture) == 0\r
2081                     || strColumn == null)\r
2082                   {\r
2083                     row = tbl.NewRow();\r
2084 \r
2085                     row["VIEW_CATALOG"] = strCatalog;\r
2086                     row["VIEW_NAME"] = rdViews.GetString(2);\r
2087                     row["TABLE_CATALOG"] = strCatalog;\r
2088                     row["TABLE_SCHEMA"] = schemaRow[SchemaTableColumn.BaseSchemaName];\r
2089                     row["TABLE_NAME"] = schemaRow[SchemaTableColumn.BaseTableName];\r
2090                     row["COLUMN_NAME"] = schemaRow[SchemaTableColumn.BaseColumnName];\r
2091                     row["VIEW_COLUMN_NAME"] = viewRow[SchemaTableColumn.ColumnName];\r
2092                     row["COLUMN_HASDEFAULT"] = (viewRow[SchemaTableOptionalColumn.DefaultValue] != DBNull.Value);\r
2093                     row["COLUMN_DEFAULT"] = viewRow[SchemaTableOptionalColumn.DefaultValue];\r
2094                     row["ORDINAL_POSITION"] = viewRow[SchemaTableColumn.ColumnOrdinal];\r
2095                     row["IS_NULLABLE"] = viewRow[SchemaTableColumn.AllowDBNull];\r
2096                     row["DATA_TYPE"] = viewRow["DataTypeName"]; // SqliteConvert.DbTypeToType((DbType)viewRow[SchemaTableColumn.ProviderType]).ToString();\r
2097                     row["EDM_TYPE"] = SqliteConvert.DbTypeToTypeName((DbType)viewRow[SchemaTableColumn.ProviderType]).ToString().ToLower(CultureInfo.InvariantCulture);\r
2098                     row["CHARACTER_MAXIMUM_LENGTH"] = viewRow[SchemaTableColumn.ColumnSize];\r
2099                     row["TABLE_SCHEMA"] = viewRow[SchemaTableColumn.BaseSchemaName];\r
2100                     row["PRIMARY_KEY"] = viewRow[SchemaTableColumn.IsKey];\r
2101                     row["AUTOINCREMENT"] = viewRow[SchemaTableOptionalColumn.IsAutoIncrement];\r
2102                     row["COLLATION_NAME"] = viewRow["CollationType"];\r
2103                     row["UNIQUE"] = viewRow[SchemaTableColumn.IsUnique];\r
2104                     tbl.Rows.Add(row);\r
2105                   }\r
2106                 }\r
2107               }\r
2108             }\r
2109           }\r
2110         }\r
2111       }\r
2112 \r
2113       tbl.EndLoadData();\r
2114       tbl.AcceptChanges();\r
2115 \r
2116       return tbl;\r
2117     }\r
2118 \r
2119     /// <summary>\r
2120     /// Retrieves foreign key information from the specified set of filters\r
2121     /// </summary>\r
2122     /// <param name="strCatalog">An optional catalog to restrict results on</param>\r
2123     /// <param name="strTable">An optional table to restrict results on</param>\r
2124     /// <param name="strKeyName">An optional foreign key name to restrict results on</param>\r
2125     /// <returns>A DataTable with the results of the query</returns>\r
2126     private DataTable Schema_ForeignKeys(string strCatalog, string strTable, string strKeyName)\r
2127     {\r
2128       DataTable tbl = new DataTable("ForeignKeys");\r
2129       DataRow row;\r
2130 \r
2131       tbl.Locale = CultureInfo.InvariantCulture;\r
2132       tbl.Columns.Add("CONSTRAINT_CATALOG", typeof(string));\r
2133       tbl.Columns.Add("CONSTRAINT_SCHEMA", typeof(string));\r
2134       tbl.Columns.Add("CONSTRAINT_NAME", typeof(string));\r
2135       tbl.Columns.Add("TABLE_CATALOG", typeof(string));\r
2136       tbl.Columns.Add("TABLE_SCHEMA", typeof(string));\r
2137       tbl.Columns.Add("TABLE_NAME", typeof(string));\r
2138       tbl.Columns.Add("CONSTRAINT_TYPE", typeof(string));\r
2139       tbl.Columns.Add("IS_DEFERRABLE", typeof(bool));\r
2140       tbl.Columns.Add("INITIALLY_DEFERRED", typeof(bool));\r
2141       tbl.Columns.Add("FKEY_FROM_COLUMN", typeof(string));\r
2142       tbl.Columns.Add("FKEY_FROM_ORDINAL_POSITION", typeof(int));\r
2143       tbl.Columns.Add("FKEY_TO_CATALOG", typeof(string));\r
2144       tbl.Columns.Add("FKEY_TO_SCHEMA", typeof(string));\r
2145       tbl.Columns.Add("FKEY_TO_TABLE", typeof(string));\r
2146       tbl.Columns.Add("FKEY_TO_COLUMN", typeof(string));\r
2147 \r
2148       if (String.IsNullOrEmpty(strCatalog)) strCatalog = "main";\r
2149 \r
2150       string master = (String.Compare(strCatalog, "temp", true, CultureInfo.InvariantCulture) == 0) ? _tempmasterdb : _masterdb;\r
2151 \r
2152       tbl.BeginLoadData();\r
2153 \r
2154       using (SqliteCommand cmdTables = new SqliteCommand(String.Format(CultureInfo.InvariantCulture, "SELECT * FROM [{0}].[{1}] WHERE [type] LIKE 'table'", strCatalog, master), this))\r
2155       using (SqliteDataReader rdTables = cmdTables.ExecuteReader())\r
2156       {\r
2157         while (rdTables.Read())\r
2158         {\r
2159           if (String.IsNullOrEmpty(strTable) || String.Compare(strTable, rdTables.GetString(2), true, CultureInfo.InvariantCulture) == 0)\r
2160           {\r
2161             try\r
2162             {\r
2163               using (SqliteCommandBuilder builder = new SqliteCommandBuilder())\r
2164               //using (SqliteCommand cmdTable = new SqliteCommand(String.Format(CultureInfo.InvariantCulture, "SELECT * FROM [{0}].[{1}]", strCatalog, rdTables.GetString(2)), this))\r
2165               //using (SqliteDataReader rdTable = cmdTable.ExecuteReader(CommandBehavior.SchemaOnly))\r
2166               using (SqliteCommand cmdKey = new SqliteCommand(String.Format(CultureInfo.InvariantCulture, "PRAGMA [{0}].foreign_key_list([{1}])", strCatalog, rdTables.GetString(2)), this))\r
2167               using (SqliteDataReader rdKey = cmdKey.ExecuteReader())\r
2168               {\r
2169                 while (rdKey.Read())\r
2170                 {\r
2171                   row = tbl.NewRow();\r
2172                   row["CONSTRAINT_CATALOG"] = strCatalog;\r
2173                   row["CONSTRAINT_NAME"] = String.Format(CultureInfo.InvariantCulture, "FK_{0}_{1}", rdTables[2], rdKey.GetInt32(0));\r
2174                   row["TABLE_CATALOG"] = strCatalog;\r
2175                   row["TABLE_NAME"] = builder.UnquoteIdentifier(rdTables.GetString(2));\r
2176                   row["CONSTRAINT_TYPE"] = "FOREIGN KEY";\r
2177                   row["IS_DEFERRABLE"] = false;\r
2178                   row["INITIALLY_DEFERRED"] = false;\r
2179                   row["FKEY_FROM_COLUMN"] = builder.UnquoteIdentifier(rdKey[3].ToString());\r
2180                   row["FKEY_TO_CATALOG"] = strCatalog;\r
2181                   row["FKEY_TO_TABLE"] = builder.UnquoteIdentifier(rdKey[2].ToString());\r
2182                   row["FKEY_TO_COLUMN"] = builder.UnquoteIdentifier(rdKey[4].ToString());\r
2183                   row["FKEY_FROM_ORDINAL_POSITION"] = rdKey[1];\r
2184 \r
2185                   if (String.IsNullOrEmpty(strKeyName) || String.Compare(strKeyName, row["CONSTRAINT_NAME"].ToString(), true, CultureInfo.InvariantCulture) == 0)\r
2186                     tbl.Rows.Add(row);\r
2187                 }\r
2188               }\r
2189             }\r
2190             catch (SqliteException)\r
2191             {\r
2192             }\r
2193           }\r
2194         }\r
2195       }\r
2196 \r
2197       tbl.EndLoadData();\r
2198       tbl.AcceptChanges();\r
2199 \r
2200       return tbl;\r
2201     }\r
2202 \r
2203     /// <summary>\r
2204     /// This event is raised whenever SQLite makes an update/delete/insert into the database on\r
2205     /// this connection.  It only applies to the given connection.\r
2206     /// </summary>\r
2207     public event SQLiteUpdateEventHandler Update\r
2208     {\r
2209       add\r
2210       {\r
2211         if (_updateHandler == null)\r
2212         {\r
2213           _updateCallback = new SQLiteUpdateCallback(UpdateCallback);\r
2214           if (_sql != null) _sql.SetUpdateHook(_updateCallback);\r
2215         }\r
2216         _updateHandler += value;\r
2217       }\r
2218       remove\r
2219       {\r
2220         _updateHandler -= value;\r
2221         if (_updateHandler == null)\r
2222         {\r
2223           if (_sql != null) _sql.SetUpdateHook(null);\r
2224           _updateCallback = null;\r
2225         }\r
2226       }\r
2227     }\r
2228 \r
2229     private void UpdateCallback(IntPtr puser, int type, IntPtr database, IntPtr table, Int64 rowid)\r
2230     {\r
2231       _updateHandler(this, new UpdateEventArgs(\r
2232         SQLiteBase.UTF8ToString(database, -1),\r
2233         SQLiteBase.UTF8ToString(table, -1),\r
2234         (UpdateEventType)type,\r
2235         rowid));\r
2236     }\r
2237 \r
2238     /// <summary>\r
2239     /// This event is raised whenever SQLite is committing a transaction.\r
2240     /// Return non-zero to trigger a rollback\r
2241     /// </summary>\r
2242     public event SQLiteCommitHandler Commit\r
2243     {\r
2244       add\r
2245       {\r
2246         if (_commitHandler == null)\r
2247         {\r
2248           _commitCallback = new SQLiteCommitCallback(CommitCallback);\r
2249           if (_sql != null) _sql.SetCommitHook(_commitCallback);\r
2250         }\r
2251         _commitHandler += value;\r
2252       }\r
2253       remove\r
2254       {\r
2255         _commitHandler -= value;\r
2256         if (_commitHandler == null)\r
2257         {\r
2258           if (_sql != null) _sql.SetCommitHook(null);\r
2259           _commitCallback = null;\r
2260         }\r
2261       }\r
2262     }\r
2263 \r
2264     /// <summary>\r
2265     /// This event is raised whenever SQLite is committing a transaction.\r
2266     /// Return non-zero to trigger a rollback\r
2267     /// </summary>\r
2268     public event EventHandler RollBack\r
2269     {\r
2270       add\r
2271       {\r
2272         if (_rollbackHandler == null)\r
2273         {\r
2274           _rollbackCallback = new SQLiteRollbackCallback(RollbackCallback);\r
2275           if (_sql != null) _sql.SetRollbackHook(_rollbackCallback);\r
2276         }\r
2277         _rollbackHandler += value;\r
2278       }\r
2279       remove\r
2280       {\r
2281         _rollbackHandler -= value;\r
2282         if (_rollbackHandler == null)\r
2283         {\r
2284           if (_sql != null) _sql.SetRollbackHook(null);\r
2285           _rollbackCallback = null;\r
2286         }\r
2287       }\r
2288     }\r
2289 \r
2290 \r
2291     private int CommitCallback(IntPtr parg)\r
2292     {\r
2293       CommitEventArgs e = new CommitEventArgs();\r
2294       _commitHandler(this, e);\r
2295       return (e.AbortTransaction == true) ? 1 : 0;\r
2296     }\r
2297 \r
2298     private void RollbackCallback(IntPtr parg)\r
2299     {\r
2300       _rollbackHandler(this, EventArgs.Empty);\r
2301     }\r
2302 \r
2303     // http://www.sqlite.org/c3ref/config.html\r
2304     public static void SetConfig (SQLiteConfig config)\r
2305     {\r
2306       int n = UnsafeNativeMethods.sqlite3_config (config);\r
2307       if (n > 0) throw new SqliteException (n, null);\r
2308     }\r
2309   }\r
2310 \r
2311   /// <summary>\r
2312   /// The I/O file cache flushing behavior for the connection\r
2313   /// </summary>\r
2314   public enum SynchronizationModes\r
2315   {\r
2316     /// <summary>\r
2317     /// Normal file flushing at critical sections of the code\r
2318     /// </summary>\r
2319     Normal = 0,\r
2320     /// <summary>\r
2321     /// Full file flushing after every write operation\r
2322     /// </summary>\r
2323     Full = 1,\r
2324     /// <summary>\r
2325     /// Use the default operating system's file flushing, SQLite does not explicitly flush the file buffers after writing\r
2326     /// </summary>\r
2327     Off = 2,\r
2328   }\r
2329 \r
2330 #if !PLATFORM_COMPACTFRAMEWORK\r
2331   [UnmanagedFunctionPointer(CallingConvention.Cdecl)]\r
2332 #endif\r
2333   internal delegate void SQLiteUpdateCallback(IntPtr puser, int type, IntPtr database, IntPtr table, Int64 rowid);\r
2334 #if !PLATFORM_COMPACTFRAMEWORK\r
2335   [UnmanagedFunctionPointer(CallingConvention.Cdecl)]\r
2336 #endif\r
2337   internal delegate int SQLiteCommitCallback(IntPtr puser);\r
2338 #if !PLATFORM_COMPACTFRAMEWORK\r
2339   [UnmanagedFunctionPointer(CallingConvention.Cdecl)]\r
2340 #endif\r
2341   internal delegate void SQLiteRollbackCallback(IntPtr puser);\r
2342 \r
2343   /// <summary>\r
2344   /// Raised when a transaction is about to be committed.  To roll back a transaction, set the \r
2345   /// rollbackTrans boolean value to true.\r
2346   /// </summary>\r
2347   /// <param name="sender">The connection committing the transaction</param>\r
2348   /// <param name="e">Event arguments on the transaction</param>\r
2349   public delegate void SQLiteCommitHandler(object sender, CommitEventArgs e);\r
2350 \r
2351   /// <summary>\r
2352   /// Raised when data is inserted, updated and deleted on a given connection\r
2353   /// </summary>\r
2354   /// <param name="sender">The connection committing the transaction</param>\r
2355   /// <param name="e">The event parameters which triggered the event</param>\r
2356   public delegate void SQLiteUpdateEventHandler(object sender, UpdateEventArgs e);\r
2357 \r
2358   /// <summary>\r
2359   /// Whenever an update event is triggered on a connection, this enum will indicate\r
2360   /// exactly what type of operation is being performed.\r
2361   /// </summary>\r
2362   public enum UpdateEventType\r
2363   {\r
2364     /// <summary>\r
2365     /// A row is being deleted from the given database and table\r
2366     /// </summary>\r
2367     Delete = 9,\r
2368     /// <summary>\r
2369     /// A row is being inserted into the table.\r
2370     /// </summary>\r
2371     Insert = 18,\r
2372     /// <summary>\r
2373     /// A row is being updated in the table.\r
2374     /// </summary>\r
2375     Update = 23,\r
2376   }\r
2377 \r
2378   /// <summary>\r
2379   /// Passed during an Update callback, these event arguments detail the type of update operation being performed\r
2380   /// on the given connection.\r
2381   /// </summary>\r
2382   public class UpdateEventArgs : EventArgs\r
2383   {\r
2384     /// <summary>\r
2385     /// The name of the database being updated (usually "main" but can be any attached or temporary database)\r
2386     /// </summary>\r
2387     public readonly string Database;\r
2388 \r
2389     /// <summary>\r
2390     /// The name of the table being updated\r
2391     /// </summary>\r
2392     public readonly string Table;\r
2393 \r
2394     /// <summary>\r
2395     /// The type of update being performed (insert/update/delete)\r
2396     /// </summary>\r
2397     public readonly UpdateEventType Event;\r
2398 \r
2399     /// <summary>\r
2400     /// The RowId affected by this update.\r
2401     /// </summary>\r
2402     public readonly Int64 RowId;\r
2403 \r
2404     internal UpdateEventArgs(string database, string table, UpdateEventType eventType, Int64 rowid)\r
2405     {\r
2406       Database = database;\r
2407       Table = table;\r
2408       Event = eventType;\r
2409       RowId = rowid;\r
2410     }\r
2411   }\r
2412 \r
2413   /// <summary>\r
2414   /// Event arguments raised when a transaction is being committed\r
2415   /// </summary>\r
2416   public class CommitEventArgs : EventArgs\r
2417   {\r
2418     internal CommitEventArgs()\r
2419     {\r
2420     }\r
2421 \r
2422     /// <summary>\r
2423     /// Set to true to abort the transaction and trigger a rollback\r
2424     /// </summary>\r
2425     public bool AbortTransaction;\r
2426   }\r
2427 \r
2428 }\r