2014-08-13 10:39:27 +01:00
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* ADO . NET 2.0 Data Provider for SQLite Version 3. X
* Written by Robert Simpson ( robert @blackcastlesoft . com )
*
* Released to the public domain , use at your own risk !
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
namespace Mono.Data.Sqlite
{
using System ;
using System.Data ;
using System.Data.Common ;
using System.Collections.Generic ;
using System.Globalization ;
using System.ComponentModel ;
using System.Text ;
using System.Runtime.InteropServices ;
using System.IO ;
/// <summary>
/// SQLite implentation of DbConnection.
/// </summary>
/// <remarks>
/// The <see cref="ConnectionString">ConnectionString</see> property of the SqliteConnection class can contain the following parameter(s), delimited with a semi-colon:
/// <list type="table">
/// <listheader>
/// <term>Parameter</term>
/// <term>Values</term>
/// <term>Required</term>
/// <term>Default</term>
/// </listheader>
/// <item>
/// <description>Data Source</description>
/// <description>{filename}</description>
/// <description>Y</description>
/// <description></description>
/// </item>
/// <item>
/// <description>Version</description>
/// <description>3</description>
/// <description>N</description>
/// <description>3</description>
/// </item>
/// <item>
/// <description>UseUTF16Encoding</description>
/// <description><b>True</b><br/><b>False</b></description>
/// <description>N</description>
/// <description>False</description>
/// </item>
/// <item>
/// <description>DateTimeFormat</description>
/// <description><b>Ticks</b> - Use DateTime.Ticks<br/><b>ISO8601</b> - Use ISO8601 DateTime format</description>
/// <description>N</description>
/// <description>ISO8601</description>
/// </item>
/// <item>
/// <description>BinaryGUID</description>
/// <description><b>True</b> - Store GUID columns in binary form<br/><b>False</b> - Store GUID columns as text</description>
/// <description>N</description>
/// <description>True</description>
/// </item>
/// <item>
/// <description>Cache Size</description>
/// <description>{size in bytes}</description>
/// <description>N</description>
/// <description>2000</description>
/// </item>
/// <item>
/// <description>Synchronous</description>
/// <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>
/// <description>N</description>
/// <description>Normal</description>
/// </item>
/// <item>
/// <description>Page Size</description>
/// <description>{size in bytes}</description>
/// <description>N</description>
/// <description>1024</description>
/// </item>
/// <item>
/// <description>Password</description>
/// <description>{password}</description>
/// <description>N</description>
/// <description></description>
/// </item>
/// <item>
/// <description>Enlist</description>
/// <description><b>Y</b> - Automatically enlist in distributed transactions<br/><b>N</b> - No automatic enlistment</description>
/// <description>N</description>
/// <description>Y</description>
/// </item>
/// <item>
/// <description>Pooling</description>
/// <description><b>True</b> - Use connection pooling<br/><b>False</b> - Do not use connection pooling</description>
/// <description>N</description>
/// <description>False</description>
/// </item>
/// <item>
/// <description>FailIfMissing</description>
/// <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>
/// <description>N</description>
/// <description>False</description>
/// </item>
/// <item>
/// <description>Max Page Count</description>
/// <description>{size in pages} - Limits the maximum number of pages (limits the size) of the database</description>
/// <description>N</description>
/// <description>0</description>
/// </item>
/// <item>
/// <description>Legacy Format</description>
/// <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>
/// <description>N</description>
/// <description>False</description>
/// </item>
/// <item>
/// <description>Default Timeout</description>
/// <description>{time in seconds}<br/>The default command timeout</description>
/// <description>N</description>
/// <description>30</description>
/// </item>
/// <item>
/// <description>Journal Mode</description>
/// <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>
/// <description>N</description>
/// <description>Delete</description>
/// </item>
/// <item>
/// <description>Read Only</description>
/// <description><b>True</b> - Open the database for read only access<br/><b>False</b> - Open the database for normal read/write access</description>
/// <description>N</description>
/// <description>False</description>
/// </item>
/// <item>
/// <description>Max Pool Size</description>
/// <description>The maximum number of connections for the given connection string that can be in the connection pool</description>
/// <description>N</description>
/// <description>100</description>
/// </item>
/// <item>
/// <description>Default IsolationLevel</description>
/// <description>The default transaciton isolation level</description>
/// <description>N</description>
/// <description>Serializable</description>
/// </item>
/// </list>
/// </remarks>
public sealed partial class SqliteConnection : DbConnection , ICloneable
{
private const string _dataDirectory = "|DataDirectory|" ;
private const string _masterdb = "sqlite_master" ;
private const string _tempmasterdb = "sqlite_temp_master" ;
/// <summary>
/// State of the current connection
/// </summary>
private ConnectionState _connectionState ;
/// <summary>
/// The connection string
/// </summary>
private string _connectionString ;
/// <summary>
/// Nesting level of the transactions open on the connection
/// </summary>
internal int _transactionLevel ;
/// <summary>
/// The default isolation level for new transactions
/// </summary>
private IsolationLevel _defaultIsolation ;
#if ! PLATFORM_COMPACTFRAMEWORK
/// <summary>
/// Whether or not the connection is enlisted in a distrubuted transaction
/// </summary>
internal SQLiteEnlistment _enlistment ;
#endif
/// <summary>
/// The base SQLite object to interop with
/// </summary>
internal SQLiteBase _sql ;
/// <summary>
/// The database filename minus path and extension
/// </summary>
private string _dataSource ;
/// <summary>
/// Temporary password storage, emptied after the database has been opened
/// </summary>
private byte [ ] _password ;
/// <summary>
/// Default command timeout
/// </summary>
private int _defaultTimeout = 30 ;
internal bool _binaryGuid ;
internal long _version ;
private event SQLiteUpdateEventHandler _updateHandler ;
private event SQLiteCommitHandler _commitHandler ;
private event EventHandler _rollbackHandler ;
private SQLiteUpdateCallback _updateCallback ;
private SQLiteCommitCallback _commitCallback ;
private SQLiteRollbackCallback _rollbackCallback ;
/// <summary>
/// This event is raised whenever the database is opened or closed.
/// </summary>
public override event StateChangeEventHandler StateChange ;
///<overloads>
/// Constructs a new SqliteConnection object
/// </overloads>
/// <summary>
/// Default constructor
/// </summary>
public SqliteConnection ( )
: this ( "" )
{
}
/// <summary>
/// Initializes the connection with the specified connection string
/// </summary>
/// <param name="connectionString">The connection string to use on the connection</param>
public SqliteConnection ( string connectionString )
{
_sql = null ;
_connectionState = ConnectionState . Closed ;
_connectionString = "" ;
_transactionLevel = 0 ;
_version = 0 ;
//_commandList = new List<WeakReference>();
if ( connectionString ! = null )
ConnectionString = connectionString ;
}
/// <summary>
/// Clones the settings and connection string from an existing connection. If the existing connection is already open, this
/// function will open its own connection, enumerate any attached databases of the original connection, and automatically
/// attach to them.
/// </summary>
/// <param name="connection"></param>
public SqliteConnection ( SqliteConnection connection )
: this ( connection . ConnectionString )
{
string str ;
if ( connection . State = = ConnectionState . Open )
{
Open ( ) ;
// Reattach all attached databases from the existing connection
using ( DataTable tbl = connection . GetSchema ( "Catalogs" ) )
{
foreach ( DataRow row in tbl . Rows )
{
str = row [ 0 ] . ToString ( ) ;
if ( String . Compare ( str , "main" , true , CultureInfo . InvariantCulture ) ! = 0
& & String . Compare ( str , "temp" , true , CultureInfo . InvariantCulture ) ! = 0 )
{
using ( SqliteCommand cmd = CreateCommand ( ) )
{
cmd . CommandText = String . Format ( CultureInfo . InvariantCulture , "ATTACH DATABASE '{0}' AS [{1}]" , row [ 1 ] , row [ 0 ] ) ;
cmd . ExecuteNonQuery ( ) ;
}
}
}
}
}
}
#if PLATFORM_COMPACTFRAMEWORK
/// <summary>
/// Obsolete
/// </summary>
public override int ConnectionTimeout
{
get
{
return 30 ;
}
}
#endif
/// <summary>
/// Creates a clone of the connection. All attached databases and user-defined functions are cloned. If the existing connection is open, the cloned connection
/// will also be opened.
/// </summary>
/// <returns></returns>
public object Clone ( )
{
return new SqliteConnection ( this ) ;
}
/// <summary>
/// Disposes of the SqliteConnection, closing it if it is active.
/// </summary>
/// <param name="disposing">True if the connection is being explicitly closed.</param>
protected override void Dispose ( bool disposing )
{
base . Dispose ( disposing ) ;
if ( _sql ! = null )
_sql . Dispose ( ) ;
if ( disposing )
Close ( ) ;
}
/// <summary>
/// Creates a database file. This just creates a zero-byte file which SQLite
/// will turn into a database when the file is opened properly.
/// </summary>
/// <param name="databaseFileName">The file to create</param>
static public void CreateFile ( string databaseFileName )
{
FileStream fs = File . Create ( databaseFileName ) ;
fs . Close ( ) ;
}
#if ! SQLITE_STANDARD
/// <summary>
/// On NTFS volumes, this function turns on the compression attribute for the given file.
/// It must not be open or referenced at the time of the function call.
/// </summary>
/// <param name="databaseFileName">The file to compress</param>
[Obsolete("This functionality is being removed from a future version of the SQLite provider")]
static public void CompressFile ( string databaseFileName )
{
UnsafeNativeMethods . sqlite3_compressfile ( databaseFileName ) ;
}
#endif
#if ! SQLITE_STANDARD
/// <summary>
/// On NTFS volumes, this function removes the compression attribute for the given file.
/// It must not be open or referenced at the time of the function call.
/// </summary>
/// <param name="databaseFileName">The file to decompress</param>
[Obsolete("This functionality is being removed from a future version of the SQLite provider")]
static public void DecompressFile ( string databaseFileName )
{
UnsafeNativeMethods . sqlite3_decompressfile ( databaseFileName ) ;
}
#endif
/// <summary>
/// Raises the state change event when the state of the connection changes
/// </summary>
/// <param name="newState">The new state. If it is different from the previous state, an event is raised.</param>
internal void OnStateChange ( ConnectionState newState )
{
ConnectionState oldState = _connectionState ;
_connectionState = newState ;
if ( StateChange ! = null & & oldState ! = newState )
{
StateChangeEventArgs e = new StateChangeEventArgs ( oldState , newState ) ;
StateChange ( this , e ) ;
}
}
/// <summary>
/// OBSOLETE. Creates a new SqliteTransaction if one isn't already active on the connection.
/// </summary>
/// <param name="isolationLevel">This parameter is ignored.</param>
/// <param name="deferredLock">When TRUE, SQLite defers obtaining a write lock until a write operation is requested.
/// When FALSE, a writelock is obtained immediately. The default is TRUE, but in a multi-threaded multi-writer
/// environment, one may instead choose to lock the database immediately to avoid any possible writer deadlock.</param>
/// <returns>Returns a SqliteTransaction object.</returns>
[Obsolete("Use one of the standard BeginTransaction methods, this one will be removed soon")]
public SqliteTransaction BeginTransaction ( IsolationLevel isolationLevel , bool deferredLock )
{
return ( SqliteTransaction ) BeginDbTransaction ( deferredLock = = false ? IsolationLevel . Serializable : IsolationLevel . ReadCommitted ) ;
}
/// <summary>
/// OBSOLETE. Creates a new SqliteTransaction if one isn't already active on the connection.
/// </summary>
/// <param name="deferredLock">When TRUE, SQLite defers obtaining a write lock until a write operation is requested.
/// When FALSE, a writelock is obtained immediately. The default is false, but in a multi-threaded multi-writer
/// environment, one may instead choose to lock the database immediately to avoid any possible writer deadlock.</param>
/// <returns>Returns a SqliteTransaction object.</returns>
[Obsolete("Use one of the standard BeginTransaction methods, this one will be removed soon")]
public SqliteTransaction BeginTransaction ( bool deferredLock )
{
return ( SqliteTransaction ) BeginDbTransaction ( deferredLock = = false ? IsolationLevel . Serializable : IsolationLevel . ReadCommitted ) ;
}
/// <summary>
/// Creates a new SqliteTransaction if one isn't already active on the connection.
/// </summary>
/// <param name="isolationLevel">Supported isolation levels are Serializable, ReadCommitted and Unspecified.</param>
/// <remarks>
/// Unspecified will use the default isolation level specified in the connection string. If no isolation level is specified in the
/// connection string, Serializable is used.
/// Serializable transactions are the default. In this mode, the engine gets an immediate lock on the database, and no other threads
/// may begin a transaction. Other threads may read from the database, but not write.
/// With a ReadCommitted isolation level, locks are deferred and elevated as needed. It is possible for multiple threads to start
/// a transaction in ReadCommitted mode, but if a thread attempts to commit a transaction while another thread
/// has a ReadCommitted lock, it may timeout or cause a deadlock on both threads until both threads' CommandTimeout's are reached.
/// </remarks>
/// <returns>Returns a SqliteTransaction object.</returns>
public new SqliteTransaction BeginTransaction ( IsolationLevel isolationLevel )
{
return ( SqliteTransaction ) BeginDbTransaction ( isolationLevel ) ;
}
/// <summary>
/// Creates a new SqliteTransaction if one isn't already active on the connection.
/// </summary>
/// <returns>Returns a SqliteTransaction object.</returns>
public new SqliteTransaction BeginTransaction ( )
{
return ( SqliteTransaction ) BeginDbTransaction ( _defaultIsolation ) ;
}
/// <summary>
/// Forwards to the local BeginTransaction() function
/// </summary>
/// <param name="isolationLevel">Supported isolation levels are Unspecified, Serializable, and ReadCommitted</param>
/// <returns></returns>
protected override DbTransaction BeginDbTransaction ( IsolationLevel isolationLevel )
{
if ( _connectionState ! = ConnectionState . Open )
throw new InvalidOperationException ( ) ;
if ( isolationLevel = = IsolationLevel . Unspecified ) isolationLevel = _defaultIsolation ;
if ( isolationLevel ! = IsolationLevel . Serializable & & isolationLevel ! = IsolationLevel . ReadCommitted )
throw new ArgumentException ( "isolationLevel" ) ;
return new SqliteTransaction ( this , isolationLevel ! = IsolationLevel . Serializable ) ;
}
/// <summary>
/// Not implemented
/// </summary>
/// <param name="databaseName"></param>
public override void ChangeDatabase ( string databaseName )
{
throw new NotImplementedException ( ) ;
}
/// <summary>
/// When the database connection is closed, all commands linked to this connection are automatically reset.
/// </summary>
public override void Close ( )
{
if ( _sql ! = null )
{
#if ! PLATFORM_COMPACTFRAMEWORK
if ( _enlistment ! = null )
{
// If the connection is enlisted in a transaction scope and the scope is still active,
// we cannot truly shut down this connection until the scope has completed. Therefore make a
// hidden connection temporarily to hold open the connection until the scope has completed.
SqliteConnection cnn = new SqliteConnection ( ) ;
cnn . _sql = _sql ;
cnn . _transactionLevel = _transactionLevel ;
cnn . _enlistment = _enlistment ;
cnn . _connectionState = _connectionState ;
cnn . _version = _version ;
cnn . _enlistment . _transaction . _cnn = cnn ;
cnn . _enlistment . _disposeConnection = true ;
_sql = null ;
_enlistment = null ;
}
#endif
if ( _sql ! = null )
{
_sql . Close ( ) ;
}
_sql = null ;
_transactionLevel = 0 ;
}
OnStateChange ( ConnectionState . Closed ) ;
}
/// <summary>
/// Clears the connection pool associated with the connection. Any other active connections using the same database file
/// will be discarded instead of returned to the pool when they are closed.
/// </summary>
/// <param name="connection"></param>
public static void ClearPool ( SqliteConnection connection )
{
if ( connection . _sql = = null ) return ;
connection . _sql . ClearPool ( ) ;
}
/// <summary>
/// Clears all connection pools. Any active connections will be discarded instead of sent to the pool when they are closed.
/// </summary>
public static void ClearAllPools ( )
{
SqliteConnectionPool . ClearAllPools ( ) ;
}
/// <summary>
/// The connection string containing the parameters for the connection
/// </summary>
/// <remarks>
/// <list type="table">
/// <listheader>
/// <term>Parameter</term>
/// <term>Values</term>
/// <term>Required</term>
/// <term>Default</term>
/// </listheader>
/// <item>
/// <description>Data Source</description>
/// <description>{filename}</description>
/// <description>Y</description>
/// <description></description>
/// </item>
/// <item>
/// <description>Version</description>
/// <description>3</description>
/// <description>N</description>
/// <description>3</description>
/// </item>
/// <item>
/// <description>UseUTF16Encoding</description>
/// <description><b>True</b><br/><b>False</b></description>
/// <description>N</description>
/// <description>False</description>
/// </item>
/// <item>
/// <description>DateTimeFormat</description>
/// <description><b>Ticks</b> - Use DateTime.Ticks<br/><b>ISO8601</b> - Use ISO8601 DateTime format<br/><b>JulianDay</b> - Use JulianDay format</description>
/// <description>N</description>
/// <description>ISO8601</description>
/// </item>
/// <item>
/// <description>BinaryGUID</description>
/// <description><b>Yes/On/1</b> - Store GUID columns in binary form<br/><b>No/Off/0</b> - Store GUID columns as text</description>
/// <description>N</description>
/// <description>On</description>
/// </item>
/// <item>
/// <description>Cache Size</description>
/// <description>{size in bytes}</description>
/// <description>N</description>
/// <description>2000</description>
/// </item>
/// <item>
/// <description>Synchronous</description>
/// <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>
/// <description>N</description>
/// <description>Normal</description>
/// </item>
/// <item>
/// <description>Page Size</description>
/// <description>{size in bytes}</description>
/// <description>N</description>
/// <description>1024</description>
/// </item>
/// <item>
/// <description>Password</description>
/// <description>{password}</description>
/// <description>N</description>
/// <description></description>
/// </item>
/// <item>
/// <description>Enlist</description>
/// <description><B>Y</B> - Automatically enlist in distributed transactions<br/><b>N</b> - No automatic enlistment</description>
/// <description>N</description>
/// <description>Y</description>
/// </item>
/// <item>
/// <description>Pooling</description>
/// <description><b>True</b> - Use connection pooling<br/><b>False</b> - Do not use connection pooling</description>
/// <description>N</description>
/// <description>False</description>
/// </item>
/// <item>
/// <description>FailIfMissing</description>
/// <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>
/// <description>N</description>
/// <description>False</description>
/// </item>
/// <item>
/// <description>Max Page Count</description>
/// <description>{size in pages} - Limits the maximum number of pages (limits the size) of the database</description>
/// <description>N</description>
/// <description>0</description>
/// </item>
/// <item>
/// <description>Legacy Format</description>
/// <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>
/// <description>N</description>
/// <description>False</description>
/// </item>
/// <item>
/// <description>Default Timeout</description>
/// <description>{time in seconds}<br/>The default command timeout</description>
/// <description>N</description>
/// <description>30</description>
/// </item>
/// <item>
/// <description>Journal Mode</description>
/// <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>
/// <description>N</description>
/// <description>Delete</description>
/// </item>
/// <item>
/// <description>Read Only</description>
/// <description><b>True</b> - Open the database for read only access<br/><b>False</b> - Open the database for normal read/write access</description>
/// <description>N</description>
/// <description>False</description>
/// </item>
/// <item>
/// <description>Max Pool Size</description>
/// <description>The maximum number of connections for the given connection string that can be in the connection pool</description>
/// <description>N</description>
/// <description>100</description>
/// </item>
/// <item>
/// <description>Default IsolationLevel</description>
/// <description>The default transaciton isolation level</description>
/// <description>N</description>
/// <description>Serializable</description>
/// </item>
/// </list>
/// </remarks>
#if ! PLATFORM_COMPACTFRAMEWORK
[RefreshProperties(RefreshProperties.All), DefaultValue("")]
[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")]
#endif
public override string ConnectionString
{
get
{
return _connectionString ;
}
set
{
if ( value = = null )
throw new ArgumentNullException ( ) ;
else if ( _connectionState ! = ConnectionState . Closed )
throw new InvalidOperationException ( ) ;
_connectionString = value ;
}
}
/// <summary>
/// Create a new SqliteCommand and associate it with this connection.
/// </summary>
/// <returns>Returns an instantiated SqliteCommand object already assigned to this connection.</returns>
public new SqliteCommand CreateCommand ( )
{
return new SqliteCommand ( this ) ;
}
/// <summary>
/// Forwards to the local CreateCommand() function
/// </summary>
/// <returns></returns>
protected override DbCommand CreateDbCommand ( )
{
return CreateCommand ( ) ;
}
/// <summary>
/// Returns the filename without extension or path
/// </summary>
#if ! PLATFORM_COMPACTFRAMEWORK
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
#endif
public override string DataSource
{
get
{
return _dataSource ;
}
}
/// <summary>
/// Returns an empty string
/// </summary>
#if ! PLATFORM_COMPACTFRAMEWORK
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
#endif
public override string Database
{
get
{
return "main" ;
}
}
/// <summary>
/// Maps mono-specific connection string keywords to the standard ones
/// </summary>
/// <returns>The mapped keyword name</returns>
internal static void MapMonoKeyword ( string [ ] arPiece , SortedList < string , string > ls )
{
string keyword , value ;
switch ( arPiece [ 0 ] . ToLower ( CultureInfo . InvariantCulture ) ) {
case "uri" :
keyword = "Data Source" ;
value = MapMonoUriPath ( arPiece [ 1 ] ) ;
break ;
default :
keyword = arPiece [ 0 ] ;
value = arPiece [ 1 ] ;
break ;
}
ls . Add ( keyword , value ) ;
}
internal static string MapMonoUriPath ( string path )
{
if ( path . StartsWith ( "file://" ) ) {
return path . Substring ( 7 ) ;
} else if ( path . StartsWith ( "file:" ) ) {
return path . Substring ( 5 ) ;
} else if ( path . StartsWith ( "/" ) ) {
return path ;
} else {
throw new InvalidOperationException ( "Invalid connection string: invalid URI" ) ;
}
}
internal static string MapUriPath ( string path )
{
if ( path . StartsWith ( "file://" ) )
return path . Substring ( 7 ) ;
else if ( path . StartsWith ( "file:" ) )
return path . Substring ( 5 ) ;
else if ( path . StartsWith ( "/" ) )
return path ;
else
throw new InvalidOperationException ( "Invalid connection string: invalid URI" ) ;
}
/// <summary>
/// Parses the connection string into component parts
/// </summary>
/// <param name="connectionString">The connection string to parse</param>
/// <returns>An array of key-value pairs representing each parameter of the connection string</returns>
internal static SortedList < string , string > ParseConnectionString ( string connectionString )
{
string s = connectionString . Replace ( ',' , ';' ) ; // Mono compatibility
int n ;
SortedList < string , string > ls = new SortedList < string , string > ( StringComparer . OrdinalIgnoreCase ) ;
// First split into semi-colon delimited values. The Split() function of SQLiteBase accounts for and properly
// skips semi-colons in quoted strings
string [ ] arParts = SqliteConvert . Split ( s , ';' ) ;
string [ ] arPiece ;
int x = arParts . Length ;
// For each semi-colon piece, split into key and value pairs by the presence of the = sign
for ( n = 0 ; n < x ; n + + )
{
arPiece = SqliteConvert . Split ( arParts [ n ] , '=' ) ;
if ( arPiece . Length = = 2 )
{
MapMonoKeyword ( arPiece , ls ) ;
}
else throw new ArgumentException ( String . Format ( CultureInfo . CurrentCulture , "Invalid ConnectionString format for parameter \"{0}\"" , ( arPiece . Length > 0 ) ? arPiece [ 0 ] : "null" ) ) ;
}
return ls ;
}
#if ! PLATFORM_COMPACTFRAMEWORK
/// <summary>
/// Manual distributed transaction enlistment support
/// </summary>
/// <param name="transaction">The distributed transaction to enlist in</param>
public override void EnlistTransaction ( System . Transactions . Transaction transaction )
{
if ( _transactionLevel > 0 & & transaction ! = null )
throw new ArgumentException ( "Unable to enlist in transaction, a local transaction already exists" ) ;
if ( _enlistment ! = null & & transaction ! = _enlistment . _scope )
throw new ArgumentException ( "Already enlisted in a transaction" ) ;
_enlistment = new SQLiteEnlistment ( this , transaction ) ;
}
#endif
/// <summary>
/// Looks for a key in the array of key/values of the parameter string. If not found, return the specified default value
/// </summary>
/// <param name="items">The list to look in</param>
/// <param name="key">The key to find</param>
/// <param name="defValue">The default value to return if the key is not found</param>
/// <returns>The value corresponding to the specified key, or the default value if not found.</returns>
static internal string FindKey ( SortedList < string , string > items , string key , string defValue )
{
string ret ;
if ( items . TryGetValue ( key , out ret ) ) return ret ;
return defValue ;
}
/// <summary>
/// Opens the connection using the parameters found in the <see cref="ConnectionString">ConnectionString</see>
/// </summary>
public override void Open ( )
{
if ( _connectionState ! = ConnectionState . Closed )
throw new InvalidOperationException ( ) ;
Close ( ) ;
SortedList < string , string > opts = ParseConnectionString ( _connectionString ) ;
string fileName ;
if ( Convert . ToInt32 ( FindKey ( opts , "Version" , "3" ) , CultureInfo . InvariantCulture ) ! = 3 )
throw new NotSupportedException ( "Only SQLite Version 3 is supported at this time" ) ;
fileName = FindKey ( opts , "Data Source" , "" ) ;
if ( String . IsNullOrEmpty ( fileName ) )
{
fileName = FindKey ( opts , "Uri" , "" ) ;
if ( String . IsNullOrEmpty ( fileName ) )
throw new ArgumentException ( "Data Source cannot be empty. Use :memory: to open an in-memory database" ) ;
else
fileName = MapUriPath ( fileName ) ;
}
if ( String . Compare ( fileName , ":MEMORY:" , true , CultureInfo . InvariantCulture ) = = 0 )
fileName = ":memory:" ;
else
{
#if PLATFORM_COMPACTFRAMEWORK
if ( fileName . StartsWith ( ".\\" ) )
fileName = Path . GetDirectoryName ( System . Reflection . Assembly . GetCallingAssembly ( ) . GetName ( ) . CodeBase ) + fileName . Substring ( 1 ) ;
#endif
fileName = ExpandFileName ( fileName ) ;
}
try
{
bool usePooling = ( SqliteConvert . ToBoolean ( FindKey ( opts , "Pooling" , Boolean . FalseString ) ) = = true ) ;
bool bUTF16 = ( SqliteConvert . ToBoolean ( FindKey ( opts , "UseUTF16Encoding" , Boolean . FalseString ) ) = = true ) ;
int maxPoolSize = Convert . ToInt32 ( FindKey ( opts , "Max Pool Size" , "100" ) ) ;
_defaultTimeout = Convert . ToInt32 ( FindKey ( opts , "Default Timeout" , "30" ) , CultureInfo . CurrentCulture ) ;
_defaultIsolation = ( IsolationLevel ) Enum . Parse ( typeof ( IsolationLevel ) , FindKey ( opts , "Default IsolationLevel" , "Serializable" ) , true ) ;
if ( _defaultIsolation ! = IsolationLevel . Serializable & & _defaultIsolation ! = IsolationLevel . ReadCommitted )
throw new NotSupportedException ( "Invalid Default IsolationLevel specified" ) ;
SQLiteDateFormats dateFormat = ( SQLiteDateFormats ) Enum . Parse ( typeof ( SQLiteDateFormats ) , FindKey ( opts , "DateTimeFormat" , "ISO8601" ) , true ) ;
//string temp = FindKey(opts, "DateTimeFormat", "ISO8601");
//if (String.Compare(temp, "ticks", true, CultureInfo.InvariantCulture) == 0) dateFormat = SQLiteDateFormats.Ticks;
//else if (String.Compare(temp, "julianday", true, CultureInfo.InvariantCulture) == 0) dateFormat = SQLiteDateFormats.JulianDay;
if ( bUTF16 ) // SQLite automatically sets the encoding of the database to UTF16 if called from sqlite3_open16()
_sql = new SQLite3_UTF16 ( dateFormat ) ;
else
_sql = new SQLite3 ( dateFormat ) ;
SQLiteOpenFlagsEnum flags = SQLiteOpenFlagsEnum . None ;
if ( SqliteConvert . ToBoolean ( FindKey ( opts , "Read Only" , Boolean . FalseString ) ) = = true )
flags | = SQLiteOpenFlagsEnum . ReadOnly ;
else {
flags | = SQLiteOpenFlagsEnum . ReadWrite ;
if ( SqliteConvert . ToBoolean ( FindKey ( opts , "FailIfMissing" , Boolean . FalseString ) ) = = false )
flags | = SQLiteOpenFlagsEnum . Create ;
}
if ( SqliteConvert . ToBoolean ( FindKey ( opts , "FileProtectionComplete" , Boolean . FalseString ) ) )
flags | = SQLiteOpenFlagsEnum . FileProtectionComplete ;
if ( SqliteConvert . ToBoolean ( FindKey ( opts , "FileProtectionCompleteUnlessOpen" , Boolean . FalseString ) ) )
flags | = SQLiteOpenFlagsEnum . FileProtectionCompleteUnlessOpen ;
if ( SqliteConvert . ToBoolean ( FindKey ( opts , "FileProtectionCompleteUntilFirstUserAuthentication" , Boolean . FalseString ) ) )
flags | = SQLiteOpenFlagsEnum . FileProtectionCompleteUntilFirstUserAuthentication ;
if ( SqliteConvert . ToBoolean ( FindKey ( opts , "FileProtectionNone" , Boolean . FalseString ) ) )
flags | = SQLiteOpenFlagsEnum . FileProtectionNone ;
_sql . Open ( fileName , flags , maxPoolSize , usePooling ) ;
_binaryGuid = ( SqliteConvert . ToBoolean ( FindKey ( opts , "BinaryGUID" , Boolean . TrueString ) ) = = true ) ;
string password = FindKey ( opts , "Password" , null ) ;
if ( String . IsNullOrEmpty ( password ) = = false )
_sql . SetPassword ( System . Text . UTF8Encoding . UTF8 . GetBytes ( password ) ) ;
else if ( _password ! = null )
_sql . SetPassword ( _password ) ;
_password = null ;
_dataSource = Path . GetFileNameWithoutExtension ( fileName ) ;
OnStateChange ( ConnectionState . Open ) ;
_version + + ;
using ( SqliteCommand cmd = CreateCommand ( ) )
{
string defValue ;
if ( fileName ! = ":memory:" )
{
defValue = FindKey ( opts , "Page Size" , "1024" ) ;
if ( Convert . ToInt32 ( defValue , CultureInfo . InvariantCulture ) ! = 1024 )
{
cmd . CommandText = String . Format ( CultureInfo . InvariantCulture , "PRAGMA page_size={0}" , defValue ) ;
cmd . ExecuteNonQuery ( ) ;
}
}
defValue = FindKey ( opts , "Max Page Count" , "0" ) ;
if ( Convert . ToInt32 ( defValue , CultureInfo . InvariantCulture ) ! = 0 )
{
cmd . CommandText = String . Format ( CultureInfo . InvariantCulture , "PRAGMA max_page_count={0}" , defValue ) ;
cmd . ExecuteNonQuery ( ) ;
}
defValue = FindKey ( opts , "Legacy Format" , Boolean . FalseString ) ;
cmd . CommandText = String . Format ( CultureInfo . InvariantCulture , "PRAGMA legacy_file_format={0}" , SqliteConvert . ToBoolean ( defValue ) = = true ? "ON" : "OFF" ) ;
cmd . ExecuteNonQuery ( ) ;
defValue = FindKey ( opts , "Synchronous" , "Normal" ) ;
if ( String . Compare ( defValue , "Full" , StringComparison . OrdinalIgnoreCase ) ! = 0 )
{
cmd . CommandText = String . Format ( CultureInfo . InvariantCulture , "PRAGMA synchronous={0}" , defValue ) ;
cmd . ExecuteNonQuery ( ) ;
}
defValue = FindKey ( opts , "Cache Size" , "2000" ) ;
if ( Convert . ToInt32 ( defValue , CultureInfo . InvariantCulture ) ! = 2000 )
{
cmd . CommandText = String . Format ( CultureInfo . InvariantCulture , "PRAGMA cache_size={0}" , defValue ) ;
cmd . ExecuteNonQuery ( ) ;
}
defValue = FindKey ( opts , "Journal Mode" , "Delete" ) ;
if ( String . Compare ( defValue , "Default" , StringComparison . OrdinalIgnoreCase ) ! = 0 )
{
cmd . CommandText = String . Format ( CultureInfo . InvariantCulture , "PRAGMA journal_mode={0}" , defValue ) ;
cmd . ExecuteNonQuery ( ) ;
}
}
if ( _commitHandler ! = null )
_sql . SetCommitHook ( _commitCallback ) ;
if ( _updateHandler ! = null )
_sql . SetUpdateHook ( _updateCallback ) ;
if ( _rollbackHandler ! = null )
_sql . SetRollbackHook ( _rollbackCallback ) ;
#if ! PLATFORM_COMPACTFRAMEWORK
if ( global :: System . Transactions . Transaction . Current ! = null & & SqliteConvert . ToBoolean ( FindKey ( opts , "Enlist" , Boolean . TrueString ) ) = = true )
EnlistTransaction ( global :: System . Transactions . Transaction . Current ) ;
#endif
}
catch ( SqliteException )
{
Close ( ) ;
throw ;
}
}
/// <summary>
/// Gets/sets the default command timeout for newly-created commands. This is especially useful for
/// commands used internally such as inside a SqliteTransaction, where setting the timeout is not possible.
/// This can also be set in the ConnectionString with "Default Timeout"
/// </summary>
public int DefaultTimeout
{
get { return _defaultTimeout ; }
set { _defaultTimeout = value ; }
}
/// <summary>
/// Returns the version of the underlying SQLite database engine
/// </summary>
#if ! PLATFORM_COMPACTFRAMEWORK
[Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
#endif
public override string ServerVersion
{
get
{
if ( _connectionState ! = ConnectionState . Open )
throw new InvalidOperationException ( ) ;
return _sql . Version ;
}
}
/// <summary>
/// Returns the version of the underlying SQLite database engine
/// </summary>
public static string SQLiteVersion
{
get { return SQLite3 . SQLiteVersion ; }
}
/// <summary>
/// Returns the state of the connection.
/// </summary>
#if ! PLATFORM_COMPACTFRAMEWORK
[Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
#endif
public override ConnectionState State
{
get
{
return _connectionState ;
}
}
/// <summary>
/// Change the password (or assign a password) to an open database.
/// </summary>
/// <remarks>
/// No readers or writers may be active for this process. The database must already be open
/// and if it already was password protected, the existing password must already have been supplied.
/// </remarks>
/// <param name="newPassword">The new password to assign to the database</param>
public void ChangePassword ( string newPassword )
{
ChangePassword ( String . IsNullOrEmpty ( newPassword ) ? null : System . Text . UTF8Encoding . UTF8 . GetBytes ( newPassword ) ) ;
}
/// <summary>
/// Change the password (or assign a password) to an open database.
/// </summary>
/// <remarks>
/// No readers or writers may be active for this process. The database must already be open
/// and if it already was password protected, the existing password must already have been supplied.
/// </remarks>
/// <param name="newPassword">The new password to assign to the database</param>
public void ChangePassword ( byte [ ] newPassword )
{
if ( _connectionState ! = ConnectionState . Open )
throw new InvalidOperationException ( "Database must be opened before changing the password." ) ;
_sql . ChangePassword ( newPassword ) ;
}
/// <summary>
/// Sets the password for a password-protected database. A password-protected database is
/// unusable for any operation until the password has been set.
/// </summary>
/// <param name="databasePassword">The password for the database</param>
public void SetPassword ( string databasePassword )
{
SetPassword ( String . IsNullOrEmpty ( databasePassword ) ? null : System . Text . UTF8Encoding . UTF8 . GetBytes ( databasePassword ) ) ;
}
/// <summary>
/// Sets the password for a password-protected database. A password-protected database is
/// unusable for any operation until the password has been set.
/// </summary>
/// <param name="databasePassword">The password for the database</param>
public void SetPassword ( byte [ ] databasePassword )
{
if ( _connectionState ! = ConnectionState . Closed )
throw new InvalidOperationException ( "Password can only be set before the database is opened." ) ;
if ( databasePassword ! = null )
if ( databasePassword . Length = = 0 ) databasePassword = null ;
_password = databasePassword ;
}
/// <summary>
/// Expand the filename of the data source, resolving the |DataDirectory| macro as appropriate.
/// </summary>
/// <param name="sourceFile">The database filename to expand</param>
/// <returns>The expanded path and filename of the filename</returns>
private string ExpandFileName ( string sourceFile )
{
if ( String . IsNullOrEmpty ( sourceFile ) ) return sourceFile ;
if ( sourceFile . StartsWith ( _dataDirectory , StringComparison . OrdinalIgnoreCase ) )
{
string dataDirectory ;
#if PLATFORM_COMPACTFRAMEWORK
dataDirectory = Path . GetDirectoryName ( System . Reflection . Assembly . GetCallingAssembly ( ) . GetName ( ) . CodeBase ) ;
#else
dataDirectory = AppDomain . CurrentDomain . GetData ( "DataDirectory" ) as string ;
if ( String . IsNullOrEmpty ( dataDirectory ) )
dataDirectory = AppDomain . CurrentDomain . BaseDirectory ;
#endif
if ( sourceFile . Length > _dataDirectory . Length )
{
if ( sourceFile [ _dataDirectory . Length ] = = Path . DirectorySeparatorChar | |
sourceFile [ _dataDirectory . Length ] = = Path . AltDirectorySeparatorChar )
sourceFile = sourceFile . Remove ( _dataDirectory . Length , 1 ) ;
}
sourceFile = Path . Combine ( dataDirectory , sourceFile . Substring ( _dataDirectory . Length ) ) ;
}
#if ! PLATFORM_COMPACTFRAMEWORK
sourceFile = Path . GetFullPath ( sourceFile ) ;
#endif
return sourceFile ;
}
///<overloads>
/// The following commands are used to extract schema information out of the database. Valid schema types are:
/// <list type="bullet">
/// <item>
/// <description>MetaDataCollections</description>
/// </item>
/// <item>
/// <description>DataSourceInformation</description>
/// </item>
/// <item>
/// <description>Catalogs</description>
/// </item>
/// <item>
/// <description>Columns</description>
/// </item>
/// <item>
/// <description>ForeignKeys</description>
/// </item>
/// <item>
/// <description>Indexes</description>
/// </item>
/// <item>
/// <description>IndexColumns</description>
/// </item>
/// <item>
/// <description>Tables</description>
/// </item>
/// <item>
/// <description>Views</description>
/// </item>
/// <item>
/// <description>ViewColumns</description>
/// </item>
/// </list>
/// </overloads>
/// <summary>
/// Returns the MetaDataCollections schema
/// </summary>
/// <returns>A DataTable of the MetaDataCollections schema</returns>
public override DataTable GetSchema ( )
{
return GetSchema ( "MetaDataCollections" , null ) ;
}
/// <summary>
/// Returns schema information of the specified collection
/// </summary>
/// <param name="collectionName">The schema collection to retrieve</param>
/// <returns>A DataTable of the specified collection</returns>
public override DataTable GetSchema ( string collectionName )
{
return GetSchema ( collectionName , new string [ 0 ] ) ;
}
/// <summary>
/// Retrieves schema information using the specified constraint(s) for the specified collection
/// </summary>
/// <param name="collectionName">The collection to retrieve</param>
/// <param name="restrictionValues">The restrictions to impose</param>
/// <returns>A DataTable of the specified collection</returns>
public override DataTable GetSchema ( string collectionName , string [ ] restrictionValues )
{
if ( _connectionState ! = ConnectionState . Open )
throw new InvalidOperationException ( ) ;
string [ ] parms = new string [ 5 ] ;
if ( restrictionValues = = null ) restrictionValues = new string [ 0 ] ;
restrictionValues . CopyTo ( parms , 0 ) ;
switch ( collectionName . ToUpper ( CultureInfo . InvariantCulture ) )
{
case "METADATACOLLECTIONS" :
return Schema_MetaDataCollections ( ) ;
case "DATASOURCEINFORMATION" :
return Schema_DataSourceInformation ( ) ;
case "DATATYPES" :
return Schema_DataTypes ( ) ;
case "COLUMNS" :
case "TABLECOLUMNS" :
return Schema_Columns ( parms [ 0 ] , parms [ 2 ] , parms [ 3 ] ) ;
case "INDEXES" :
return Schema_Indexes ( parms [ 0 ] , parms [ 2 ] , parms [ 3 ] ) ;
case "TRIGGERS" :
return Schema_Triggers ( parms [ 0 ] , parms [ 2 ] , parms [ 3 ] ) ;
case "INDEXCOLUMNS" :
return Schema_IndexColumns ( parms [ 0 ] , parms [ 2 ] , parms [ 3 ] , parms [ 4 ] ) ;
case "TABLES" :
return Schema_Tables ( parms [ 0 ] , parms [ 2 ] , parms [ 3 ] ) ;
case "VIEWS" :
return Schema_Views ( parms [ 0 ] , parms [ 2 ] ) ;
case "VIEWCOLUMNS" :
return Schema_ViewColumns ( parms [ 0 ] , parms [ 2 ] , parms [ 3 ] ) ;
case "FOREIGNKEYS" :
return Schema_ForeignKeys ( parms [ 0 ] , parms [ 2 ] , parms [ 3 ] ) ;
case "CATALOGS" :
return Schema_Catalogs ( parms [ 0 ] ) ;
case "RESERVEDWORDS" :
return Schema_ReservedWords ( ) ;
}
throw new NotSupportedException ( ) ;
}
private static DataTable Schema_ReservedWords ( )
{
DataTable tbl = new DataTable ( "MetaDataCollections" ) ;
tbl . Locale = CultureInfo . InvariantCulture ;
tbl . Columns . Add ( "ReservedWord" , typeof ( string ) ) ;
tbl . Columns . Add ( "MaximumVersion" , typeof ( string ) ) ;
tbl . Columns . Add ( "MinimumVersion" , typeof ( string ) ) ;
tbl . BeginLoadData ( ) ;
DataRow row ;
foreach ( string word in SR . Keywords . Split ( new char [ ] { ',' } ) )
{
row = tbl . NewRow ( ) ;
row [ 0 ] = word ;
tbl . Rows . Add ( row ) ;
}
tbl . AcceptChanges ( ) ;
tbl . EndLoadData ( ) ;
return tbl ;
}
/// <summary>
/// Builds a MetaDataCollections schema datatable
/// </summary>
/// <returns>DataTable</returns>
private static DataTable Schema_MetaDataCollections ( )
{
DataTable tbl = new DataTable ( "MetaDataCollections" ) ;
tbl . Locale = CultureInfo . InvariantCulture ;
tbl . Columns . Add ( "CollectionName" , typeof ( string ) ) ;
tbl . Columns . Add ( "NumberOfRestrictions" , typeof ( int ) ) ;
tbl . Columns . Add ( "NumberOfIdentifierParts" , typeof ( int ) ) ;
tbl . BeginLoadData ( ) ;
StringReader reader = new StringReader ( SR . MetaDataCollections ) ;
tbl . ReadXml ( reader ) ;
reader . Close ( ) ;
tbl . AcceptChanges ( ) ;
tbl . EndLoadData ( ) ;
return tbl ;
}
/// <summary>
/// Builds a DataSourceInformation datatable
/// </summary>
/// <returns>DataTable</returns>
private DataTable Schema_DataSourceInformation ( )
{
DataTable tbl = new DataTable ( "DataSourceInformation" ) ;
DataRow row ;
tbl . Locale = CultureInfo . InvariantCulture ;
tbl . Columns . Add ( DbMetaDataColumnNames . CompositeIdentifierSeparatorPattern , typeof ( string ) ) ;
tbl . Columns . Add ( DbMetaDataColumnNames . DataSourceProductName , typeof ( string ) ) ;
tbl . Columns . Add ( DbMetaDataColumnNames . DataSourceProductVersion , typeof ( string ) ) ;
tbl . Columns . Add ( DbMetaDataColumnNames . DataSourceProductVersionNormalized , typeof ( string ) ) ;
tbl . Columns . Add ( DbMetaDataColumnNames . GroupByBehavior , typeof ( int ) ) ;
tbl . Columns . Add ( DbMetaDataColumnNames . IdentifierPattern , typeof ( string ) ) ;
tbl . Columns . Add ( DbMetaDataColumnNames . IdentifierCase , typeof ( int ) ) ;
tbl . Columns . Add ( DbMetaDataColumnNames . OrderByColumnsInSelect , typeof ( bool ) ) ;
tbl . Columns . Add ( DbMetaDataColumnNames . ParameterMarkerFormat , typeof ( string ) ) ;
tbl . Columns . Add ( DbMetaDataColumnNames . ParameterMarkerPattern , typeof ( string ) ) ;
tbl . Columns . Add ( DbMetaDataColumnNames . ParameterNameMaxLength , typeof ( int ) ) ;
tbl . Columns . Add ( DbMetaDataColumnNames . ParameterNamePattern , typeof ( string ) ) ;
tbl . Columns . Add ( DbMetaDataColumnNames . QuotedIdentifierPattern , typeof ( string ) ) ;
tbl . Columns . Add ( DbMetaDataColumnNames . QuotedIdentifierCase , typeof ( int ) ) ;
tbl . Columns . Add ( DbMetaDataColumnNames . StatementSeparatorPattern , typeof ( string ) ) ;
tbl . Columns . Add ( DbMetaDataColumnNames . StringLiteralPattern , typeof ( string ) ) ;
tbl . Columns . Add ( DbMetaDataColumnNames . SupportedJoinOperators , typeof ( int ) ) ;
tbl . BeginLoadData ( ) ;
row = tbl . NewRow ( ) ;
row . ItemArray = new object [ ] {
null ,
"SQLite" ,
_sql . Version ,
_sql . Version ,
3 ,
@"(^\[\p{Lo}\p{Lu}\p{Ll}_@#][\p{Lo}\p{Lu}\p{Ll}\p{Nd}@$#_]*$)|(^\[[^\]\0]|\]\]+\]$)|(^\""[^\""\0]|\""\""+\""$)" ,
1 ,
false ,
"{0}" ,
@"@[\p{Lo}\p{Lu}\p{Ll}\p{Lm}_@#][\p{Lo}\p{Lu}\p{Ll}\p{Lm}\p{Nd}\uff3f_@#\$]*(?=\s+|$)" ,
255 ,
@"^[\p{Lo}\p{Lu}\p{Ll}\p{Lm}_@#][\p{Lo}\p{Lu}\p{Ll}\p{Lm}\p{Nd}\uff3f_@#\$]*(?=\s+|$)" ,
@"(([^\[]|\]\])*)" ,
1 ,
";" ,
@"'(([^']|'')*)'" ,
15
} ;
tbl . Rows . Add ( row ) ;
tbl . AcceptChanges ( ) ;
tbl . EndLoadData ( ) ;
return tbl ;
}
/// <summary>
/// Build a Columns schema
/// </summary>
/// <param name="strCatalog">The catalog (attached database) to query, can be null</param>
/// <param name="strTable">The table to retrieve schema information for, must not be null</param>
/// <param name="strColumn">The column to retrieve schema information for, can be null</param>
/// <returns>DataTable</returns>
private DataTable Schema_Columns ( string strCatalog , string strTable , string strColumn )
{
DataTable tbl = new DataTable ( "Columns" ) ;
DataRow row ;
tbl . Locale = CultureInfo . InvariantCulture ;
tbl . Columns . Add ( "TABLE_CATALOG" , typeof ( string ) ) ;
tbl . Columns . Add ( "TABLE_SCHEMA" , typeof ( string ) ) ;
tbl . Columns . Add ( "TABLE_NAME" , typeof ( string ) ) ;
tbl . Columns . Add ( "COLUMN_NAME" , typeof ( string ) ) ;
tbl . Columns . Add ( "COLUMN_GUID" , typeof ( Guid ) ) ;
tbl . Columns . Add ( "COLUMN_PROPID" , typeof ( long ) ) ;
tbl . Columns . Add ( "ORDINAL_POSITION" , typeof ( int ) ) ;
tbl . Columns . Add ( "COLUMN_HASDEFAULT" , typeof ( bool ) ) ;
tbl . Columns . Add ( "COLUMN_DEFAULT" , typeof ( string ) ) ;
tbl . Columns . Add ( "COLUMN_FLAGS" , typeof ( long ) ) ;
tbl . Columns . Add ( "IS_NULLABLE" , typeof ( bool ) ) ;
tbl . Columns . Add ( "DATA_TYPE" , typeof ( string ) ) ;
tbl . Columns . Add ( "TYPE_GUID" , typeof ( Guid ) ) ;
tbl . Columns . Add ( "CHARACTER_MAXIMUM_LENGTH" , typeof ( int ) ) ;
tbl . Columns . Add ( "CHARACTER_OCTET_LENGTH" , typeof ( int ) ) ;
tbl . Columns . Add ( "NUMERIC_PRECISION" , typeof ( int ) ) ;
tbl . Columns . Add ( "NUMERIC_SCALE" , typeof ( int ) ) ;
tbl . Columns . Add ( "DATETIME_PRECISION" , typeof ( long ) ) ;
tbl . Columns . Add ( "CHARACTER_SET_CATALOG" , typeof ( string ) ) ;
tbl . Columns . Add ( "CHARACTER_SET_SCHEMA" , typeof ( string ) ) ;
tbl . Columns . Add ( "CHARACTER_SET_NAME" , typeof ( string ) ) ;
tbl . Columns . Add ( "COLLATION_CATALOG" , typeof ( string ) ) ;
tbl . Columns . Add ( "COLLATION_SCHEMA" , typeof ( string ) ) ;
tbl . Columns . Add ( "COLLATION_NAME" , typeof ( string ) ) ;
tbl . Columns . Add ( "DOMAIN_CATALOG" , typeof ( string ) ) ;
tbl . Columns . Add ( "DOMAIN_NAME" , typeof ( string ) ) ;
tbl . Columns . Add ( "DESCRIPTION" , typeof ( string ) ) ;
tbl . Columns . Add ( "PRIMARY_KEY" , typeof ( bool ) ) ;
tbl . Columns . Add ( "EDM_TYPE" , typeof ( string ) ) ;
tbl . Columns . Add ( "AUTOINCREMENT" , typeof ( bool ) ) ;
tbl . Columns . Add ( "UNIQUE" , typeof ( bool ) ) ;
tbl . BeginLoadData ( ) ;
if ( String . IsNullOrEmpty ( strCatalog ) ) strCatalog = "main" ;
string master = ( String . Compare ( strCatalog , "temp" , true , CultureInfo . InvariantCulture ) = = 0 ) ? _tempmasterdb : _masterdb ;
using ( SqliteCommand cmdTables = new SqliteCommand ( String . Format ( CultureInfo . InvariantCulture , "SELECT * FROM [{0}].[{1}] WHERE [type] LIKE 'table' OR [type] LIKE 'view'" , strCatalog , master ) , this ) )
using ( SqliteDataReader rdTables = cmdTables . ExecuteReader ( ) )
{
while ( rdTables . Read ( ) )
{
if ( String . IsNullOrEmpty ( strTable ) | | String . Compare ( strTable , rdTables . GetString ( 2 ) , true , CultureInfo . InvariantCulture ) = = 0 )
{
try
{
using ( SqliteCommand cmd = new SqliteCommand ( String . Format ( CultureInfo . InvariantCulture , "SELECT * FROM [{0}].[{1}]" , strCatalog , rdTables . GetString ( 2 ) ) , this ) )
using ( SqliteDataReader rd = ( SqliteDataReader ) cmd . ExecuteReader ( CommandBehavior . SchemaOnly ) )
using ( DataTable tblSchema = rd . GetSchemaTable ( true , true ) )
{
foreach ( DataRow schemaRow in tblSchema . Rows )
{
if ( String . Compare ( schemaRow [ SchemaTableColumn . ColumnName ] . ToString ( ) , strColumn , true , CultureInfo . InvariantCulture ) = = 0
| | strColumn = = null )
{
row = tbl . NewRow ( ) ;
row [ "NUMERIC_PRECISION" ] = schemaRow [ SchemaTableColumn . NumericPrecision ] ;
row [ "NUMERIC_SCALE" ] = schemaRow [ SchemaTableColumn . NumericScale ] ;
row [ "TABLE_NAME" ] = rdTables . GetString ( 2 ) ;
row [ "COLUMN_NAME" ] = schemaRow [ SchemaTableColumn . ColumnName ] ;
row [ "TABLE_CATALOG" ] = strCatalog ;
row [ "ORDINAL_POSITION" ] = schemaRow [ SchemaTableColumn . ColumnOrdinal ] ;
row [ "COLUMN_HASDEFAULT" ] = ( schemaRow [ SchemaTableOptionalColumn . DefaultValue ] ! = DBNull . Value ) ;
row [ "COLUMN_DEFAULT" ] = schemaRow [ SchemaTableOptionalColumn . DefaultValue ] ;
row [ "IS_NULLABLE" ] = schemaRow [ SchemaTableColumn . AllowDBNull ] ;
row [ "DATA_TYPE" ] = schemaRow [ "DataTypeName" ] . ToString ( ) . ToLower ( CultureInfo . InvariantCulture ) ;
row [ "EDM_TYPE" ] = SqliteConvert . DbTypeToTypeName ( ( DbType ) schemaRow [ SchemaTableColumn . ProviderType ] ) . ToString ( ) . ToLower ( CultureInfo . InvariantCulture ) ;
row [ "CHARACTER_MAXIMUM_LENGTH" ] = schemaRow [ SchemaTableColumn . ColumnSize ] ;
row [ "TABLE_SCHEMA" ] = schemaRow [ SchemaTableColumn . BaseSchemaName ] ;
row [ "PRIMARY_KEY" ] = schemaRow [ SchemaTableColumn . IsKey ] ;
row [ "AUTOINCREMENT" ] = schemaRow [ SchemaTableOptionalColumn . IsAutoIncrement ] ;
row [ "COLLATION_NAME" ] = schemaRow [ "CollationType" ] ;
row [ "UNIQUE" ] = schemaRow [ SchemaTableColumn . IsUnique ] ;
tbl . Rows . Add ( row ) ;
}
}
}
}
catch ( SqliteException )
{
}
}
}
}
tbl . AcceptChanges ( ) ;
tbl . EndLoadData ( ) ;
return tbl ;
}
/// <summary>
/// Returns index information for the given database and catalog
/// </summary>
/// <param name="strCatalog">The catalog (attached database) to query, can be null</param>
/// <param name="strIndex">The name of the index to retrieve information for, can be null</param>
/// <param name="strTable">The table to retrieve index information for, can be null</param>
/// <returns>DataTable</returns>
private DataTable Schema_Indexes ( string strCatalog , string strTable , string strIndex )
{
DataTable tbl = new DataTable ( "Indexes" ) ;
DataRow row ;
List < int > primaryKeys = new List < int > ( ) ;
bool maybeRowId ;
tbl . Locale = CultureInfo . InvariantCulture ;
tbl . Columns . Add ( "TABLE_CATALOG" , typeof ( string ) ) ;
tbl . Columns . Add ( "TABLE_SCHEMA" , typeof ( string ) ) ;
tbl . Columns . Add ( "TABLE_NAME" , typeof ( string ) ) ;
tbl . Columns . Add ( "INDEX_CATALOG" , typeof ( string ) ) ;
tbl . Columns . Add ( "INDEX_SCHEMA" , typeof ( string ) ) ;
tbl . Columns . Add ( "INDEX_NAME" , typeof ( string ) ) ;
tbl . Columns . Add ( "PRIMARY_KEY" , typeof ( bool ) ) ;
tbl . Columns . Add ( "UNIQUE" , typeof ( bool ) ) ;
tbl . Columns . Add ( "CLUSTERED" , typeof ( bool ) ) ;
tbl . Columns . Add ( "TYPE" , typeof ( int ) ) ;
tbl . Columns . Add ( "FILL_FACTOR" , typeof ( int ) ) ;
tbl . Columns . Add ( "INITIAL_SIZE" , typeof ( int ) ) ;
tbl . Columns . Add ( "NULLS" , typeof ( int ) ) ;
tbl . Columns . Add ( "SORT_BOOKMARKS" , typeof ( bool ) ) ;
tbl . Columns . Add ( "AUTO_UPDATE" , typeof ( bool ) ) ;
tbl . Columns . Add ( "NULL_COLLATION" , typeof ( int ) ) ;
tbl . Columns . Add ( "ORDINAL_POSITION" , typeof ( int ) ) ;
tbl . Columns . Add ( "COLUMN_NAME" , typeof ( string ) ) ;
tbl . Columns . Add ( "COLUMN_GUID" , typeof ( Guid ) ) ;
tbl . Columns . Add ( "COLUMN_PROPID" , typeof ( long ) ) ;
tbl . Columns . Add ( "COLLATION" , typeof ( short ) ) ;
tbl . Columns . Add ( "CARDINALITY" , typeof ( Decimal ) ) ;
tbl . Columns . Add ( "PAGES" , typeof ( int ) ) ;
tbl . Columns . Add ( "FILTER_CONDITION" , typeof ( string ) ) ;
tbl . Columns . Add ( "INTEGRATED" , typeof ( bool ) ) ;
tbl . Columns . Add ( "INDEX_DEFINITION" , typeof ( string ) ) ;
tbl . BeginLoadData ( ) ;
if ( String . IsNullOrEmpty ( strCatalog ) ) strCatalog = "main" ;
string master = ( String . Compare ( strCatalog , "temp" , true , CultureInfo . InvariantCulture ) = = 0 ) ? _tempmasterdb : _masterdb ;
using ( SqliteCommand cmdTables = new SqliteCommand ( String . Format ( CultureInfo . InvariantCulture , "SELECT * FROM [{0}].[{1}] WHERE [type] LIKE 'table'" , strCatalog , master ) , this ) )
using ( SqliteDataReader rdTables = cmdTables . ExecuteReader ( ) )
{
while ( rdTables . Read ( ) )
{
maybeRowId = false ;
primaryKeys . Clear ( ) ;
if ( String . IsNullOrEmpty ( strTable ) | | String . Compare ( rdTables . GetString ( 2 ) , strTable , true , CultureInfo . InvariantCulture ) = = 0 )
{
// First, look for any rowid indexes -- which sqlite defines are INTEGER PRIMARY KEY columns.
// Such indexes are not listed in the indexes list but count as indexes just the same.
try
{
using ( SqliteCommand cmdTable = new SqliteCommand ( String . Format ( CultureInfo . InvariantCulture , "PRAGMA [{0}].table_info([{1}])" , strCatalog , rdTables . GetString ( 2 ) ) , this ) )
using ( SqliteDataReader rdTable = cmdTable . ExecuteReader ( ) )
{
while ( rdTable . Read ( ) )
{
if ( rdTable . GetInt32 ( 5 ) = = 1 )
{
primaryKeys . Add ( rdTable . GetInt32 ( 0 ) ) ;
// If the primary key is of type INTEGER, then its a rowid and we need to make a fake index entry for it.
if ( String . Compare ( rdTable . GetString ( 2 ) , "INTEGER" , true , CultureInfo . InvariantCulture ) = = 0 )
maybeRowId = true ;
}
}
}
}
catch ( SqliteException )
{
}
if ( primaryKeys . Count = = 1 & & maybeRowId = = true )
{
row = tbl . NewRow ( ) ;
row [ "TABLE_CATALOG" ] = strCatalog ;
row [ "TABLE_NAME" ] = rdTables . GetString ( 2 ) ;
row [ "INDEX_CATALOG" ] = strCatalog ;
row [ "PRIMARY_KEY" ] = true ;
row [ "INDEX_NAME" ] = String . Format ( CultureInfo . InvariantCulture , "{1}_PK_{0}" , rdTables . GetString ( 2 ) , master ) ;
row [ "UNIQUE" ] = true ;
if ( String . Compare ( ( string ) row [ "INDEX_NAME" ] , strIndex , true , CultureInfo . InvariantCulture ) = = 0
| | strIndex = = null )
{
tbl . Rows . Add ( row ) ;
}
primaryKeys . Clear ( ) ;
}
// Now fetch all the rest of the indexes.
try
{
using ( SqliteCommand cmd = new SqliteCommand ( String . Format ( CultureInfo . InvariantCulture , "PRAGMA [{0}].index_list([{1}])" , strCatalog , rdTables . GetString ( 2 ) ) , this ) )
using ( SqliteDataReader rd = ( SqliteDataReader ) cmd . ExecuteReader ( ) )
{
while ( rd . Read ( ) )
{
if ( String . Compare ( rd . GetString ( 1 ) , strIndex , true , CultureInfo . InvariantCulture ) = = 0
| | strIndex = = null )
{
row = tbl . NewRow ( ) ;
row [ "TABLE_CATALOG" ] = strCatalog ;
row [ "TABLE_NAME" ] = rdTables . GetString ( 2 ) ;
row [ "INDEX_CATALOG" ] = strCatalog ;
row [ "INDEX_NAME" ] = rd . GetString ( 1 ) ;
row [ "UNIQUE" ] = rd . GetBoolean ( 2 ) ;
row [ "PRIMARY_KEY" ] = false ;
// get the index definition
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 ) )
using ( SqliteDataReader rdIndexes = cmdIndexes . ExecuteReader ( ) )
{
while ( rdIndexes . Read ( ) )
{
if ( rdIndexes . IsDBNull ( 4 ) = = false )
2017-08-21 15:34:15 +00:00
{
2014-08-13 10:39:27 +01:00
row [ "INDEX_DEFINITION" ] = rdIndexes . GetString ( 4 ) ;
2017-08-21 15:34:15 +00:00
break ;
}
2014-08-13 10:39:27 +01:00
}
}
// Now for the really hard work. Figure out which index is the primary key index.
// The only way to figure it out is to check if the index was an autoindex and if we have a non-rowid
// primary key, and all the columns in the given index match the primary key columns
if ( primaryKeys . Count > 0 & & rd . GetString ( 1 ) . StartsWith ( "sqlite_autoindex_" + rdTables . GetString ( 2 ) , StringComparison . InvariantCultureIgnoreCase ) = = true )
{
using ( SqliteCommand cmdDetails = new SqliteCommand ( String . Format ( CultureInfo . InvariantCulture , "PRAGMA [{0}].index_info([{1}])" , strCatalog , rd . GetString ( 1 ) ) , this ) )
using ( SqliteDataReader rdDetails = cmdDetails . ExecuteReader ( ) )
{
int nMatches = 0 ;
while ( rdDetails . Read ( ) )
{
if ( primaryKeys . Contains ( rdDetails . GetInt32 ( 1 ) ) = = false )
{
nMatches = 0 ;
break ;
}
nMatches + + ;
}
if ( nMatches = = primaryKeys . Count )
{
row [ "PRIMARY_KEY" ] = true ;
primaryKeys . Clear ( ) ;
}
}
}
tbl . Rows . Add ( row ) ;
}
}
}
}
catch ( SqliteException )
{
}
}
}
}
tbl . AcceptChanges ( ) ;
tbl . EndLoadData ( ) ;
return tbl ;
}
private DataTable Schema_Triggers ( string catalog , string table , string triggerName )
{
DataTable tbl = new DataTable ( "Triggers" ) ;
DataRow row ;
tbl . Locale = CultureInfo . InvariantCulture ;
tbl . Columns . Add ( "TABLE_CATALOG" , typeof ( string ) ) ;
tbl . Columns . Add ( "TABLE_SCHEMA" , typeof ( string ) ) ;
tbl . Columns . Add ( "TABLE_NAME" , typeof ( string ) ) ;
tbl . Columns . Add ( "TRIGGER_NAME" , typeof ( string ) ) ;
tbl . Columns . Add ( "TRIGGER_DEFINITION" , typeof ( string ) ) ;
tbl . BeginLoadData ( ) ;
if ( String . IsNullOrEmpty ( table ) ) table = null ;
if ( String . IsNullOrEmpty ( catalog ) ) catalog = "main" ;
string master = ( String . Compare ( catalog , "temp" , true , CultureInfo . InvariantCulture ) = = 0 ) ? _tempmasterdb : _masterdb ;
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 ) )
using ( SqliteDataReader rd = ( SqliteDataReader ) cmd . ExecuteReader ( ) )
{
while ( rd . Read ( ) )
{
if ( String . Compare ( rd . GetString ( 1 ) , triggerName , true , CultureInfo . InvariantCulture ) = = 0
| | triggerName = = null )
{
if ( table = = null | | String . Compare ( table , rd . GetString ( 2 ) , true , CultureInfo . InvariantCulture ) = = 0 )
{
row = tbl . NewRow ( ) ;
row [ "TABLE_CATALOG" ] = catalog ;
row [ "TABLE_NAME" ] = rd . GetString ( 2 ) ;
row [ "TRIGGER_NAME" ] = rd . GetString ( 1 ) ;
row [ "TRIGGER_DEFINITION" ] = rd . GetString ( 4 ) ;
tbl . Rows . Add ( row ) ;
}
}
}
}
tbl . AcceptChanges ( ) ;
tbl . EndLoadData ( ) ;
return tbl ;
}
/// <summary>
/// Retrieves table schema information for the database and catalog
/// </summary>
/// <param name="strCatalog">The catalog (attached database) to retrieve tables on</param>
/// <param name="strTable">The table to retrieve, can be null</param>
/// <param name="strType">The table type, can be null</param>
/// <returns>DataTable</returns>
private DataTable Schema_Tables ( string strCatalog , string strTable , string strType )
{
DataTable tbl = new DataTable ( "Tables" ) ;
DataRow row ;
string strItem ;
tbl . Locale = CultureInfo . InvariantCulture ;
tbl . Columns . Add ( "TABLE_CATALOG" , typeof ( string ) ) ;
tbl . Columns . Add ( "TABLE_SCHEMA" , typeof ( string ) ) ;
tbl . Columns . Add ( "TABLE_NAME" , typeof ( string ) ) ;
tbl . Columns . Add ( "TABLE_TYPE" , typeof ( string ) ) ;
tbl . Columns . Add ( "TABLE_ID" , typeof ( long ) ) ;
tbl . Columns . Add ( "TABLE_ROOTPAGE" , typeof ( int ) ) ;
tbl . Columns . Add ( "TABLE_DEFINITION" , typeof ( string ) ) ;
tbl . BeginLoadData ( ) ;
if ( String . IsNullOrEmpty ( strCatalog ) ) strCatalog = "main" ;
string master = ( String . Compare ( strCatalog , "temp" , true , CultureInfo . InvariantCulture ) = = 0 ) ? _tempmasterdb : _masterdb ;
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 ) )
using ( SqliteDataReader rd = ( SqliteDataReader ) cmd . ExecuteReader ( ) )
{
while ( rd . Read ( ) )
{
strItem = rd . GetString ( 0 ) ;
if ( String . Compare ( rd . GetString ( 2 ) , 0 , "SQLITE_" , 0 , 7 , true , CultureInfo . InvariantCulture ) = = 0 )
strItem = "SYSTEM_TABLE" ;
if ( String . Compare ( strType , strItem , true , CultureInfo . InvariantCulture ) = = 0
| | strType = = null )
{
if ( String . Compare ( rd . GetString ( 2 ) , strTable , true , CultureInfo . InvariantCulture ) = = 0
| | strTable = = null )
{
row = tbl . NewRow ( ) ;
row [ "TABLE_CATALOG" ] = strCatalog ;
row [ "TABLE_NAME" ] = rd . GetString ( 2 ) ;
row [ "TABLE_TYPE" ] = strItem ;
row [ "TABLE_ID" ] = rd . GetInt64 ( 5 ) ;
row [ "TABLE_ROOTPAGE" ] = rd . GetInt32 ( 3 ) ;
row [ "TABLE_DEFINITION" ] = rd . GetString ( 4 ) ;
tbl . Rows . Add ( row ) ;
}
}
}
}
tbl . AcceptChanges ( ) ;
tbl . EndLoadData ( ) ;
return tbl ;
}
/// <summary>
/// Retrieves view schema information for the database
/// </summary>
/// <param name="strCatalog">The catalog (attached database) to retrieve views on</param>
/// <param name="strView">The view name, can be null</param>
/// <returns>DataTable</returns>
private DataTable Schema_Views ( string strCatalog , string strView )
{
DataTable tbl = new DataTable ( "Views" ) ;
DataRow row ;
string strItem ;
int nPos ;
tbl . Locale = CultureInfo . InvariantCulture ;
tbl . Columns . Add ( "TABLE_CATALOG" , typeof ( string ) ) ;
tbl . Columns . Add ( "TABLE_SCHEMA" , typeof ( string ) ) ;
tbl . Columns . Add ( "TABLE_NAME" , typeof ( string ) ) ;
tbl . Columns . Add ( "VIEW_DEFINITION" , typeof ( string ) ) ;
tbl . Columns . Add ( "CHECK_OPTION" , typeof ( bool ) ) ;
tbl . Columns . Add ( "IS_UPDATABLE" , typeof ( bool ) ) ;
tbl . Columns . Add ( "DESCRIPTION" , typeof ( string ) ) ;
tbl . Columns . Add ( "DATE_CREATED" , typeof ( DateTime ) ) ;
tbl . Columns . Add ( "DATE_MODIFIED" , typeof ( DateTime ) ) ;
tbl . BeginLoadData ( ) ;
if ( String . IsNullOrEmpty ( strCatalog ) ) strCatalog = "main" ;
string master = ( String . Compare ( strCatalog , "temp" , true , CultureInfo . InvariantCulture ) = = 0 ) ? _tempmasterdb : _masterdb ;
using ( SqliteCommand cmd = new SqliteCommand ( String . Format ( CultureInfo . InvariantCulture , "SELECT * FROM [{0}].[{1}] WHERE [type] LIKE 'view'" , strCatalog , master ) , this ) )
using ( SqliteDataReader rd = ( SqliteDataReader ) cmd . ExecuteReader ( ) )
{
while ( rd . Read ( ) )
{
if ( String . Compare ( rd . GetString ( 1 ) , strView , true , CultureInfo . InvariantCulture ) = = 0
| | String . IsNullOrEmpty ( strView ) )
{
strItem = rd . GetString ( 4 ) . Replace ( '\r' , ' ' ) . Replace ( '\n' , ' ' ) . Replace ( '\t' , ' ' ) ;
nPos = CultureInfo . InvariantCulture . CompareInfo . IndexOf ( strItem , " AS " , CompareOptions . IgnoreCase ) ;
if ( nPos > - 1 )
{
strItem = strItem . Substring ( nPos + 4 ) . Trim ( ) ;
row = tbl . NewRow ( ) ;
row [ "TABLE_CATALOG" ] = strCatalog ;
row [ "TABLE_NAME" ] = rd . GetString ( 2 ) ;
row [ "IS_UPDATABLE" ] = false ;
row [ "VIEW_DEFINITION" ] = strItem ;
tbl . Rows . Add ( row ) ;
}
}
}
}
tbl . AcceptChanges ( ) ;
tbl . EndLoadData ( ) ;
return tbl ;
}
/// <summary>
/// Retrieves catalog (attached databases) schema information for the database
/// </summary>
/// <param name="strCatalog">The catalog to retrieve, can be null</param>
/// <returns>DataTable</returns>
private DataTable Schema_Catalogs ( string strCatalog )
{
DataTable tbl = new DataTable ( "Catalogs" ) ;
DataRow row ;
tbl . Locale = CultureInfo . InvariantCulture ;
tbl . Columns . Add ( "CATALOG_NAME" , typeof ( string ) ) ;
tbl . Columns . Add ( "DESCRIPTION" , typeof ( string ) ) ;
tbl . Columns . Add ( "ID" , typeof ( long ) ) ;
tbl . BeginLoadData ( ) ;
using ( SqliteCommand cmd = new SqliteCommand ( "PRAGMA database_list" , this ) )
using ( SqliteDataReader rd = ( SqliteDataReader ) cmd . ExecuteReader ( ) )
{
while ( rd . Read ( ) )
{
if ( String . Compare ( rd . GetString ( 1 ) , strCatalog , true , CultureInfo . InvariantCulture ) = = 0
| | strCatalog = = null )
{
row = tbl . NewRow ( ) ;
row [ "CATALOG_NAME" ] = rd . GetString ( 1 ) ;
row [ "DESCRIPTION" ] = rd . GetString ( 2 ) ;
row [ "ID" ] = rd . GetInt64 ( 0 ) ;
tbl . Rows . Add ( row ) ;
}
}
}
tbl . AcceptChanges ( ) ;
tbl . EndLoadData ( ) ;
return tbl ;
}
private DataTable Schema_DataTypes ( )
{
DataTable tbl = new DataTable ( "DataTypes" ) ;
tbl . Locale = CultureInfo . InvariantCulture ;
tbl . Columns . Add ( "TypeName" , typeof ( String ) ) ;
tbl . Columns . Add ( "ProviderDbType" , typeof ( int ) ) ;
tbl . Columns . Add ( "ColumnSize" , typeof ( long ) ) ;
tbl . Columns . Add ( "CreateFormat" , typeof ( String ) ) ;
tbl . Columns . Add ( "CreateParameters" , typeof ( String ) ) ;
tbl . Columns . Add ( "DataType" , typeof ( String ) ) ;
tbl . Columns . Add ( "IsAutoIncrementable" , typeof ( bool ) ) ;
tbl . Columns . Add ( "IsBestMatch" , typeof ( bool ) ) ;
tbl . Columns . Add ( "IsCaseSensitive" , typeof ( bool ) ) ;
tbl . Columns . Add ( "IsFixedLength" , typeof ( bool ) ) ;
tbl . Columns . Add ( "IsFixedPrecisionScale" , typeof ( bool ) ) ;
tbl . Columns . Add ( "IsLong" , typeof ( bool ) ) ;
tbl . Columns . Add ( "IsNullable" , typeof ( bool ) ) ;
tbl . Columns . Add ( "IsSearchable" , typeof ( bool ) ) ;
tbl . Columns . Add ( "IsSearchableWithLike" , typeof ( bool ) ) ;
tbl . Columns . Add ( "IsLiteralSupported" , typeof ( bool ) ) ;
tbl . Columns . Add ( "LiteralPrefix" , typeof ( String ) ) ;
tbl . Columns . Add ( "LiteralSuffix" , typeof ( String ) ) ;
tbl . Columns . Add ( "IsUnsigned" , typeof ( bool ) ) ;
tbl . Columns . Add ( "MaximumScale" , typeof ( short ) ) ;
tbl . Columns . Add ( "MinimumScale" , typeof ( short ) ) ;
tbl . Columns . Add ( "IsConcurrencyType" , typeof ( bool ) ) ;
tbl . BeginLoadData ( ) ;
StringReader reader = new StringReader ( SR . DataTypes ) ;
tbl . ReadXml ( reader ) ;
reader . Close ( ) ;
tbl . AcceptChanges ( ) ;
tbl . EndLoadData ( ) ;
return tbl ;
}
/// <summary>
/// Returns the base column information for indexes in a database
/// </summary>
/// <param name="strCatalog">The catalog to retrieve indexes for (can be null)</param>
/// <param name="strTable">The table to restrict index information by (can be null)</param>
/// <param name="strIndex">The index to restrict index information by (can be null)</param>
/// <param name="strColumn">The source column to restrict index information by (can be null)</param>
/// <returns>A DataTable containing the results</returns>
private DataTable Schema_IndexColumns ( string strCatalog , string strTable , string strIndex , string strColumn )
{
DataTable tbl = new DataTable ( "IndexColumns" ) ;
DataRow row ;
List < KeyValuePair < int , string > > primaryKeys = new List < KeyValuePair < int , string > > ( ) ;
bool maybeRowId ;
tbl . Locale = CultureInfo . InvariantCulture ;
tbl . Columns . Add ( "CONSTRAINT_CATALOG" , typeof ( string ) ) ;
tbl . Columns . Add ( "CONSTRAINT_SCHEMA" , typeof ( string ) ) ;
tbl . Columns . Add ( "CONSTRAINT_NAME" , typeof ( string ) ) ;
tbl . Columns . Add ( "TABLE_CATALOG" , typeof ( string ) ) ;
tbl . Columns . Add ( "TABLE_SCHEMA" , typeof ( string ) ) ;
tbl . Columns . Add ( "TABLE_NAME" , typeof ( string ) ) ;
tbl . Columns . Add ( "COLUMN_NAME" , typeof ( string ) ) ;
tbl . Columns . Add ( "ORDINAL_POSITION" , typeof ( int ) ) ;
tbl . Columns . Add ( "INDEX_NAME" , typeof ( string ) ) ;
tbl . Columns . Add ( "COLLATION_NAME" , typeof ( string ) ) ;
tbl . Columns . Add ( "SORT_MODE" , typeof ( string ) ) ;
tbl . Columns . Add ( "CONFLICT_OPTION" , typeof ( int ) ) ;
if ( String . IsNullOrEmpty ( strCatalog ) ) strCatalog = "main" ;
string master = ( String . Compare ( strCatalog , "temp" , true , CultureInfo . InvariantCulture ) = = 0 ) ? _tempmasterdb : _masterdb ;
tbl . BeginLoadData ( ) ;
using ( SqliteCommand cmdTables = new SqliteCommand ( String . Format ( CultureInfo . InvariantCulture , "SELECT * FROM [{0}].[{1}] WHERE [type] LIKE 'table'" , strCatalog , master ) , this ) )
using ( SqliteDataReader rdTables = cmdTables . ExecuteReader ( ) )
{
while ( rdTables . Read ( ) )
{
maybeRowId = false ;
primaryKeys . Clear ( ) ;
if ( String . IsNullOrEmpty ( strTable ) | | String . Compare ( rdTables . GetString ( 2 ) , strTable , true , CultureInfo . InvariantCulture ) = = 0 )
{
try
{
using ( SqliteCommand cmdTable = new SqliteCommand ( String . Format ( CultureInfo . InvariantCulture , "PRAGMA [{0}].table_info([{1}])" , strCatalog , rdTables . GetString ( 2 ) ) , this ) )
using ( SqliteDataReader rdTable = cmdTable . ExecuteReader ( ) )
{
while ( rdTable . Read ( ) )
{
if ( rdTable . GetInt32 ( 5 ) = = 1 ) // is a primary key
{
primaryKeys . Add ( new KeyValuePair < int , string > ( rdTable . GetInt32 ( 0 ) , rdTable . GetString ( 1 ) ) ) ;
// Is an integer -- could be a rowid if no other primary keys exist in the table
if ( String . Compare ( rdTable . GetString ( 2 ) , "INTEGER" , true , CultureInfo . InvariantCulture ) = = 0 )
maybeRowId = true ;
}
}
}
}
catch ( SqliteException )
{
}
// This is a rowid row
if ( primaryKeys . Count = = 1 & & maybeRowId = = true )
{
row = tbl . NewRow ( ) ;
row [ "CONSTRAINT_CATALOG" ] = strCatalog ;
row [ "CONSTRAINT_NAME" ] = String . Format ( CultureInfo . InvariantCulture , "{1}_PK_{0}" , rdTables . GetString ( 2 ) , master ) ;
row [ "TABLE_CATALOG" ] = strCatalog ;
row [ "TABLE_NAME" ] = rdTables . GetString ( 2 ) ;
row [ "COLUMN_NAME" ] = primaryKeys [ 0 ] . Value ;
row [ "INDEX_NAME" ] = row [ "CONSTRAINT_NAME" ] ;
row [ "ORDINAL_POSITION" ] = 0 ; // primaryKeys[0].Key;
row [ "COLLATION_NAME" ] = "BINARY" ;
row [ "SORT_MODE" ] = "ASC" ;
row [ "CONFLICT_OPTION" ] = 2 ;
if ( String . IsNullOrEmpty ( strIndex ) | | String . Compare ( strIndex , ( string ) row [ "INDEX_NAME" ] , true , CultureInfo . InvariantCulture ) = = 0 )
tbl . Rows . Add ( row ) ;
}
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 ) )
using ( SqliteDataReader rdIndexes = cmdIndexes . ExecuteReader ( ) )
{
while ( rdIndexes . Read ( ) )
{
int ordinal = 0 ;
if ( String . IsNullOrEmpty ( strIndex ) | | String . Compare ( strIndex , rdIndexes . GetString ( 1 ) , true , CultureInfo . InvariantCulture ) = = 0 )
{
try
{
using ( SqliteCommand cmdIndex = new SqliteCommand ( String . Format ( CultureInfo . InvariantCulture , "PRAGMA [{0}].index_info([{1}])" , strCatalog , rdIndexes . GetString ( 1 ) ) , this ) )
using ( SqliteDataReader rdIndex = cmdIndex . ExecuteReader ( ) )
{
while ( rdIndex . Read ( ) )
{
row = tbl . NewRow ( ) ;
row [ "CONSTRAINT_CATALOG" ] = strCatalog ;
row [ "CONSTRAINT_NAME" ] = rdIndexes . GetString ( 1 ) ;
row [ "TABLE_CATALOG" ] = strCatalog ;
row [ "TABLE_NAME" ] = rdIndexes . GetString ( 2 ) ;
row [ "COLUMN_NAME" ] = rdIndex . GetString ( 2 ) ;
row [ "INDEX_NAME" ] = rdIndexes . GetString ( 1 ) ;
row [ "ORDINAL_POSITION" ] = ordinal ; // rdIndex.GetInt32(1);
string collationSequence ;
int sortMode ;
int onError ;
_sql . GetIndexColumnExtendedInfo ( strCatalog , rdIndexes . GetString ( 1 ) , rdIndex . GetString ( 2 ) , out sortMode , out onError , out collationSequence ) ;
if ( String . IsNullOrEmpty ( collationSequence ) = = false )
row [ "COLLATION_NAME" ] = collationSequence ;
row [ "SORT_MODE" ] = ( sortMode = = 0 ) ? "ASC" : "DESC" ;
row [ "CONFLICT_OPTION" ] = onError ;
ordinal + + ;
if ( String . IsNullOrEmpty ( strColumn ) | | String . Compare ( strColumn , row [ "COLUMN_NAME" ] . ToString ( ) , true , CultureInfo . InvariantCulture ) = = 0 )
tbl . Rows . Add ( row ) ;
}
}
}
catch ( SqliteException )
{
}
}
}
}
}
}
}
tbl . EndLoadData ( ) ;
tbl . AcceptChanges ( ) ;
return tbl ;
}
/// <summary>
/// Returns detailed column information for a specified view
/// </summary>
/// <param name="strCatalog">The catalog to retrieve columns for (can be null)</param>
/// <param name="strView">The view to restrict column information by (can be null)</param>
/// <param name="strColumn">The source column to restrict column information by (can be null)</param>
/// <returns>A DataTable containing the results</returns>
private DataTable Schema_ViewColumns ( string strCatalog , string strView , string strColumn )
{
DataTable tbl = new DataTable ( "ViewColumns" ) ;
DataRow row ;
string strSql ;
int n ;
DataRow schemaRow ;
DataRow viewRow ;
tbl . Locale = CultureInfo . InvariantCulture ;
tbl . Columns . Add ( "VIEW_CATALOG" , typeof ( string ) ) ;
tbl . Columns . Add ( "VIEW_SCHEMA" , typeof ( string ) ) ;
tbl . Columns . Add ( "VIEW_NAME" , typeof ( string ) ) ;
tbl . Columns . Add ( "VIEW_COLUMN_NAME" , typeof ( String ) ) ;
tbl . Columns . Add ( "TABLE_CATALOG" , typeof ( string ) ) ;
tbl . Columns . Add ( "TABLE_SCHEMA" , typeof ( string ) ) ;
tbl . Columns . Add ( "TABLE_NAME" , typeof ( string ) ) ;
tbl . Columns . Add ( "COLUMN_NAME" , typeof ( string ) ) ;
tbl . Columns . Add ( "ORDINAL_POSITION" , typeof ( int ) ) ;
tbl . Columns . Add ( "COLUMN_HASDEFAULT" , typeof ( bool ) ) ;
tbl . Columns . Add ( "COLUMN_DEFAULT" , typeof ( string ) ) ;
tbl . Columns . Add ( "COLUMN_FLAGS" , typeof ( long ) ) ;
tbl . Columns . Add ( "IS_NULLABLE" , typeof ( bool ) ) ;
tbl . Columns . Add ( "DATA_TYPE" , typeof ( string ) ) ;
tbl . Columns . Add ( "CHARACTER_MAXIMUM_LENGTH" , typeof ( int ) ) ;
tbl . Columns . Add ( "NUMERIC_PRECISION" , typeof ( int ) ) ;
tbl . Columns . Add ( "NUMERIC_SCALE" , typeof ( int ) ) ;
tbl . Columns . Add ( "DATETIME_PRECISION" , typeof ( long ) ) ;
tbl . Columns . Add ( "CHARACTER_SET_CATALOG" , typeof ( string ) ) ;
tbl . Columns . Add ( "CHARACTER_SET_SCHEMA" , typeof ( string ) ) ;
tbl . Columns . Add ( "CHARACTER_SET_NAME" , typeof ( string ) ) ;
tbl . Columns . Add ( "COLLATION_CATALOG" , typeof ( string ) ) ;
tbl . Columns . Add ( "COLLATION_SCHEMA" , typeof ( string ) ) ;
tbl . Columns . Add ( "COLLATION_NAME" , typeof ( string ) ) ;
tbl . Columns . Add ( "PRIMARY_KEY" , typeof ( bool ) ) ;
tbl . Columns . Add ( "EDM_TYPE" , typeof ( string ) ) ;
tbl . Columns . Add ( "AUTOINCREMENT" , typeof ( bool ) ) ;
tbl . Columns . Add ( "UNIQUE" , typeof ( bool ) ) ;
if ( String . IsNullOrEmpty ( strCatalog ) ) strCatalog = "main" ;
string master = ( String . Compare ( strCatalog , "temp" , true , CultureInfo . InvariantCulture ) = = 0 ) ? _tempmasterdb : _masterdb ;
tbl . BeginLoadData ( ) ;
using ( SqliteCommand cmdViews = new SqliteCommand ( String . Format ( CultureInfo . InvariantCulture , "SELECT * FROM [{0}].[{1}] WHERE [type] LIKE 'view'" , strCatalog , master ) , this ) )
using ( SqliteDataReader rdViews = cmdViews . ExecuteReader ( ) )
{
while ( rdViews . Read ( ) )
{
if ( String . IsNullOrEmpty ( strView ) | | String . Compare ( strView , rdViews . GetString ( 2 ) , true , CultureInfo . InvariantCulture ) = = 0 )
{
using ( SqliteCommand cmdViewSelect = new SqliteCommand ( String . Format ( CultureInfo . InvariantCulture , "SELECT * FROM [{0}].[{1}]" , strCatalog , rdViews . GetString ( 2 ) ) , this ) )
{
strSql = rdViews . GetString ( 4 ) . Replace ( '\r' , ' ' ) . Replace ( '\n' , ' ' ) . Replace ( '\t' , ' ' ) ;
n = CultureInfo . InvariantCulture . CompareInfo . IndexOf ( strSql , " AS " , CompareOptions . IgnoreCase ) ;
if ( n < 0 )
continue ;
strSql = strSql . Substring ( n + 4 ) ;
using ( SqliteCommand cmd = new SqliteCommand ( strSql , this ) )
using ( SqliteDataReader rdViewSelect = cmdViewSelect . ExecuteReader ( CommandBehavior . SchemaOnly ) )
using ( SqliteDataReader rd = ( SqliteDataReader ) cmd . ExecuteReader ( CommandBehavior . SchemaOnly ) )
using ( DataTable tblSchemaView = rdViewSelect . GetSchemaTable ( false , false ) )
using ( DataTable tblSchema = rd . GetSchemaTable ( false , false ) )
{
for ( n = 0 ; n < tblSchema . Rows . Count ; n + + )
{
viewRow = tblSchemaView . Rows [ n ] ;
schemaRow = tblSchema . Rows [ n ] ;
if ( String . Compare ( viewRow [ SchemaTableColumn . ColumnName ] . ToString ( ) , strColumn , true , CultureInfo . InvariantCulture ) = = 0
| | strColumn = = null )
{
row = tbl . NewRow ( ) ;
row [ "VIEW_CATALOG" ] = strCatalog ;
row [ "VIEW_NAME" ] = rdViews . GetString ( 2 ) ;
row [ "TABLE_CATALOG" ] = strCatalog ;
row [ "TABLE_SCHEMA" ] = schemaRow [ SchemaTableColumn . BaseSchemaName ] ;
row [ "TABLE_NAME" ] = schemaRow [ SchemaTableColumn . BaseTableName ] ;
row [ "COLUMN_NAME" ] = schemaRow [ SchemaTableColumn . BaseColumnName ] ;
row [ "VIEW_COLUMN_NAME" ] = viewRow [ SchemaTableColumn . ColumnName ] ;
row [ "COLUMN_HASDEFAULT" ] = ( viewRow [ SchemaTableOptionalColumn . DefaultValue ] ! = DBNull . Value ) ;
row [ "COLUMN_DEFAULT" ] = viewRow [ SchemaTableOptionalColumn . DefaultValue ] ;
row [ "ORDINAL_POSITION" ] = viewRow [ SchemaTableColumn . ColumnOrdinal ] ;
row [ "IS_NULLABLE" ] = viewRow [ SchemaTableColumn . AllowDBNull ] ;
row [ "DATA_TYPE" ] = viewRow [ "DataTypeName" ] ; // SqliteConvert.DbTypeToType((DbType)viewRow[SchemaTableColumn.ProviderType]).ToString();
row [ "EDM_TYPE" ] = SqliteConvert . DbTypeToTypeName ( ( DbType ) viewRow [ SchemaTableColumn . ProviderType ] ) . ToString ( ) . ToLower ( CultureInfo . InvariantCulture ) ;
row [ "CHARACTER_MAXIMUM_LENGTH" ] = viewRow [ SchemaTableColumn . ColumnSize ] ;
row [ "TABLE_SCHEMA" ] = viewRow [ SchemaTableColumn . BaseSchemaName ] ;
row [ "PRIMARY_KEY" ] = viewRow [ SchemaTableColumn . IsKey ] ;
row [ "AUTOINCREMENT" ] = viewRow [ SchemaTableOptionalColumn . IsAutoIncrement ] ;
row [ "COLLATION_NAME" ] = viewRow [ "CollationType" ] ;
row [ "UNIQUE" ] = viewRow [ SchemaTableColumn . IsUnique ] ;
tbl . Rows . Add ( row ) ;
}
}
}
}
}
}
}
tbl . EndLoadData ( ) ;
tbl . AcceptChanges ( ) ;
return tbl ;
}
/// <summary>
/// Retrieves foreign key information from the specified set of filters
/// </summary>
/// <param name="strCatalog">An optional catalog to restrict results on</param>
/// <param name="strTable">An optional table to restrict results on</param>
/// <param name="strKeyName">An optional foreign key name to restrict results on</param>
/// <returns>A DataTable with the results of the query</returns>
private DataTable Schema_ForeignKeys ( string strCatalog , string strTable , string strKeyName )
{
DataTable tbl = new DataTable ( "ForeignKeys" ) ;
DataRow row ;
tbl . Locale = CultureInfo . InvariantCulture ;
tbl . Columns . Add ( "CONSTRAINT_CATALOG" , typeof ( string ) ) ;
tbl . Columns . Add ( "CONSTRAINT_SCHEMA" , typeof ( string ) ) ;
tbl . Columns . Add ( "CONSTRAINT_NAME" , typeof ( string ) ) ;
tbl . Columns . Add ( "TABLE_CATALOG" , typeof ( string ) ) ;
tbl . Columns . Add ( "TABLE_SCHEMA" , typeof ( string ) ) ;
tbl . Columns . Add ( "TABLE_NAME" , typeof ( string ) ) ;
tbl . Columns . Add ( "CONSTRAINT_TYPE" , typeof ( string ) ) ;
tbl . Columns . Add ( "IS_DEFERRABLE" , typeof ( bool ) ) ;
tbl . Columns . Add ( "INITIALLY_DEFERRED" , typeof ( bool ) ) ;
tbl . Columns . Add ( "FKEY_FROM_COLUMN" , typeof ( string ) ) ;
tbl . Columns . Add ( "FKEY_FROM_ORDINAL_POSITION" , typeof ( int ) ) ;
tbl . Columns . Add ( "FKEY_TO_CATALOG" , typeof ( string ) ) ;
tbl . Columns . Add ( "FKEY_TO_SCHEMA" , typeof ( string ) ) ;
tbl . Columns . Add ( "FKEY_TO_TABLE" , typeof ( string ) ) ;
tbl . Columns . Add ( "FKEY_TO_COLUMN" , typeof ( string ) ) ;
if ( String . IsNullOrEmpty ( strCatalog ) ) strCatalog = "main" ;
string master = ( String . Compare ( strCatalog , "temp" , true , CultureInfo . InvariantCulture ) = = 0 ) ? _tempmasterdb : _masterdb ;
tbl . BeginLoadData ( ) ;
using ( SqliteCommand cmdTables = new SqliteCommand ( String . Format ( CultureInfo . InvariantCulture , "SELECT * FROM [{0}].[{1}] WHERE [type] LIKE 'table'" , strCatalog , master ) , this ) )
using ( SqliteDataReader rdTables = cmdTables . ExecuteReader ( ) )
{
while ( rdTables . Read ( ) )
{
if ( String . IsNullOrEmpty ( strTable ) | | String . Compare ( strTable , rdTables . GetString ( 2 ) , true , CultureInfo . InvariantCulture ) = = 0 )
{
try
{
using ( SqliteCommandBuilder builder = new SqliteCommandBuilder ( ) )
//using (SqliteCommand cmdTable = new SqliteCommand(String.Format(CultureInfo.InvariantCulture, "SELECT * FROM [{0}].[{1}]", strCatalog, rdTables.GetString(2)), this))
//using (SqliteDataReader rdTable = cmdTable.ExecuteReader(CommandBehavior.SchemaOnly))
using ( SqliteCommand cmdKey = new SqliteCommand ( String . Format ( CultureInfo . InvariantCulture , "PRAGMA [{0}].foreign_key_list([{1}])" , strCatalog , rdTables . GetString ( 2 ) ) , this ) )
using ( SqliteDataReader rdKey = cmdKey . ExecuteReader ( ) )
{
while ( rdKey . Read ( ) )
{
row = tbl . NewRow ( ) ;
row [ "CONSTRAINT_CATALOG" ] = strCatalog ;
row [ "CONSTRAINT_NAME" ] = String . Format ( CultureInfo . InvariantCulture , "FK_{0}_{1}" , rdTables [ 2 ] , rdKey . GetInt32 ( 0 ) ) ;
row [ "TABLE_CATALOG" ] = strCatalog ;
row [ "TABLE_NAME" ] = builder . UnquoteIdentifier ( rdTables . GetString ( 2 ) ) ;
row [ "CONSTRAINT_TYPE" ] = "FOREIGN KEY" ;
row [ "IS_DEFERRABLE" ] = false ;
row [ "INITIALLY_DEFERRED" ] = false ;
row [ "FKEY_FROM_COLUMN" ] = builder . UnquoteIdentifier ( rdKey [ 3 ] . ToString ( ) ) ;
row [ "FKEY_TO_CATALOG" ] = strCatalog ;
row [ "FKEY_TO_TABLE" ] = builder . UnquoteIdentifier ( rdKey [ 2 ] . ToString ( ) ) ;
row [ "FKEY_TO_COLUMN" ] = builder . UnquoteIdentifier ( rdKey [ 4 ] . ToString ( ) ) ;
row [ "FKEY_FROM_ORDINAL_POSITION" ] = rdKey [ 1 ] ;
if ( String . IsNullOrEmpty ( strKeyName ) | | String . Compare ( strKeyName , row [ "CONSTRAINT_NAME" ] . ToString ( ) , true , CultureInfo . InvariantCulture ) = = 0 )
tbl . Rows . Add ( row ) ;
}
}
}
catch ( SqliteException )
{
}
}
}
}
tbl . EndLoadData ( ) ;
tbl . AcceptChanges ( ) ;
return tbl ;
}
/// <summary>
/// This event is raised whenever SQLite makes an update/delete/insert into the database on
/// this connection. It only applies to the given connection.
/// </summary>
public event SQLiteUpdateEventHandler Update
{
add
{
if ( _updateHandler = = null )
{
_updateCallback = new SQLiteUpdateCallback ( UpdateCallback ) ;
if ( _sql ! = null ) _sql . SetUpdateHook ( _updateCallback ) ;
}
_updateHandler + = value ;
}
remove
{
_updateHandler - = value ;
if ( _updateHandler = = null )
{
if ( _sql ! = null ) _sql . SetUpdateHook ( null ) ;
_updateCallback = null ;
}
}
}
private void UpdateCallback ( IntPtr puser , int type , IntPtr database , IntPtr table , Int64 rowid )
{
_updateHandler ( this , new UpdateEventArgs (
SQLiteBase . UTF8ToString ( database , - 1 ) ,
SQLiteBase . UTF8ToString ( table , - 1 ) ,
( UpdateEventType ) type ,
rowid ) ) ;
}
/// <summary>
/// This event is raised whenever SQLite is committing a transaction.
/// Return non-zero to trigger a rollback
/// </summary>
public event SQLiteCommitHandler Commit
{
add
{
if ( _commitHandler = = null )
{
_commitCallback = new SQLiteCommitCallback ( CommitCallback ) ;
if ( _sql ! = null ) _sql . SetCommitHook ( _commitCallback ) ;
}
_commitHandler + = value ;
}
remove
{
_commitHandler - = value ;
if ( _commitHandler = = null )
{
if ( _sql ! = null ) _sql . SetCommitHook ( null ) ;
_commitCallback = null ;
}
}
}
/// <summary>
/// This event is raised whenever SQLite is committing a transaction.
/// Return non-zero to trigger a rollback
/// </summary>
public event EventHandler RollBack
{
add
{
if ( _rollbackHandler = = null )
{
_rollbackCallback = new SQLiteRollbackCallback ( RollbackCallback ) ;
if ( _sql ! = null ) _sql . SetRollbackHook ( _rollbackCallback ) ;
}
_rollbackHandler + = value ;
}
remove
{
_rollbackHandler - = value ;
if ( _rollbackHandler = = null )
{
if ( _sql ! = null ) _sql . SetRollbackHook ( null ) ;
_rollbackCallback = null ;
}
}
}
private int CommitCallback ( IntPtr parg )
{
CommitEventArgs e = new CommitEventArgs ( ) ;
_commitHandler ( this , e ) ;
return ( e . AbortTransaction = = true ) ? 1 : 0 ;
}
private void RollbackCallback ( IntPtr parg )
{
_rollbackHandler ( this , EventArgs . Empty ) ;
}
// http://www.sqlite.org/c3ref/config.html
public static void SetConfig ( SQLiteConfig config )
{
int n = UnsafeNativeMethods . sqlite3_config ( config ) ;
if ( n > 0 ) throw new SqliteException ( n , null ) ;
}
}
/// <summary>
/// The I/O file cache flushing behavior for the connection
/// </summary>
public enum SynchronizationModes
{
/// <summary>
/// Normal file flushing at critical sections of the code
/// </summary>
Normal = 0 ,
/// <summary>
/// Full file flushing after every write operation
/// </summary>
Full = 1 ,
/// <summary>
/// Use the default operating system's file flushing, SQLite does not explicitly flush the file buffers after writing
/// </summary>
Off = 2 ,
}
#if ! PLATFORM_COMPACTFRAMEWORK
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
#endif
internal delegate void SQLiteUpdateCallback ( IntPtr puser , int type , IntPtr database , IntPtr table , Int64 rowid ) ;
#if ! PLATFORM_COMPACTFRAMEWORK
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
#endif
internal delegate int SQLiteCommitCallback ( IntPtr puser ) ;
#if ! PLATFORM_COMPACTFRAMEWORK
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
#endif
internal delegate void SQLiteRollbackCallback ( IntPtr puser ) ;
/// <summary>
/// Raised when a transaction is about to be committed. To roll back a transaction, set the
/// rollbackTrans boolean value to true.
/// </summary>
/// <param name="sender">The connection committing the transaction</param>
/// <param name="e">Event arguments on the transaction</param>
public delegate void SQLiteCommitHandler ( object sender , CommitEventArgs e ) ;
/// <summary>
/// Raised when data is inserted, updated and deleted on a given connection
/// </summary>
/// <param name="sender">The connection committing the transaction</param>
/// <param name="e">The event parameters which triggered the event</param>
public delegate void SQLiteUpdateEventHandler ( object sender , UpdateEventArgs e ) ;
/// <summary>
/// Whenever an update event is triggered on a connection, this enum will indicate
/// exactly what type of operation is being performed.
/// </summary>
public enum UpdateEventType
{
/// <summary>
/// A row is being deleted from the given database and table
/// </summary>
Delete = 9 ,
/// <summary>
/// A row is being inserted into the table.
/// </summary>
Insert = 18 ,
/// <summary>
/// A row is being updated in the table.
/// </summary>
Update = 23 ,
}
/// <summary>
/// Passed during an Update callback, these event arguments detail the type of update operation being performed
/// on the given connection.
/// </summary>
public class UpdateEventArgs : EventArgs
{
/// <summary>
/// The name of the database being updated (usually "main" but can be any attached or temporary database)
/// </summary>
public readonly string Database ;
/// <summary>
/// The name of the table being updated
/// </summary>
public readonly string Table ;
/// <summary>
/// The type of update being performed (insert/update/delete)
/// </summary>
public readonly UpdateEventType Event ;
/// <summary>
/// The RowId affected by this update.
/// </summary>
public readonly Int64 RowId ;
internal UpdateEventArgs ( string database , string table , UpdateEventType eventType , Int64 rowid )
{
Database = database ;
Table = table ;
Event = eventType ;
RowId = rowid ;
}
}
/// <summary>
/// Event arguments raised when a transaction is being committed
/// </summary>
public class CommitEventArgs : EventArgs
{
internal CommitEventArgs ( )
{
}
/// <summary>
/// Set to true to abort the transaction and trigger a rollback
/// </summary>
public bool AbortTransaction ;
}
}