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