536cd135cc
Former-commit-id: 5624ac747d633e885131e8349322922b6a59baaa
2431 lines
98 KiB
C#
2431 lines
98 KiB
C#
/********************************************************
|
|
* 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)
|
|
{
|
|
row["INDEX_DEFINITION"] = rdIndexes.GetString(4);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
|
|
}
|