2009-07-11 Michael Barker <mike@middlesoft.co.uk>
[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, "FailIfMissing", Boolean.FalseString)) == false)\r
869           flags |= SQLiteOpenFlagsEnum.Create;\r
870 \r
871         if (SqliteConvert.ToBoolean(FindKey(opts, "Read Only", Boolean.FalseString)) == true)\r
872           flags |= SQLiteOpenFlagsEnum.ReadOnly;\r
873         else\r
874           flags |= SQLiteOpenFlagsEnum.ReadWrite;\r
875 \r
876         _sql.Open(fileName, flags, maxPoolSize, usePooling);\r
877 \r
878         _binaryGuid = (SqliteConvert.ToBoolean(FindKey(opts, "BinaryGUID", Boolean.TrueString)) == true);\r
879 \r
880         string password = FindKey(opts, "Password", null);\r
881 \r
882         if (String.IsNullOrEmpty(password) == false)\r
883           _sql.SetPassword(System.Text.UTF8Encoding.UTF8.GetBytes(password));\r
884         else if (_password != null)\r
885           _sql.SetPassword(_password);\r
886         _password = null;\r
887 \r
888         _dataSource = Path.GetFileNameWithoutExtension(fileName);\r
889 \r
890         OnStateChange(ConnectionState.Open);\r
891         _version++;\r
892 \r
893         using (SqliteCommand cmd = CreateCommand())\r
894         {\r
895           string defValue;\r
896 \r
897           if (fileName != ":memory:")\r
898           {\r
899             defValue = FindKey(opts, "Page Size", "1024");\r
900             if (Convert.ToInt32(defValue, CultureInfo.InvariantCulture) != 1024)\r
901             {\r
902               cmd.CommandText = String.Format(CultureInfo.InvariantCulture, "PRAGMA page_size={0}", defValue);\r
903               cmd.ExecuteNonQuery();\r
904             }\r
905           }\r
906 \r
907           defValue = FindKey(opts, "Max Page Count", "0");\r
908           if (Convert.ToInt32(defValue, CultureInfo.InvariantCulture) != 0)\r
909           {\r
910             cmd.CommandText = String.Format(CultureInfo.InvariantCulture, "PRAGMA max_page_count={0}", defValue);\r
911             cmd.ExecuteNonQuery();\r
912           }\r
913 \r
914           defValue = FindKey(opts, "Legacy Format", Boolean.FalseString);\r
915           cmd.CommandText = String.Format(CultureInfo.InvariantCulture, "PRAGMA legacy_file_format={0}", SqliteConvert.ToBoolean(defValue) == true ? "ON" : "OFF");\r
916           cmd.ExecuteNonQuery();\r
917 \r
918           defValue = FindKey(opts, "Synchronous", "Normal");\r
919           if (String.Compare(defValue, "Full", StringComparison.OrdinalIgnoreCase) != 0)\r
920           {\r
921             cmd.CommandText = String.Format(CultureInfo.InvariantCulture, "PRAGMA synchronous={0}", defValue);\r
922             cmd.ExecuteNonQuery();\r
923           }\r
924 \r
925           defValue = FindKey(opts, "Cache Size", "2000");\r
926           if (Convert.ToInt32(defValue, CultureInfo.InvariantCulture) != 2000)\r
927           {\r
928             cmd.CommandText = String.Format(CultureInfo.InvariantCulture, "PRAGMA cache_size={0}", defValue);\r
929             cmd.ExecuteNonQuery();\r
930           }\r
931 \r
932           defValue = FindKey(opts, "Journal Mode", "Delete");\r
933           if (String.Compare(defValue, "Default", StringComparison.OrdinalIgnoreCase) != 0)\r
934           {\r
935             cmd.CommandText = String.Format(CultureInfo.InvariantCulture, "PRAGMA journal_mode={0}", defValue);\r
936             cmd.ExecuteNonQuery();\r
937           }\r
938         }\r
939 \r
940         if (_commitHandler != null)\r
941           _sql.SetCommitHook(_commitCallback);\r
942 \r
943         if (_updateHandler != null)\r
944           _sql.SetUpdateHook(_updateCallback);\r
945 \r
946         if (_rollbackHandler != null)\r
947           _sql.SetRollbackHook(_rollbackCallback);\r
948 \r
949 #if !PLATFORM_COMPACTFRAMEWORK\r
950         if (global::System.Transactions.Transaction.Current != null && SqliteConvert.ToBoolean(FindKey(opts, "Enlist", Boolean.TrueString)) == true)\r
951                 EnlistTransaction(global::System.Transactions.Transaction.Current);\r
952 #endif\r
953       }\r
954       catch (SqliteException)\r
955       {\r
956         Close();\r
957         throw;\r
958       }\r
959     }\r
960 \r
961     /// <summary>\r
962     /// Gets/sets the default command timeout for newly-created commands.  This is especially useful for \r
963     /// commands used internally such as inside a SqliteTransaction, where setting the timeout is not possible.\r
964     /// This can also be set in the ConnectionString with "Default Timeout"\r
965     /// </summary>\r
966     public int DefaultTimeout\r
967     {\r
968       get { return _defaultTimeout; }\r
969       set { _defaultTimeout = value; }\r
970     }\r
971 \r
972     /// <summary>\r
973     /// Returns the version of the underlying SQLite database engine\r
974     /// </summary>\r
975 #if !PLATFORM_COMPACTFRAMEWORK\r
976     [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]\r
977 #endif\r
978     public override string ServerVersion\r
979     {\r
980       get\r
981       {\r
982         if (_connectionState != ConnectionState.Open)\r
983           throw new InvalidOperationException();\r
984 \r
985         return _sql.Version;\r
986       }\r
987     }\r
988 \r
989     /// <summary>\r
990     /// Returns the version of the underlying SQLite database engine\r
991     /// </summary>\r
992     public static string SQLiteVersion\r
993     {\r
994       get { return SQLite3.SQLiteVersion; }\r
995     }\r
996 \r
997     /// <summary>\r
998     /// Returns the state of the connection.\r
999     /// </summary>\r
1000 #if !PLATFORM_COMPACTFRAMEWORK\r
1001     [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]\r
1002 #endif\r
1003     public override ConnectionState State\r
1004     {\r
1005       get\r
1006       {\r
1007         return _connectionState;\r
1008       }\r
1009     }\r
1010 \r
1011     /// <summary>\r
1012     /// Change the password (or assign a password) to an open database.\r
1013     /// </summary>\r
1014     /// <remarks>\r
1015     /// No readers or writers may be active for this process.  The database must already be open\r
1016     /// and if it already was password protected, the existing password must already have been supplied.\r
1017     /// </remarks>\r
1018     /// <param name="newPassword">The new password to assign to the database</param>\r
1019     public void ChangePassword(string newPassword)\r
1020     {\r
1021       ChangePassword(String.IsNullOrEmpty(newPassword) ? null : System.Text.UTF8Encoding.UTF8.GetBytes(newPassword));\r
1022     }\r
1023 \r
1024     /// <summary>\r
1025     /// Change the password (or assign a password) to an open database.\r
1026     /// </summary>\r
1027     /// <remarks>\r
1028     /// No readers or writers may be active for this process.  The database must already be open\r
1029     /// and if it already was password protected, the existing password must already have been supplied.\r
1030     /// </remarks>\r
1031     /// <param name="newPassword">The new password to assign to the database</param>\r
1032     public void ChangePassword(byte[] newPassword)\r
1033     {\r
1034       if (_connectionState != ConnectionState.Open)\r
1035         throw new InvalidOperationException("Database must be opened before changing the password.");\r
1036 \r
1037       _sql.ChangePassword(newPassword);\r
1038     }\r
1039 \r
1040     /// <summary>\r
1041     /// Sets the password for a password-protected database.  A password-protected database is\r
1042     /// unusable for any operation until the password has been set.\r
1043     /// </summary>\r
1044     /// <param name="databasePassword">The password for the database</param>\r
1045     public void SetPassword(string databasePassword)\r
1046     {\r
1047       SetPassword(String.IsNullOrEmpty(databasePassword) ? null : System.Text.UTF8Encoding.UTF8.GetBytes(databasePassword));\r
1048     }\r
1049 \r
1050     /// <summary>\r
1051     /// Sets the password for a password-protected database.  A password-protected database is\r
1052     /// unusable for any operation until the password has been set.\r
1053     /// </summary>\r
1054     /// <param name="databasePassword">The password for the database</param>\r
1055     public void SetPassword(byte[] databasePassword)\r
1056     {\r
1057       if (_connectionState != ConnectionState.Closed)\r
1058         throw new InvalidOperationException("Password can only be set before the database is opened.");\r
1059 \r
1060       if (databasePassword != null)\r
1061         if (databasePassword.Length == 0) databasePassword = null;\r
1062 \r
1063       _password = databasePassword;\r
1064     }\r
1065 \r
1066     /// <summary>\r
1067     /// Expand the filename of the data source, resolving the |DataDirectory| macro as appropriate.\r
1068     /// </summary>\r
1069     /// <param name="sourceFile">The database filename to expand</param>\r
1070     /// <returns>The expanded path and filename of the filename</returns>\r
1071     private string ExpandFileName(string sourceFile)\r
1072     {\r
1073       if (String.IsNullOrEmpty(sourceFile)) return sourceFile;\r
1074 \r
1075       if (sourceFile.StartsWith(_dataDirectory, StringComparison.OrdinalIgnoreCase))\r
1076       {\r
1077         string dataDirectory;\r
1078 \r
1079 #if PLATFORM_COMPACTFRAMEWORK\r
1080         dataDirectory = Path.GetDirectoryName(System.Reflection.Assembly.GetCallingAssembly().GetName().CodeBase);\r
1081 #else\r
1082         dataDirectory = AppDomain.CurrentDomain.GetData("DataDirectory") as string;\r
1083         if (String.IsNullOrEmpty(dataDirectory))\r
1084           dataDirectory = AppDomain.CurrentDomain.BaseDirectory;\r
1085 #endif\r
1086 \r
1087         if (sourceFile.Length > _dataDirectory.Length)\r
1088         {\r
1089           if (sourceFile[_dataDirectory.Length] == Path.DirectorySeparatorChar ||\r
1090               sourceFile[_dataDirectory.Length] == Path.AltDirectorySeparatorChar)\r
1091             sourceFile = sourceFile.Remove(_dataDirectory.Length, 1);\r
1092         }\r
1093         sourceFile = Path.Combine(dataDirectory, sourceFile.Substring(_dataDirectory.Length));\r
1094       }\r
1095 \r
1096 #if !PLATFORM_COMPACTFRAMEWORK\r
1097       sourceFile = Path.GetFullPath(sourceFile);\r
1098 #endif\r
1099 \r
1100       return sourceFile;\r
1101     }\r
1102 \r
1103     ///<overloads>\r
1104     /// The following commands are used to extract schema information out of the database.  Valid schema types are:\r
1105     /// <list type="bullet">\r
1106     /// <item>\r
1107     /// <description>MetaDataCollections</description>\r
1108     /// </item>\r
1109     /// <item>\r
1110     /// <description>DataSourceInformation</description>\r
1111     /// </item>\r
1112     /// <item>\r
1113     /// <description>Catalogs</description>\r
1114     /// </item>\r
1115     /// <item>\r
1116     /// <description>Columns</description>\r
1117     /// </item>\r
1118     /// <item>\r
1119     /// <description>ForeignKeys</description>\r
1120     /// </item>\r
1121     /// <item>\r
1122     /// <description>Indexes</description>\r
1123     /// </item>\r
1124     /// <item>\r
1125     /// <description>IndexColumns</description>\r
1126     /// </item>\r
1127     /// <item>\r
1128     /// <description>Tables</description>\r
1129     /// </item>\r
1130     /// <item>\r
1131     /// <description>Views</description>\r
1132     /// </item>\r
1133     /// <item>\r
1134     /// <description>ViewColumns</description>\r
1135     /// </item>\r
1136     /// </list>\r
1137     /// </overloads>\r
1138     /// <summary>\r
1139     /// Returns the MetaDataCollections schema\r
1140     /// </summary>\r
1141     /// <returns>A DataTable of the MetaDataCollections schema</returns>\r
1142     public override DataTable GetSchema()\r
1143     {\r
1144       return GetSchema("MetaDataCollections", null);\r
1145     }\r
1146 \r
1147     /// <summary>\r
1148     /// Returns schema information of the specified collection\r
1149     /// </summary>\r
1150     /// <param name="collectionName">The schema collection to retrieve</param>\r
1151     /// <returns>A DataTable of the specified collection</returns>\r
1152     public override DataTable GetSchema(string collectionName)\r
1153     {\r
1154       return GetSchema(collectionName, new string[0]);\r
1155     }\r
1156 \r
1157     /// <summary>\r
1158     /// Retrieves schema information using the specified constraint(s) for the specified collection\r
1159     /// </summary>\r
1160     /// <param name="collectionName">The collection to retrieve</param>\r
1161     /// <param name="restrictionValues">The restrictions to impose</param>\r
1162     /// <returns>A DataTable of the specified collection</returns>\r
1163     public override DataTable GetSchema(string collectionName, string[] restrictionValues)\r
1164     {\r
1165       if (_connectionState != ConnectionState.Open)\r
1166         throw new InvalidOperationException();\r
1167 \r
1168       string[] parms = new string[5];\r
1169 \r
1170       if (restrictionValues == null) restrictionValues = new string[0];\r
1171       restrictionValues.CopyTo(parms, 0);\r
1172 \r
1173       switch (collectionName.ToUpper(CultureInfo.InvariantCulture))\r
1174       {\r
1175         case "METADATACOLLECTIONS":\r
1176           return Schema_MetaDataCollections();\r
1177         case "DATASOURCEINFORMATION":\r
1178           return Schema_DataSourceInformation();\r
1179         case "DATATYPES":\r
1180           return Schema_DataTypes();\r
1181         case "COLUMNS":\r
1182         case "TABLECOLUMNS":\r
1183           return Schema_Columns(parms[0], parms[2], parms[3]);\r
1184         case "INDEXES":\r
1185           return Schema_Indexes(parms[0], parms[2], parms[3]);\r
1186         case "TRIGGERS":\r
1187           return Schema_Triggers(parms[0], parms[2], parms[3]);\r
1188         case "INDEXCOLUMNS":\r
1189           return Schema_IndexColumns(parms[0], parms[2], parms[3], parms[4]);\r
1190         case "TABLES":\r
1191           return Schema_Tables(parms[0], parms[2], parms[3]);\r
1192         case "VIEWS":\r
1193           return Schema_Views(parms[0], parms[2]);\r
1194         case "VIEWCOLUMNS":\r
1195           return Schema_ViewColumns(parms[0], parms[2], parms[3]);\r
1196         case "FOREIGNKEYS":\r
1197           return Schema_ForeignKeys(parms[0], parms[2], parms[3]);\r
1198         case "CATALOGS":\r
1199           return Schema_Catalogs(parms[0]);\r
1200         case "RESERVEDWORDS":\r
1201           return Schema_ReservedWords();\r
1202       }\r
1203       throw new NotSupportedException();\r
1204     }\r
1205 \r
1206     private static DataTable Schema_ReservedWords()\r
1207     {\r
1208       DataTable tbl = new DataTable("MetaDataCollections");\r
1209 \r
1210       tbl.Locale = CultureInfo.InvariantCulture;\r
1211       tbl.Columns.Add("ReservedWord", typeof(string));\r
1212       tbl.Columns.Add("MaximumVersion", typeof(string));\r
1213       tbl.Columns.Add("MinimumVersion", typeof(string));\r
1214 \r
1215       tbl.BeginLoadData();\r
1216       DataRow row;\r
1217       foreach (string word in SR.Keywords.Split(new char[] { ',' }))\r
1218       {\r
1219         row = tbl.NewRow();\r
1220         row[0] = word;\r
1221         tbl.Rows.Add(row);\r
1222       }\r
1223 \r
1224       tbl.AcceptChanges();\r
1225       tbl.EndLoadData();\r
1226 \r
1227       return tbl;\r
1228     }\r
1229 \r
1230     /// <summary>\r
1231     /// Builds a MetaDataCollections schema datatable\r
1232     /// </summary>\r
1233     /// <returns>DataTable</returns>\r
1234     private static DataTable Schema_MetaDataCollections()\r
1235     {\r
1236       DataTable tbl = new DataTable("MetaDataCollections");\r
1237 \r
1238       tbl.Locale = CultureInfo.InvariantCulture;\r
1239       tbl.Columns.Add("CollectionName", typeof(string));\r
1240       tbl.Columns.Add("NumberOfRestrictions", typeof(int));\r
1241       tbl.Columns.Add("NumberOfIdentifierParts", typeof(int));\r
1242 \r
1243       tbl.BeginLoadData();\r
1244 \r
1245       StringReader reader = new StringReader(SR.MetaDataCollections);\r
1246       tbl.ReadXml(reader);\r
1247       reader.Close();\r
1248 \r
1249       tbl.AcceptChanges();\r
1250       tbl.EndLoadData();\r
1251 \r
1252       return tbl;\r
1253     }\r
1254 \r
1255     /// <summary>\r
1256     /// Builds a DataSourceInformation datatable\r
1257     /// </summary>\r
1258     /// <returns>DataTable</returns>\r
1259     private DataTable Schema_DataSourceInformation()\r
1260     {\r
1261       DataTable tbl = new DataTable("DataSourceInformation");\r
1262       DataRow row;\r
1263 \r
1264       tbl.Locale = CultureInfo.InvariantCulture;\r
1265       tbl.Columns.Add(DbMetaDataColumnNames.CompositeIdentifierSeparatorPattern, typeof(string));\r
1266       tbl.Columns.Add(DbMetaDataColumnNames.DataSourceProductName, typeof(string));\r
1267       tbl.Columns.Add(DbMetaDataColumnNames.DataSourceProductVersion, typeof(string));\r
1268       tbl.Columns.Add(DbMetaDataColumnNames.DataSourceProductVersionNormalized, typeof(string));\r
1269       tbl.Columns.Add(DbMetaDataColumnNames.GroupByBehavior, typeof(int));\r
1270       tbl.Columns.Add(DbMetaDataColumnNames.IdentifierPattern, typeof(string));\r
1271       tbl.Columns.Add(DbMetaDataColumnNames.IdentifierCase, typeof(int));\r
1272       tbl.Columns.Add(DbMetaDataColumnNames.OrderByColumnsInSelect, typeof(bool));\r
1273       tbl.Columns.Add(DbMetaDataColumnNames.ParameterMarkerFormat, typeof(string));\r
1274       tbl.Columns.Add(DbMetaDataColumnNames.ParameterMarkerPattern, typeof(string));\r
1275       tbl.Columns.Add(DbMetaDataColumnNames.ParameterNameMaxLength, typeof(int));\r
1276       tbl.Columns.Add(DbMetaDataColumnNames.ParameterNamePattern, typeof(string));\r
1277       tbl.Columns.Add(DbMetaDataColumnNames.QuotedIdentifierPattern, typeof(string));\r
1278       tbl.Columns.Add(DbMetaDataColumnNames.QuotedIdentifierCase, typeof(int));\r
1279       tbl.Columns.Add(DbMetaDataColumnNames.StatementSeparatorPattern, typeof(string));\r
1280       tbl.Columns.Add(DbMetaDataColumnNames.StringLiteralPattern, typeof(string));\r
1281       tbl.Columns.Add(DbMetaDataColumnNames.SupportedJoinOperators, typeof(int));\r
1282 \r
1283       tbl.BeginLoadData();\r
1284 \r
1285       row = tbl.NewRow();\r
1286       row.ItemArray = new object[] {\r
1287         null,\r
1288         "SQLite",\r
1289         _sql.Version,\r
1290         _sql.Version,\r
1291         3,\r
1292         @"(^\[\p{Lo}\p{Lu}\p{Ll}_@#][\p{Lo}\p{Lu}\p{Ll}\p{Nd}@$#_]*$)|(^\[[^\]\0]|\]\]+\]$)|(^\""[^\""\0]|\""\""+\""$)",\r
1293         1,\r
1294         false,\r
1295         "{0}",\r
1296         @"@[\p{Lo}\p{Lu}\p{Ll}\p{Lm}_@#][\p{Lo}\p{Lu}\p{Ll}\p{Lm}\p{Nd}\uff3f_@#\$]*(?=\s+|$)",\r
1297         255,\r
1298         @"^[\p{Lo}\p{Lu}\p{Ll}\p{Lm}_@#][\p{Lo}\p{Lu}\p{Ll}\p{Lm}\p{Nd}\uff3f_@#\$]*(?=\s+|$)",\r
1299         @"(([^\[]|\]\])*)",\r
1300         1,\r
1301         ";",\r
1302         @"'(([^']|'')*)'",\r
1303         15\r
1304       };\r
1305       tbl.Rows.Add(row);\r
1306 \r
1307       tbl.AcceptChanges();\r
1308       tbl.EndLoadData();\r
1309 \r
1310       return tbl;\r
1311     }\r
1312 \r
1313     /// <summary>\r
1314     /// Build a Columns schema\r
1315     /// </summary>\r
1316     /// <param name="strCatalog">The catalog (attached database) to query, can be null</param>\r
1317     /// <param name="strTable">The table to retrieve schema information for, must not be null</param>\r
1318     /// <param name="strColumn">The column to retrieve schema information for, can be null</param>\r
1319     /// <returns>DataTable</returns>\r
1320     private DataTable Schema_Columns(string strCatalog, string strTable, string strColumn)\r
1321     {\r
1322       DataTable tbl = new DataTable("Columns");\r
1323       DataRow row;\r
1324 \r
1325       tbl.Locale = CultureInfo.InvariantCulture;\r
1326       tbl.Columns.Add("TABLE_CATALOG", typeof(string));\r
1327       tbl.Columns.Add("TABLE_SCHEMA", typeof(string));\r
1328       tbl.Columns.Add("TABLE_NAME", typeof(string));\r
1329       tbl.Columns.Add("COLUMN_NAME", typeof(string));\r
1330       tbl.Columns.Add("COLUMN_GUID", typeof(Guid));\r
1331       tbl.Columns.Add("COLUMN_PROPID", typeof(long));\r
1332       tbl.Columns.Add("ORDINAL_POSITION", typeof(int));\r
1333       tbl.Columns.Add("COLUMN_HASDEFAULT", typeof(bool));\r
1334       tbl.Columns.Add("COLUMN_DEFAULT", typeof(string));\r
1335       tbl.Columns.Add("COLUMN_FLAGS", typeof(long));\r
1336       tbl.Columns.Add("IS_NULLABLE", typeof(bool));\r
1337       tbl.Columns.Add("DATA_TYPE", typeof(string));\r
1338       tbl.Columns.Add("TYPE_GUID", typeof(Guid));\r
1339       tbl.Columns.Add("CHARACTER_MAXIMUM_LENGTH", typeof(int));\r
1340       tbl.Columns.Add("CHARACTER_OCTET_LENGTH", typeof(int));\r
1341       tbl.Columns.Add("NUMERIC_PRECISION", typeof(int));\r
1342       tbl.Columns.Add("NUMERIC_SCALE", typeof(int));\r
1343       tbl.Columns.Add("DATETIME_PRECISION", typeof(long));\r
1344       tbl.Columns.Add("CHARACTER_SET_CATALOG", typeof(string));\r
1345       tbl.Columns.Add("CHARACTER_SET_SCHEMA", typeof(string));\r
1346       tbl.Columns.Add("CHARACTER_SET_NAME", typeof(string));\r
1347       tbl.Columns.Add("COLLATION_CATALOG", typeof(string));\r
1348       tbl.Columns.Add("COLLATION_SCHEMA", typeof(string));\r
1349       tbl.Columns.Add("COLLATION_NAME", typeof(string));\r
1350       tbl.Columns.Add("DOMAIN_CATALOG", typeof(string));\r
1351       tbl.Columns.Add("DOMAIN_NAME", typeof(string));\r
1352       tbl.Columns.Add("DESCRIPTION", typeof(string));\r
1353       tbl.Columns.Add("PRIMARY_KEY", typeof(bool));\r
1354       tbl.Columns.Add("EDM_TYPE", typeof(string));\r
1355       tbl.Columns.Add("AUTOINCREMENT", typeof(bool));\r
1356       tbl.Columns.Add("UNIQUE", typeof(bool));\r
1357 \r
1358       tbl.BeginLoadData();\r
1359 \r
1360       if (String.IsNullOrEmpty(strCatalog)) strCatalog = "main";\r
1361 \r
1362       string master = (String.Compare(strCatalog, "temp", true, CultureInfo.InvariantCulture) == 0) ? _tempmasterdb : _masterdb;\r
1363 \r
1364       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
1365       using (SqliteDataReader rdTables = cmdTables.ExecuteReader())\r
1366       {\r
1367         while (rdTables.Read())\r
1368         {\r
1369           if (String.IsNullOrEmpty(strTable) || String.Compare(strTable, rdTables.GetString(2), true, CultureInfo.InvariantCulture) == 0)\r
1370           {\r
1371             try\r
1372             {\r
1373               using (SqliteCommand cmd = new SqliteCommand(String.Format(CultureInfo.InvariantCulture, "SELECT * FROM [{0}].[{1}]", strCatalog, rdTables.GetString(2)), this))\r
1374               using (SqliteDataReader rd = (SqliteDataReader)cmd.ExecuteReader(CommandBehavior.SchemaOnly))\r
1375               using (DataTable tblSchema = rd.GetSchemaTable(true, true))\r
1376               {\r
1377                 foreach (DataRow schemaRow in tblSchema.Rows)\r
1378                 {\r
1379                   if (String.Compare(schemaRow[SchemaTableColumn.ColumnName].ToString(), strColumn, true, CultureInfo.InvariantCulture) == 0\r
1380                     || strColumn == null)\r
1381                   {\r
1382                     row = tbl.NewRow();\r
1383 \r
1384                     row["NUMERIC_PRECISION"] = schemaRow[SchemaTableColumn.NumericPrecision];\r
1385                     row["NUMERIC_SCALE"] = schemaRow[SchemaTableColumn.NumericScale];\r
1386                     row["TABLE_NAME"] = rdTables.GetString(2);\r
1387                     row["COLUMN_NAME"] = schemaRow[SchemaTableColumn.ColumnName];\r
1388                     row["TABLE_CATALOG"] = strCatalog;\r
1389                     row["ORDINAL_POSITION"] = schemaRow[SchemaTableColumn.ColumnOrdinal];\r
1390                     row["COLUMN_HASDEFAULT"] = (schemaRow[SchemaTableOptionalColumn.DefaultValue] != DBNull.Value);\r
1391                     row["COLUMN_DEFAULT"] = schemaRow[SchemaTableOptionalColumn.DefaultValue];\r
1392                     row["IS_NULLABLE"] = schemaRow[SchemaTableColumn.AllowDBNull];\r
1393                     row["DATA_TYPE"] = schemaRow["DataTypeName"].ToString().ToLower(CultureInfo.InvariantCulture);\r
1394                     row["EDM_TYPE"] = SqliteConvert.DbTypeToTypeName((DbType)schemaRow[SchemaTableColumn.ProviderType]).ToString().ToLower(CultureInfo.InvariantCulture);\r
1395                     row["CHARACTER_MAXIMUM_LENGTH"] = schemaRow[SchemaTableColumn.ColumnSize];\r
1396                     row["TABLE_SCHEMA"] = schemaRow[SchemaTableColumn.BaseSchemaName];\r
1397                     row["PRIMARY_KEY"] = schemaRow[SchemaTableColumn.IsKey];\r
1398                     row["AUTOINCREMENT"] = schemaRow[SchemaTableOptionalColumn.IsAutoIncrement];\r
1399                     row["COLLATION_NAME"] = schemaRow["CollationType"];\r
1400                     row["UNIQUE"] = schemaRow[SchemaTableColumn.IsUnique];\r
1401                     tbl.Rows.Add(row);\r
1402                   }\r
1403                 }\r
1404               }\r
1405             }\r
1406             catch(SqliteException)\r
1407             {\r
1408             }\r
1409           }\r
1410         }\r
1411       }\r
1412 \r
1413       tbl.AcceptChanges();\r
1414       tbl.EndLoadData();\r
1415 \r
1416       return tbl;\r
1417     }\r
1418 \r
1419     /// <summary>\r
1420     /// Returns index information for the given database and catalog\r
1421     /// </summary>\r
1422     /// <param name="strCatalog">The catalog (attached database) to query, can be null</param>\r
1423     /// <param name="strIndex">The name of the index to retrieve information for, can be null</param>\r
1424     /// <param name="strTable">The table to retrieve index information for, can be null</param>\r
1425     /// <returns>DataTable</returns>\r
1426     private DataTable Schema_Indexes(string strCatalog, string strTable, string strIndex)\r
1427     {\r
1428       DataTable tbl = new DataTable("Indexes");\r
1429       DataRow row;\r
1430       List<int> primaryKeys = new List<int>();\r
1431       bool maybeRowId;\r
1432 \r
1433       tbl.Locale = CultureInfo.InvariantCulture;\r
1434       tbl.Columns.Add("TABLE_CATALOG", typeof(string));\r
1435       tbl.Columns.Add("TABLE_SCHEMA", typeof(string));\r
1436       tbl.Columns.Add("TABLE_NAME", typeof(string));\r
1437       tbl.Columns.Add("INDEX_CATALOG", typeof(string));\r
1438       tbl.Columns.Add("INDEX_SCHEMA", typeof(string));\r
1439       tbl.Columns.Add("INDEX_NAME", typeof(string));\r
1440       tbl.Columns.Add("PRIMARY_KEY", typeof(bool));\r
1441       tbl.Columns.Add("UNIQUE", typeof(bool));\r
1442       tbl.Columns.Add("CLUSTERED", typeof(bool));\r
1443       tbl.Columns.Add("TYPE", typeof(int));\r
1444       tbl.Columns.Add("FILL_FACTOR", typeof(int));\r
1445       tbl.Columns.Add("INITIAL_SIZE", typeof(int));\r
1446       tbl.Columns.Add("NULLS", typeof(int));\r
1447       tbl.Columns.Add("SORT_BOOKMARKS", typeof(bool));\r
1448       tbl.Columns.Add("AUTO_UPDATE", typeof(bool));\r
1449       tbl.Columns.Add("NULL_COLLATION", typeof(int));\r
1450       tbl.Columns.Add("ORDINAL_POSITION", typeof(int));\r
1451       tbl.Columns.Add("COLUMN_NAME", typeof(string));\r
1452       tbl.Columns.Add("COLUMN_GUID", typeof(Guid));\r
1453       tbl.Columns.Add("COLUMN_PROPID", typeof(long));\r
1454       tbl.Columns.Add("COLLATION", typeof(short));\r
1455       tbl.Columns.Add("CARDINALITY", typeof(Decimal));\r
1456       tbl.Columns.Add("PAGES", typeof(int));\r
1457       tbl.Columns.Add("FILTER_CONDITION", typeof(string));\r
1458       tbl.Columns.Add("INTEGRATED", typeof(bool));\r
1459       tbl.Columns.Add("INDEX_DEFINITION", typeof(string));\r
1460 \r
1461       tbl.BeginLoadData();\r
1462 \r
1463       if (String.IsNullOrEmpty(strCatalog)) strCatalog = "main";\r
1464 \r
1465       string master = (String.Compare(strCatalog, "temp", true, CultureInfo.InvariantCulture) == 0) ? _tempmasterdb : _masterdb;\r
1466       \r
1467       using (SqliteCommand cmdTables = new SqliteCommand(String.Format(CultureInfo.InvariantCulture, "SELECT * FROM [{0}].[{1}] WHERE [type] LIKE 'table'", strCatalog, master), this))\r
1468       using (SqliteDataReader rdTables = cmdTables.ExecuteReader())\r
1469       {\r
1470         while (rdTables.Read())\r
1471         {\r
1472           maybeRowId = false;\r
1473           primaryKeys.Clear();\r
1474           if (String.IsNullOrEmpty(strTable) || String.Compare(rdTables.GetString(2), strTable, true, CultureInfo.InvariantCulture) == 0)\r
1475           {\r
1476             // First, look for any rowid indexes -- which sqlite defines are INTEGER PRIMARY KEY columns.\r
1477             // Such indexes are not listed in the indexes list but count as indexes just the same.\r
1478             try\r
1479             {\r
1480               using (SqliteCommand cmdTable = new SqliteCommand(String.Format(CultureInfo.InvariantCulture, "PRAGMA [{0}].table_info([{1}])", strCatalog, rdTables.GetString(2)), this))\r
1481               using (SqliteDataReader rdTable = cmdTable.ExecuteReader())\r
1482               {\r
1483                 while (rdTable.Read())\r
1484                 {\r
1485                   if (rdTable.GetInt32(5) == 1)\r
1486                   {\r
1487                     primaryKeys.Add(rdTable.GetInt32(0));\r
1488 \r
1489                     // If the primary key is of type INTEGER, then its a rowid and we need to make a fake index entry for it.\r
1490                     if (String.Compare(rdTable.GetString(2), "INTEGER", true, CultureInfo.InvariantCulture) == 0)\r
1491                       maybeRowId = true;\r
1492                   }\r
1493                 }\r
1494               }\r
1495             }\r
1496             catch (SqliteException)\r
1497             {\r
1498             }\r
1499             if (primaryKeys.Count == 1 && maybeRowId == true)\r
1500             {\r
1501               row = tbl.NewRow();\r
1502 \r
1503               row["TABLE_CATALOG"] = strCatalog;\r
1504               row["TABLE_NAME"] = rdTables.GetString(2);\r
1505               row["INDEX_CATALOG"] = strCatalog;\r
1506               row["PRIMARY_KEY"] = true;\r
1507               row["INDEX_NAME"] = String.Format(CultureInfo.InvariantCulture, "{1}_PK_{0}", rdTables.GetString(2), master);\r
1508               row["UNIQUE"] = true;\r
1509 \r
1510               if (String.Compare((string)row["INDEX_NAME"], strIndex, true, CultureInfo.InvariantCulture) == 0\r
1511               || strIndex == null)\r
1512               {\r
1513                 tbl.Rows.Add(row);\r
1514               }\r
1515 \r
1516               primaryKeys.Clear();\r
1517             }\r
1518 \r
1519             // Now fetch all the rest of the indexes.\r
1520             try\r
1521             {\r
1522               using (SqliteCommand cmd = new SqliteCommand(String.Format(CultureInfo.InvariantCulture, "PRAGMA [{0}].index_list([{1}])", strCatalog, rdTables.GetString(2)), this))\r
1523               using (SqliteDataReader rd = (SqliteDataReader)cmd.ExecuteReader())\r
1524               {\r
1525                 while (rd.Read())\r
1526                 {\r
1527                   if (String.Compare(rd.GetString(1), strIndex, true, CultureInfo.InvariantCulture) == 0\r
1528                   || strIndex == null)\r
1529                   {\r
1530                     row = tbl.NewRow();\r
1531 \r
1532                     row["TABLE_CATALOG"] = strCatalog;\r
1533                     row["TABLE_NAME"] = rdTables.GetString(2);\r
1534                     row["INDEX_CATALOG"] = strCatalog;\r
1535                     row["INDEX_NAME"] = rd.GetString(1);\r
1536                     row["UNIQUE"] = rd.GetBoolean(2);\r
1537                     row["PRIMARY_KEY"] = false;\r
1538 \r
1539                     // get the index definition\r
1540                     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
1541                     using (SqliteDataReader rdIndexes = cmdIndexes.ExecuteReader())\r
1542                     {\r
1543                       while (rdIndexes.Read())\r
1544                       {\r
1545                         if (rdIndexes.IsDBNull(4) == false)\r
1546                           row["INDEX_DEFINITION"] = rdIndexes.GetString(4);\r
1547                         break;\r
1548                       }\r
1549                     }\r
1550 \r
1551                     // Now for the really hard work.  Figure out which index is the primary key index.\r
1552                     // The only way to figure it out is to check if the index was an autoindex and if we have a non-rowid\r
1553                     // primary key, and all the columns in the given index match the primary key columns\r
1554                     if (primaryKeys.Count > 0 && rd.GetString(1).StartsWith("sqlite_autoindex_" + rdTables.GetString(2), StringComparison.InvariantCultureIgnoreCase) == true)\r
1555                     {\r
1556                       using (SqliteCommand cmdDetails = new SqliteCommand(String.Format(CultureInfo.InvariantCulture, "PRAGMA [{0}].index_info([{1}])", strCatalog, rd.GetString(1)), this))\r
1557                       using (SqliteDataReader rdDetails = cmdDetails.ExecuteReader())\r
1558                       {\r
1559                         int nMatches = 0;\r
1560                         while (rdDetails.Read())\r
1561                         {\r
1562                           if (primaryKeys.Contains(rdDetails.GetInt32(1)) == false)\r
1563                           {\r
1564                             nMatches = 0;\r
1565                             break;\r
1566                           }\r
1567                           nMatches++;\r
1568                         }\r
1569                         if (nMatches == primaryKeys.Count)\r
1570                         {\r
1571                           row["PRIMARY_KEY"] = true;\r
1572                           primaryKeys.Clear();\r
1573                         }\r
1574                       }\r
1575                     }\r
1576 \r
1577                     tbl.Rows.Add(row);\r
1578                   }\r
1579                 }\r
1580               }\r
1581             }\r
1582             catch (SqliteException)\r
1583             {\r
1584             }\r
1585           }\r
1586         }\r
1587       }\r
1588 \r
1589       tbl.AcceptChanges();\r
1590       tbl.EndLoadData();\r
1591 \r
1592       return tbl;\r
1593     }\r
1594 \r
1595     private DataTable Schema_Triggers(string catalog, string table, string triggerName)\r
1596     {\r
1597       DataTable tbl = new DataTable("Triggers");\r
1598       DataRow row;\r
1599 \r
1600       tbl.Locale = CultureInfo.InvariantCulture;\r
1601       tbl.Columns.Add("TABLE_CATALOG", typeof(string));\r
1602       tbl.Columns.Add("TABLE_SCHEMA", typeof(string));\r
1603       tbl.Columns.Add("TABLE_NAME", typeof(string));\r
1604       tbl.Columns.Add("TRIGGER_NAME", typeof(string));\r
1605       tbl.Columns.Add("TRIGGER_DEFINITION", typeof(string));\r
1606 \r
1607       tbl.BeginLoadData();\r
1608 \r
1609       if (String.IsNullOrEmpty(table)) table = null;\r
1610       if (String.IsNullOrEmpty(catalog)) catalog = "main";\r
1611       string master = (String.Compare(catalog, "temp", true, CultureInfo.InvariantCulture) == 0) ? _tempmasterdb : _masterdb;\r
1612 \r
1613       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
1614       using (SqliteDataReader rd = (SqliteDataReader)cmd.ExecuteReader())\r
1615       {\r
1616         while (rd.Read())\r
1617         {\r
1618           if (String.Compare(rd.GetString(1), triggerName, true, CultureInfo.InvariantCulture) == 0\r
1619             || triggerName == null)\r
1620           {\r
1621             if (table == null || String.Compare(table, rd.GetString(2), true, CultureInfo.InvariantCulture) == 0)\r
1622             {\r
1623               row = tbl.NewRow();\r
1624 \r
1625               row["TABLE_CATALOG"] = catalog;\r
1626               row["TABLE_NAME"] = rd.GetString(2);\r
1627               row["TRIGGER_NAME"] = rd.GetString(1);\r
1628               row["TRIGGER_DEFINITION"] = rd.GetString(4);\r
1629 \r
1630               tbl.Rows.Add(row);\r
1631             }\r
1632           }\r
1633         }\r
1634       }\r
1635       tbl.AcceptChanges();\r
1636       tbl.EndLoadData();\r
1637 \r
1638       return tbl;\r
1639     }\r
1640 \r
1641     /// <summary>\r
1642     /// Retrieves table schema information for the database and catalog\r
1643     /// </summary>\r
1644     /// <param name="strCatalog">The catalog (attached database) to retrieve tables on</param>\r
1645     /// <param name="strTable">The table to retrieve, can be null</param>\r
1646     /// <param name="strType">The table type, can be null</param>\r
1647     /// <returns>DataTable</returns>\r
1648     private DataTable Schema_Tables(string strCatalog, string strTable, string strType)\r
1649     {\r
1650       DataTable tbl = new DataTable("Tables");\r
1651       DataRow row;\r
1652       string strItem;\r
1653 \r
1654       tbl.Locale = CultureInfo.InvariantCulture;\r
1655       tbl.Columns.Add("TABLE_CATALOG", typeof(string));\r
1656       tbl.Columns.Add("TABLE_SCHEMA", typeof(string));\r
1657       tbl.Columns.Add("TABLE_NAME", typeof(string));\r
1658       tbl.Columns.Add("TABLE_TYPE", typeof(string));\r
1659       tbl.Columns.Add("TABLE_ID", typeof(long));\r
1660       tbl.Columns.Add("TABLE_ROOTPAGE", typeof(int));\r
1661       tbl.Columns.Add("TABLE_DEFINITION", typeof(string));\r
1662       tbl.BeginLoadData();\r
1663 \r
1664       if (String.IsNullOrEmpty(strCatalog)) strCatalog = "main";\r
1665 \r
1666       string master = (String.Compare(strCatalog, "temp", true, CultureInfo.InvariantCulture) == 0) ? _tempmasterdb : _masterdb;\r
1667 \r
1668       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
1669       using (SqliteDataReader rd = (SqliteDataReader)cmd.ExecuteReader())\r
1670       {\r
1671         while (rd.Read())\r
1672         {\r
1673           strItem = rd.GetString(0);\r
1674           if (String.Compare(rd.GetString(2), 0, "SQLITE_", 0, 7, true, CultureInfo.InvariantCulture) == 0)\r
1675             strItem = "SYSTEM_TABLE";\r
1676 \r
1677           if (String.Compare(strType, strItem, true, CultureInfo.InvariantCulture) == 0\r
1678             || strType == null)\r
1679           {\r
1680             if (String.Compare(rd.GetString(2), strTable, true, CultureInfo.InvariantCulture) == 0\r
1681               || strTable == null)\r
1682             {\r
1683               row = tbl.NewRow();\r
1684 \r
1685               row["TABLE_CATALOG"] = strCatalog;\r
1686               row["TABLE_NAME"] = rd.GetString(2);\r
1687               row["TABLE_TYPE"] = strItem;\r
1688               row["TABLE_ID"] = rd.GetInt64(5);\r
1689               row["TABLE_ROOTPAGE"] = rd.GetInt32(3);\r
1690               row["TABLE_DEFINITION"] = rd.GetString(4);\r
1691 \r
1692               tbl.Rows.Add(row);\r
1693             }\r
1694           }\r
1695         }\r
1696       }\r
1697 \r
1698       tbl.AcceptChanges();\r
1699       tbl.EndLoadData();\r
1700 \r
1701       return tbl;\r
1702     }\r
1703 \r
1704     /// <summary>\r
1705     /// Retrieves view schema information for the database\r
1706     /// </summary>\r
1707     /// <param name="strCatalog">The catalog (attached database) to retrieve views on</param>\r
1708     /// <param name="strView">The view name, can be null</param>\r
1709     /// <returns>DataTable</returns>\r
1710     private DataTable Schema_Views(string strCatalog, string strView)\r
1711     {\r
1712       DataTable tbl = new DataTable("Views");\r
1713       DataRow row;\r
1714       string strItem;\r
1715       int nPos;\r
1716 \r
1717       tbl.Locale = CultureInfo.InvariantCulture;\r
1718       tbl.Columns.Add("TABLE_CATALOG", typeof(string));\r
1719       tbl.Columns.Add("TABLE_SCHEMA", typeof(string));\r
1720       tbl.Columns.Add("TABLE_NAME", typeof(string));\r
1721       tbl.Columns.Add("VIEW_DEFINITION", typeof(string));\r
1722       tbl.Columns.Add("CHECK_OPTION", typeof(bool));\r
1723       tbl.Columns.Add("IS_UPDATABLE", typeof(bool));\r
1724       tbl.Columns.Add("DESCRIPTION", typeof(string));\r
1725       tbl.Columns.Add("DATE_CREATED", typeof(DateTime));\r
1726       tbl.Columns.Add("DATE_MODIFIED", typeof(DateTime));\r
1727 \r
1728       tbl.BeginLoadData();\r
1729 \r
1730       if (String.IsNullOrEmpty(strCatalog)) strCatalog = "main";\r
1731 \r
1732       string master = (String.Compare(strCatalog, "temp", true, CultureInfo.InvariantCulture) == 0) ? _tempmasterdb : _masterdb;\r
1733 \r
1734       using (SqliteCommand cmd = new SqliteCommand(String.Format(CultureInfo.InvariantCulture, "SELECT * FROM [{0}].[{1}] WHERE [type] LIKE 'view'", strCatalog, master), this))\r
1735       using (SqliteDataReader rd = (SqliteDataReader)cmd.ExecuteReader())\r
1736       {\r
1737         while (rd.Read())\r
1738         {\r
1739           if (String.Compare(rd.GetString(1), strView, true, CultureInfo.InvariantCulture) == 0\r
1740             || String.IsNullOrEmpty(strView))\r
1741           {\r
1742             strItem = rd.GetString(4).Replace('\r', ' ').Replace('\n', ' ').Replace('\t', ' ');\r
1743             nPos = CultureInfo.InvariantCulture.CompareInfo.IndexOf(strItem, " AS ", CompareOptions.IgnoreCase);\r
1744             if (nPos > -1)\r
1745             {\r
1746               strItem = strItem.Substring(nPos + 4).Trim();\r
1747               row = tbl.NewRow();\r
1748 \r
1749               row["TABLE_CATALOG"] = strCatalog;\r
1750               row["TABLE_NAME"] = rd.GetString(2);\r
1751               row["IS_UPDATABLE"] = false;\r
1752               row["VIEW_DEFINITION"] = strItem;\r
1753 \r
1754               tbl.Rows.Add(row);\r
1755             }\r
1756           }\r
1757         }\r
1758       }\r
1759 \r
1760       tbl.AcceptChanges();\r
1761       tbl.EndLoadData();\r
1762 \r
1763       return tbl;\r
1764     }\r
1765 \r
1766     /// <summary>\r
1767     /// Retrieves catalog (attached databases) schema information for the database\r
1768     /// </summary>\r
1769     /// <param name="strCatalog">The catalog to retrieve, can be null</param>\r
1770     /// <returns>DataTable</returns>\r
1771     private DataTable Schema_Catalogs(string strCatalog)\r
1772     {\r
1773       DataTable tbl = new DataTable("Catalogs");\r
1774       DataRow row;\r
1775 \r
1776       tbl.Locale = CultureInfo.InvariantCulture;\r
1777       tbl.Columns.Add("CATALOG_NAME", typeof(string));\r
1778       tbl.Columns.Add("DESCRIPTION", typeof(string));\r
1779       tbl.Columns.Add("ID", typeof(long));\r
1780 \r
1781       tbl.BeginLoadData();\r
1782 \r
1783       using (SqliteCommand cmd = new SqliteCommand("PRAGMA database_list", this))\r
1784       using (SqliteDataReader rd = (SqliteDataReader)cmd.ExecuteReader())\r
1785       {\r
1786         while (rd.Read())\r
1787         {\r
1788           if (String.Compare(rd.GetString(1), strCatalog, true, CultureInfo.InvariantCulture) == 0\r
1789             || strCatalog == null)\r
1790           {\r
1791             row = tbl.NewRow();\r
1792 \r
1793             row["CATALOG_NAME"] = rd.GetString(1);\r
1794             row["DESCRIPTION"] = rd.GetString(2);\r
1795             row["ID"] = rd.GetInt64(0);\r
1796 \r
1797             tbl.Rows.Add(row);\r
1798           }\r
1799         }\r
1800       }\r
1801 \r
1802       tbl.AcceptChanges();\r
1803       tbl.EndLoadData();\r
1804 \r
1805       return tbl;\r
1806     }\r
1807 \r
1808     private DataTable Schema_DataTypes()\r
1809     {\r
1810       DataTable tbl = new DataTable("DataTypes");\r
1811 \r
1812       tbl.Locale = CultureInfo.InvariantCulture;\r
1813       tbl.Columns.Add("TypeName", typeof(String));\r
1814       tbl.Columns.Add("ProviderDbType", typeof(int));\r
1815       tbl.Columns.Add("ColumnSize", typeof(long));\r
1816       tbl.Columns.Add("CreateFormat", typeof(String));\r
1817       tbl.Columns.Add("CreateParameters", typeof(String));\r
1818       tbl.Columns.Add("DataType", typeof(String));\r
1819       tbl.Columns.Add("IsAutoIncrementable", typeof(bool));\r
1820       tbl.Columns.Add("IsBestMatch", typeof(bool));\r
1821       tbl.Columns.Add("IsCaseSensitive", typeof(bool));\r
1822       tbl.Columns.Add("IsFixedLength", typeof(bool));\r
1823       tbl.Columns.Add("IsFixedPrecisionScale", typeof(bool));\r
1824       tbl.Columns.Add("IsLong", typeof(bool));\r
1825       tbl.Columns.Add("IsNullable", typeof(bool));\r
1826       tbl.Columns.Add("IsSearchable", typeof(bool));\r
1827       tbl.Columns.Add("IsSearchableWithLike", typeof(bool));\r
1828       tbl.Columns.Add("IsLiteralSupported", typeof(bool));\r
1829       tbl.Columns.Add("LiteralPrefix", typeof(String));\r
1830       tbl.Columns.Add("LiteralSuffix", typeof(String));\r
1831       tbl.Columns.Add("IsUnsigned", typeof(bool));\r
1832       tbl.Columns.Add("MaximumScale", typeof(short));\r
1833       tbl.Columns.Add("MinimumScale", typeof(short));\r
1834       tbl.Columns.Add("IsConcurrencyType", typeof(bool));\r
1835 \r
1836       tbl.BeginLoadData();\r
1837 \r
1838       StringReader reader = new StringReader(SR.DataTypes);\r
1839       tbl.ReadXml(reader);\r
1840       reader.Close();\r
1841 \r
1842       tbl.AcceptChanges();\r
1843       tbl.EndLoadData();\r
1844 \r
1845       return tbl;\r
1846     }\r
1847 \r
1848     /// <summary>\r
1849     /// Returns the base column information for indexes in a database\r
1850     /// </summary>\r
1851     /// <param name="strCatalog">The catalog to retrieve indexes for (can be null)</param>\r
1852     /// <param name="strTable">The table to restrict index information by (can be null)</param>\r
1853     /// <param name="strIndex">The index to restrict index information by (can be null)</param>\r
1854     /// <param name="strColumn">The source column to restrict index information by (can be null)</param>\r
1855     /// <returns>A DataTable containing the results</returns>\r
1856     private DataTable Schema_IndexColumns(string strCatalog, string strTable, string strIndex, string strColumn)\r
1857     {\r
1858       DataTable tbl = new DataTable("IndexColumns");\r
1859       DataRow row;\r
1860       List<KeyValuePair<int, string>> primaryKeys = new List<KeyValuePair<int, string>>();\r
1861       bool maybeRowId;\r
1862 \r
1863       tbl.Locale = CultureInfo.InvariantCulture;\r
1864       tbl.Columns.Add("CONSTRAINT_CATALOG", typeof(string));\r
1865       tbl.Columns.Add("CONSTRAINT_SCHEMA", typeof(string));\r
1866       tbl.Columns.Add("CONSTRAINT_NAME", typeof(string));\r
1867       tbl.Columns.Add("TABLE_CATALOG", typeof(string));\r
1868       tbl.Columns.Add("TABLE_SCHEMA", typeof(string));\r
1869       tbl.Columns.Add("TABLE_NAME", typeof(string));\r
1870       tbl.Columns.Add("COLUMN_NAME", typeof(string));\r
1871       tbl.Columns.Add("ORDINAL_POSITION", typeof(int));\r
1872       tbl.Columns.Add("INDEX_NAME", typeof(string));\r
1873       tbl.Columns.Add("COLLATION_NAME", typeof(string));\r
1874       tbl.Columns.Add("SORT_MODE", typeof(string));\r
1875       tbl.Columns.Add("CONFLICT_OPTION", typeof(int));\r
1876 \r
1877       if (String.IsNullOrEmpty(strCatalog)) strCatalog = "main";\r
1878 \r
1879       string master = (String.Compare(strCatalog, "temp", true, CultureInfo.InvariantCulture) == 0) ? _tempmasterdb : _masterdb;\r
1880 \r
1881       tbl.BeginLoadData();\r
1882 \r
1883       using (SqliteCommand cmdTables = new SqliteCommand(String.Format(CultureInfo.InvariantCulture, "SELECT * FROM [{0}].[{1}] WHERE [type] LIKE 'table'", strCatalog, master), this))\r
1884       using (SqliteDataReader rdTables = cmdTables.ExecuteReader())\r
1885       {\r
1886         while (rdTables.Read())\r
1887         {\r
1888           maybeRowId = false;\r
1889           primaryKeys.Clear();\r
1890           if (String.IsNullOrEmpty(strTable) || String.Compare(rdTables.GetString(2), strTable, true, CultureInfo.InvariantCulture) == 0)\r
1891           {\r
1892             try\r
1893             {\r
1894               using (SqliteCommand cmdTable = new SqliteCommand(String.Format(CultureInfo.InvariantCulture, "PRAGMA [{0}].table_info([{1}])", strCatalog, rdTables.GetString(2)), this))\r
1895               using (SqliteDataReader rdTable = cmdTable.ExecuteReader())\r
1896               {\r
1897                 while (rdTable.Read())\r
1898                 {\r
1899                   if (rdTable.GetInt32(5) == 1) // is a primary key\r
1900                   {\r
1901                     primaryKeys.Add(new KeyValuePair<int, string>(rdTable.GetInt32(0), rdTable.GetString(1)));\r
1902                     // Is an integer -- could be a rowid if no other primary keys exist in the table\r
1903                     if (String.Compare(rdTable.GetString(2), "INTEGER", true, CultureInfo.InvariantCulture) == 0)\r
1904                       maybeRowId = true;\r
1905                   }\r
1906                 }\r
1907               }\r
1908             }\r
1909             catch (SqliteException)\r
1910             {\r
1911             }\r
1912             // This is a rowid row\r
1913             if (primaryKeys.Count == 1 && maybeRowId == true)\r
1914             {\r
1915               row = tbl.NewRow();\r
1916               row["CONSTRAINT_CATALOG"] = strCatalog;\r
1917               row["CONSTRAINT_NAME"] = String.Format(CultureInfo.InvariantCulture, "{1}_PK_{0}", rdTables.GetString(2), master);\r
1918               row["TABLE_CATALOG"] = strCatalog;\r
1919               row["TABLE_NAME"] = rdTables.GetString(2);\r
1920               row["COLUMN_NAME"] = primaryKeys[0].Value;\r
1921               row["INDEX_NAME"] = row["CONSTRAINT_NAME"];\r
1922               row["ORDINAL_POSITION"] = 0; // primaryKeys[0].Key;\r
1923               row["COLLATION_NAME"] = "BINARY";\r
1924               row["SORT_MODE"] = "ASC";\r
1925               row["CONFLICT_OPTION"] = 2;\r
1926 \r
1927               if (String.IsNullOrEmpty(strIndex) || String.Compare(strIndex, (string)row["INDEX_NAME"], true, CultureInfo.InvariantCulture) == 0)\r
1928                 tbl.Rows.Add(row);\r
1929             }\r
1930 \r
1931             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
1932             using (SqliteDataReader rdIndexes = cmdIndexes.ExecuteReader())\r
1933             {\r
1934               while (rdIndexes.Read())\r
1935               {\r
1936                 int ordinal = 0;\r
1937                 if (String.IsNullOrEmpty(strIndex) || String.Compare(strIndex, rdIndexes.GetString(1), true, CultureInfo.InvariantCulture) == 0)\r
1938                 {\r
1939                   try\r
1940                   {\r
1941                     using (SqliteCommand cmdIndex = new SqliteCommand(String.Format(CultureInfo.InvariantCulture, "PRAGMA [{0}].index_info([{1}])", strCatalog, rdIndexes.GetString(1)), this))\r
1942                     using (SqliteDataReader rdIndex = cmdIndex.ExecuteReader())\r
1943                     {\r
1944                       while (rdIndex.Read())\r
1945                       {\r
1946                         row = tbl.NewRow();\r
1947                         row["CONSTRAINT_CATALOG"] = strCatalog;\r
1948                         row["CONSTRAINT_NAME"] = rdIndexes.GetString(1);\r
1949                         row["TABLE_CATALOG"] = strCatalog;\r
1950                         row["TABLE_NAME"] = rdIndexes.GetString(2);\r
1951                         row["COLUMN_NAME"] = rdIndex.GetString(2);\r
1952                         row["INDEX_NAME"] = rdIndexes.GetString(1);\r
1953                         row["ORDINAL_POSITION"] = ordinal; // rdIndex.GetInt32(1);\r
1954 \r
1955                         string collationSequence;\r
1956                         int sortMode;\r
1957                         int onError;\r
1958                         _sql.GetIndexColumnExtendedInfo(strCatalog, rdIndexes.GetString(1), rdIndex.GetString(2), out sortMode, out onError, out collationSequence);\r
1959 \r
1960                         if (String.IsNullOrEmpty(collationSequence) == false)\r
1961                           row["COLLATION_NAME"] = collationSequence;\r
1962 \r
1963                         row["SORT_MODE"] = (sortMode == 0) ? "ASC" : "DESC";\r
1964                         row["CONFLICT_OPTION"] = onError;\r
1965 \r
1966                         ordinal++;\r
1967 \r
1968                         if (String.IsNullOrEmpty(strColumn) || String.Compare(strColumn, row["COLUMN_NAME"].ToString(), true, CultureInfo.InvariantCulture) == 0)\r
1969                           tbl.Rows.Add(row);\r
1970                       }\r
1971                     }\r
1972                   }\r
1973                   catch (SqliteException)\r
1974                   {\r
1975                   }\r
1976                 }\r
1977               }\r
1978             }\r
1979           }\r
1980         }\r
1981       }\r
1982 \r
1983       tbl.EndLoadData();\r
1984       tbl.AcceptChanges();\r
1985 \r
1986       return tbl;\r
1987     }\r
1988 \r
1989     /// <summary>\r
1990     /// Returns detailed column information for a specified view\r
1991     /// </summary>\r
1992     /// <param name="strCatalog">The catalog to retrieve columns for (can be null)</param>\r
1993     /// <param name="strView">The view to restrict column information by (can be null)</param>\r
1994     /// <param name="strColumn">The source column to restrict column information by (can be null)</param>\r
1995     /// <returns>A DataTable containing the results</returns>\r
1996     private DataTable Schema_ViewColumns(string strCatalog, string strView, string strColumn)\r
1997     {\r
1998       DataTable tbl = new DataTable("ViewColumns");\r
1999       DataRow row;\r
2000       string strSql;\r
2001       int n;\r
2002       DataRow schemaRow;\r
2003       DataRow viewRow;\r
2004 \r
2005       tbl.Locale = CultureInfo.InvariantCulture;\r
2006       tbl.Columns.Add("VIEW_CATALOG", typeof(string));\r
2007       tbl.Columns.Add("VIEW_SCHEMA", typeof(string));\r
2008       tbl.Columns.Add("VIEW_NAME", typeof(string));\r
2009       tbl.Columns.Add("VIEW_COLUMN_NAME", typeof(String));\r
2010       tbl.Columns.Add("TABLE_CATALOG", typeof(string));\r
2011       tbl.Columns.Add("TABLE_SCHEMA", typeof(string));\r
2012       tbl.Columns.Add("TABLE_NAME", typeof(string));\r
2013       tbl.Columns.Add("COLUMN_NAME", typeof(string));\r
2014       tbl.Columns.Add("ORDINAL_POSITION", typeof(int));\r
2015       tbl.Columns.Add("COLUMN_HASDEFAULT", typeof(bool));\r
2016       tbl.Columns.Add("COLUMN_DEFAULT", typeof(string));\r
2017       tbl.Columns.Add("COLUMN_FLAGS", typeof(long));\r
2018       tbl.Columns.Add("IS_NULLABLE", typeof(bool));\r
2019       tbl.Columns.Add("DATA_TYPE", typeof(string));\r
2020       tbl.Columns.Add("CHARACTER_MAXIMUM_LENGTH", typeof(int));\r
2021       tbl.Columns.Add("NUMERIC_PRECISION", typeof(int));\r
2022       tbl.Columns.Add("NUMERIC_SCALE", typeof(int));\r
2023       tbl.Columns.Add("DATETIME_PRECISION", typeof(long));\r
2024       tbl.Columns.Add("CHARACTER_SET_CATALOG", typeof(string));\r
2025       tbl.Columns.Add("CHARACTER_SET_SCHEMA", typeof(string));\r
2026       tbl.Columns.Add("CHARACTER_SET_NAME", typeof(string));\r
2027       tbl.Columns.Add("COLLATION_CATALOG", typeof(string));\r
2028       tbl.Columns.Add("COLLATION_SCHEMA", typeof(string));\r
2029       tbl.Columns.Add("COLLATION_NAME", typeof(string));\r
2030       tbl.Columns.Add("PRIMARY_KEY", typeof(bool));\r
2031       tbl.Columns.Add("EDM_TYPE", typeof(string));\r
2032       tbl.Columns.Add("AUTOINCREMENT", typeof(bool));\r
2033       tbl.Columns.Add("UNIQUE", typeof(bool));\r
2034 \r
2035       if (String.IsNullOrEmpty(strCatalog)) strCatalog = "main";\r
2036 \r
2037       string master = (String.Compare(strCatalog, "temp", true, CultureInfo.InvariantCulture) == 0) ? _tempmasterdb : _masterdb;\r
2038       \r
2039       tbl.BeginLoadData();\r
2040 \r
2041       using (SqliteCommand cmdViews = new SqliteCommand(String.Format(CultureInfo.InvariantCulture, "SELECT * FROM [{0}].[{1}] WHERE [type] LIKE 'view'", strCatalog, master), this))\r
2042       using (SqliteDataReader rdViews = cmdViews.ExecuteReader())\r
2043       {\r
2044         while (rdViews.Read())\r
2045         {\r
2046           if (String.IsNullOrEmpty(strView) || String.Compare(strView, rdViews.GetString(2), true, CultureInfo.InvariantCulture) == 0)\r
2047           {\r
2048             using (SqliteCommand cmdViewSelect = new SqliteCommand(String.Format(CultureInfo.InvariantCulture, "SELECT * FROM [{0}].[{1}]", strCatalog, rdViews.GetString(2)), this))\r
2049             {\r
2050               strSql = rdViews.GetString(4).Replace('\r', ' ').Replace('\n', ' ').Replace('\t', ' ');\r
2051               n = CultureInfo.InvariantCulture.CompareInfo.IndexOf(strSql, " AS ", CompareOptions.IgnoreCase);\r
2052               if (n < 0)\r
2053                 continue;\r
2054 \r
2055               strSql = strSql.Substring(n + 4);\r
2056 \r
2057               using (SqliteCommand cmd = new SqliteCommand(strSql, this))\r
2058               using (SqliteDataReader rdViewSelect = cmdViewSelect.ExecuteReader(CommandBehavior.SchemaOnly))\r
2059               using (SqliteDataReader rd = (SqliteDataReader)cmd.ExecuteReader(CommandBehavior.SchemaOnly))\r
2060               using (DataTable tblSchemaView = rdViewSelect.GetSchemaTable(false, false))\r
2061               using (DataTable tblSchema = rd.GetSchemaTable(false, false))\r
2062               {\r
2063                 for (n = 0; n < tblSchema.Rows.Count; n++)\r
2064                 {\r
2065                   viewRow = tblSchemaView.Rows[n];\r
2066                   schemaRow = tblSchema.Rows[n];\r
2067 \r
2068                   if (String.Compare(viewRow[SchemaTableColumn.ColumnName].ToString(), strColumn, true, CultureInfo.InvariantCulture) == 0\r
2069                     || strColumn == null)\r
2070                   {\r
2071                     row = tbl.NewRow();\r
2072 \r
2073                     row["VIEW_CATALOG"] = strCatalog;\r
2074                     row["VIEW_NAME"] = rdViews.GetString(2);\r
2075                     row["TABLE_CATALOG"] = strCatalog;\r
2076                     row["TABLE_SCHEMA"] = schemaRow[SchemaTableColumn.BaseSchemaName];\r
2077                     row["TABLE_NAME"] = schemaRow[SchemaTableColumn.BaseTableName];\r
2078                     row["COLUMN_NAME"] = schemaRow[SchemaTableColumn.BaseColumnName];\r
2079                     row["VIEW_COLUMN_NAME"] = viewRow[SchemaTableColumn.ColumnName];\r
2080                     row["COLUMN_HASDEFAULT"] = (viewRow[SchemaTableOptionalColumn.DefaultValue] != DBNull.Value);\r
2081                     row["COLUMN_DEFAULT"] = viewRow[SchemaTableOptionalColumn.DefaultValue];\r
2082                     row["ORDINAL_POSITION"] = viewRow[SchemaTableColumn.ColumnOrdinal];\r
2083                     row["IS_NULLABLE"] = viewRow[SchemaTableColumn.AllowDBNull];\r
2084                     row["DATA_TYPE"] = viewRow["DataTypeName"]; // SqliteConvert.DbTypeToType((DbType)viewRow[SchemaTableColumn.ProviderType]).ToString();\r
2085                     row["EDM_TYPE"] = SqliteConvert.DbTypeToTypeName((DbType)viewRow[SchemaTableColumn.ProviderType]).ToString().ToLower(CultureInfo.InvariantCulture);\r
2086                     row["CHARACTER_MAXIMUM_LENGTH"] = viewRow[SchemaTableColumn.ColumnSize];\r
2087                     row["TABLE_SCHEMA"] = viewRow[SchemaTableColumn.BaseSchemaName];\r
2088                     row["PRIMARY_KEY"] = viewRow[SchemaTableColumn.IsKey];\r
2089                     row["AUTOINCREMENT"] = viewRow[SchemaTableOptionalColumn.IsAutoIncrement];\r
2090                     row["COLLATION_NAME"] = viewRow["CollationType"];\r
2091                     row["UNIQUE"] = viewRow[SchemaTableColumn.IsUnique];\r
2092                     tbl.Rows.Add(row);\r
2093                   }\r
2094                 }\r
2095               }\r
2096             }\r
2097           }\r
2098         }\r
2099       }\r
2100 \r
2101       tbl.EndLoadData();\r
2102       tbl.AcceptChanges();\r
2103 \r
2104       return tbl;\r
2105     }\r
2106 \r
2107     /// <summary>\r
2108     /// Retrieves foreign key information from the specified set of filters\r
2109     /// </summary>\r
2110     /// <param name="strCatalog">An optional catalog to restrict results on</param>\r
2111     /// <param name="strTable">An optional table to restrict results on</param>\r
2112     /// <param name="strKeyName">An optional foreign key name to restrict results on</param>\r
2113     /// <returns>A DataTable with the results of the query</returns>\r
2114     private DataTable Schema_ForeignKeys(string strCatalog, string strTable, string strKeyName)\r
2115     {\r
2116       DataTable tbl = new DataTable("ForeignKeys");\r
2117       DataRow row;\r
2118 \r
2119       tbl.Locale = CultureInfo.InvariantCulture;\r
2120       tbl.Columns.Add("CONSTRAINT_CATALOG", typeof(string));\r
2121       tbl.Columns.Add("CONSTRAINT_SCHEMA", typeof(string));\r
2122       tbl.Columns.Add("CONSTRAINT_NAME", typeof(string));\r
2123       tbl.Columns.Add("TABLE_CATALOG", typeof(string));\r
2124       tbl.Columns.Add("TABLE_SCHEMA", typeof(string));\r
2125       tbl.Columns.Add("TABLE_NAME", typeof(string));\r
2126       tbl.Columns.Add("CONSTRAINT_TYPE", typeof(string));\r
2127       tbl.Columns.Add("IS_DEFERRABLE", typeof(bool));\r
2128       tbl.Columns.Add("INITIALLY_DEFERRED", typeof(bool));\r
2129       tbl.Columns.Add("FKEY_FROM_COLUMN", typeof(string));\r
2130       tbl.Columns.Add("FKEY_FROM_ORDINAL_POSITION", typeof(int));\r
2131       tbl.Columns.Add("FKEY_TO_CATALOG", typeof(string));\r
2132       tbl.Columns.Add("FKEY_TO_SCHEMA", typeof(string));\r
2133       tbl.Columns.Add("FKEY_TO_TABLE", typeof(string));\r
2134       tbl.Columns.Add("FKEY_TO_COLUMN", typeof(string));\r
2135 \r
2136       if (String.IsNullOrEmpty(strCatalog)) strCatalog = "main";\r
2137 \r
2138       string master = (String.Compare(strCatalog, "temp", true, CultureInfo.InvariantCulture) == 0) ? _tempmasterdb : _masterdb;\r
2139 \r
2140       tbl.BeginLoadData();\r
2141 \r
2142       using (SqliteCommand cmdTables = new SqliteCommand(String.Format(CultureInfo.InvariantCulture, "SELECT * FROM [{0}].[{1}] WHERE [type] LIKE 'table'", strCatalog, master), this))\r
2143       using (SqliteDataReader rdTables = cmdTables.ExecuteReader())\r
2144       {\r
2145         while (rdTables.Read())\r
2146         {\r
2147           if (String.IsNullOrEmpty(strTable) || String.Compare(strTable, rdTables.GetString(2), true, CultureInfo.InvariantCulture) == 0)\r
2148           {\r
2149             try\r
2150             {\r
2151               using (SqliteCommandBuilder builder = new SqliteCommandBuilder())\r
2152               //using (SqliteCommand cmdTable = new SqliteCommand(String.Format(CultureInfo.InvariantCulture, "SELECT * FROM [{0}].[{1}]", strCatalog, rdTables.GetString(2)), this))\r
2153               //using (SqliteDataReader rdTable = cmdTable.ExecuteReader(CommandBehavior.SchemaOnly))\r
2154               using (SqliteCommand cmdKey = new SqliteCommand(String.Format(CultureInfo.InvariantCulture, "PRAGMA [{0}].foreign_key_list([{1}])", strCatalog, rdTables.GetString(2)), this))\r
2155               using (SqliteDataReader rdKey = cmdKey.ExecuteReader())\r
2156               {\r
2157                 while (rdKey.Read())\r
2158                 {\r
2159                   row = tbl.NewRow();\r
2160                   row["CONSTRAINT_CATALOG"] = strCatalog;\r
2161                   row["CONSTRAINT_NAME"] = String.Format(CultureInfo.InvariantCulture, "FK_{0}_{1}", rdTables[2], rdKey.GetInt32(0));\r
2162                   row["TABLE_CATALOG"] = strCatalog;\r
2163                   row["TABLE_NAME"] = builder.UnquoteIdentifier(rdTables.GetString(2));\r
2164                   row["CONSTRAINT_TYPE"] = "FOREIGN KEY";\r
2165                   row["IS_DEFERRABLE"] = false;\r
2166                   row["INITIALLY_DEFERRED"] = false;\r
2167                   row["FKEY_FROM_COLUMN"] = builder.UnquoteIdentifier(rdKey[3].ToString());\r
2168                   row["FKEY_TO_CATALOG"] = strCatalog;\r
2169                   row["FKEY_TO_TABLE"] = builder.UnquoteIdentifier(rdKey[2].ToString());\r
2170                   row["FKEY_TO_COLUMN"] = builder.UnquoteIdentifier(rdKey[4].ToString());\r
2171                   row["FKEY_FROM_ORDINAL_POSITION"] = rdKey[1];\r
2172 \r
2173                   if (String.IsNullOrEmpty(strKeyName) || String.Compare(strKeyName, row["CONSTRAINT_NAME"].ToString(), true, CultureInfo.InvariantCulture) == 0)\r
2174                     tbl.Rows.Add(row);\r
2175                 }\r
2176               }\r
2177             }\r
2178             catch (SqliteException)\r
2179             {\r
2180             }\r
2181           }\r
2182         }\r
2183       }\r
2184 \r
2185       tbl.EndLoadData();\r
2186       tbl.AcceptChanges();\r
2187 \r
2188       return tbl;\r
2189     }\r
2190 \r
2191     /// <summary>\r
2192     /// This event is raised whenever SQLite makes an update/delete/insert into the database on\r
2193     /// this connection.  It only applies to the given connection.\r
2194     /// </summary>\r
2195     public event SQLiteUpdateEventHandler Update\r
2196     {\r
2197       add\r
2198       {\r
2199         if (_updateHandler == null)\r
2200         {\r
2201           _updateCallback = new SQLiteUpdateCallback(UpdateCallback);\r
2202           if (_sql != null) _sql.SetUpdateHook(_updateCallback);\r
2203         }\r
2204         _updateHandler += value;\r
2205       }\r
2206       remove\r
2207       {\r
2208         _updateHandler -= value;\r
2209         if (_updateHandler == null)\r
2210         {\r
2211           if (_sql != null) _sql.SetUpdateHook(null);\r
2212           _updateCallback = null;\r
2213         }\r
2214       }\r
2215     }\r
2216 \r
2217     private void UpdateCallback(IntPtr puser, int type, IntPtr database, IntPtr table, Int64 rowid)\r
2218     {\r
2219       _updateHandler(this, new UpdateEventArgs(\r
2220         SQLiteBase.UTF8ToString(database, -1),\r
2221         SQLiteBase.UTF8ToString(table, -1),\r
2222         (UpdateEventType)type,\r
2223         rowid));\r
2224     }\r
2225 \r
2226     /// <summary>\r
2227     /// This event is raised whenever SQLite is committing a transaction.\r
2228     /// Return non-zero to trigger a rollback\r
2229     /// </summary>\r
2230     public event SQLiteCommitHandler Commit\r
2231     {\r
2232       add\r
2233       {\r
2234         if (_commitHandler == null)\r
2235         {\r
2236           _commitCallback = new SQLiteCommitCallback(CommitCallback);\r
2237           if (_sql != null) _sql.SetCommitHook(_commitCallback);\r
2238         }\r
2239         _commitHandler += value;\r
2240       }\r
2241       remove\r
2242       {\r
2243         _commitHandler -= value;\r
2244         if (_commitHandler == null)\r
2245         {\r
2246           if (_sql != null) _sql.SetCommitHook(null);\r
2247           _commitCallback = null;\r
2248         }\r
2249       }\r
2250     }\r
2251 \r
2252     /// <summary>\r
2253     /// This event is raised whenever SQLite is committing a transaction.\r
2254     /// Return non-zero to trigger a rollback\r
2255     /// </summary>\r
2256     public event EventHandler RollBack\r
2257     {\r
2258       add\r
2259       {\r
2260         if (_rollbackHandler == null)\r
2261         {\r
2262           _rollbackCallback = new SQLiteRollbackCallback(RollbackCallback);\r
2263           if (_sql != null) _sql.SetRollbackHook(_rollbackCallback);\r
2264         }\r
2265         _rollbackHandler += value;\r
2266       }\r
2267       remove\r
2268       {\r
2269         _rollbackHandler -= value;\r
2270         if (_rollbackHandler == null)\r
2271         {\r
2272           if (_sql != null) _sql.SetRollbackHook(null);\r
2273           _rollbackCallback = null;\r
2274         }\r
2275       }\r
2276     }\r
2277 \r
2278 \r
2279     private int CommitCallback(IntPtr parg)\r
2280     {\r
2281       CommitEventArgs e = new CommitEventArgs();\r
2282       _commitHandler(this, e);\r
2283       return (e.AbortTransaction == true) ? 1 : 0;\r
2284     }\r
2285 \r
2286     private void RollbackCallback(IntPtr parg)\r
2287     {\r
2288       _rollbackHandler(this, EventArgs.Empty);\r
2289     }\r
2290   }\r
2291 \r
2292   /// <summary>\r
2293   /// The I/O file cache flushing behavior for the connection\r
2294   /// </summary>\r
2295   public enum SynchronizationModes\r
2296   {\r
2297     /// <summary>\r
2298     /// Normal file flushing at critical sections of the code\r
2299     /// </summary>\r
2300     Normal = 0,\r
2301     /// <summary>\r
2302     /// Full file flushing after every write operation\r
2303     /// </summary>\r
2304     Full = 1,\r
2305     /// <summary>\r
2306     /// Use the default operating system's file flushing, SQLite does not explicitly flush the file buffers after writing\r
2307     /// </summary>\r
2308     Off = 2,\r
2309   }\r
2310 \r
2311 #if !PLATFORM_COMPACTFRAMEWORK\r
2312   [UnmanagedFunctionPointer(CallingConvention.Cdecl)]\r
2313 #endif\r
2314   internal delegate void SQLiteUpdateCallback(IntPtr puser, int type, IntPtr database, IntPtr table, Int64 rowid);\r
2315 #if !PLATFORM_COMPACTFRAMEWORK\r
2316   [UnmanagedFunctionPointer(CallingConvention.Cdecl)]\r
2317 #endif\r
2318   internal delegate int SQLiteCommitCallback(IntPtr puser);\r
2319 #if !PLATFORM_COMPACTFRAMEWORK\r
2320   [UnmanagedFunctionPointer(CallingConvention.Cdecl)]\r
2321 #endif\r
2322   internal delegate void SQLiteRollbackCallback(IntPtr puser);\r
2323 \r
2324   /// <summary>\r
2325   /// Raised when a transaction is about to be committed.  To roll back a transaction, set the \r
2326   /// rollbackTrans boolean value to true.\r
2327   /// </summary>\r
2328   /// <param name="sender">The connection committing the transaction</param>\r
2329   /// <param name="e">Event arguments on the transaction</param>\r
2330   public delegate void SQLiteCommitHandler(object sender, CommitEventArgs e);\r
2331 \r
2332   /// <summary>\r
2333   /// Raised when data is inserted, updated and deleted on a given connection\r
2334   /// </summary>\r
2335   /// <param name="sender">The connection committing the transaction</param>\r
2336   /// <param name="e">The event parameters which triggered the event</param>\r
2337   public delegate void SQLiteUpdateEventHandler(object sender, UpdateEventArgs e);\r
2338 \r
2339   /// <summary>\r
2340   /// Whenever an update event is triggered on a connection, this enum will indicate\r
2341   /// exactly what type of operation is being performed.\r
2342   /// </summary>\r
2343   public enum UpdateEventType\r
2344   {\r
2345     /// <summary>\r
2346     /// A row is being deleted from the given database and table\r
2347     /// </summary>\r
2348     Delete = 9,\r
2349     /// <summary>\r
2350     /// A row is being inserted into the table.\r
2351     /// </summary>\r
2352     Insert = 18,\r
2353     /// <summary>\r
2354     /// A row is being updated in the table.\r
2355     /// </summary>\r
2356     Update = 23,\r
2357   }\r
2358 \r
2359   /// <summary>\r
2360   /// Passed during an Update callback, these event arguments detail the type of update operation being performed\r
2361   /// on the given connection.\r
2362   /// </summary>\r
2363   public class UpdateEventArgs : EventArgs\r
2364   {\r
2365     /// <summary>\r
2366     /// The name of the database being updated (usually "main" but can be any attached or temporary database)\r
2367     /// </summary>\r
2368     public readonly string Database;\r
2369 \r
2370     /// <summary>\r
2371     /// The name of the table being updated\r
2372     /// </summary>\r
2373     public readonly string Table;\r
2374 \r
2375     /// <summary>\r
2376     /// The type of update being performed (insert/update/delete)\r
2377     /// </summary>\r
2378     public readonly UpdateEventType Event;\r
2379 \r
2380     /// <summary>\r
2381     /// The RowId affected by this update.\r
2382     /// </summary>\r
2383     public readonly Int64 RowId;\r
2384 \r
2385     internal UpdateEventArgs(string database, string table, UpdateEventType eventType, Int64 rowid)\r
2386     {\r
2387       Database = database;\r
2388       Table = table;\r
2389       Event = eventType;\r
2390       RowId = rowid;\r
2391     }\r
2392   }\r
2393 \r
2394   /// <summary>\r
2395   /// Event arguments raised when a transaction is being committed\r
2396   /// </summary>\r
2397   public class CommitEventArgs : EventArgs\r
2398   {\r
2399     internal CommitEventArgs()\r
2400     {\r
2401     }\r
2402 \r
2403     /// <summary>\r
2404     /// Set to true to abort the transaction and trigger a rollback\r
2405     /// </summary>\r
2406     public bool AbortTransaction;\r
2407   }\r
2408 \r
2409 }\r