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