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