Index: Setup/verify.lst
==================================================================
--- Setup/verify.lst
+++ Setup/verify.lst
@@ -405,10 +405,11 @@
testlinq/testlinq.2008.csproj
testlinq/testlinq.2010.csproj
testlinq/testlinq.2012.csproj
Tests/
Tests/all.eagle
+ Tests/authorizer.eagle
Tests/backup.eagle
Tests/basic.eagle
Tests/common.eagle
Tests/empty.eagle
Tests/installer.eagle
Index: System.Data.SQLite/SQLite3.cs
==================================================================
--- System.Data.SQLite/SQLite3.cs
+++ System.Data.SQLite/SQLite3.cs
@@ -2043,10 +2043,15 @@
{
SQLiteErrorCode n = UnsafeNativeMethods.sqlite3_rekey(_sql, newPasswordBytes, (newPasswordBytes == null) ? 0 : newPasswordBytes.Length);
if (n != SQLiteErrorCode.Ok) throw new SQLiteException(n, GetLastError());
}
#endif
+
+ internal override void SetAuthorizerHook(SQLiteAuthorizerCallback func)
+ {
+ UnsafeNativeMethods.sqlite3_set_authorizer(_sql, func, IntPtr.Zero);
+ }
internal override void SetUpdateHook(SQLiteUpdateCallback func)
{
UnsafeNativeMethods.sqlite3_update_hook(_sql, func, IntPtr.Zero);
}
Index: System.Data.SQLite/SQLiteBase.cs
==================================================================
--- System.Data.SQLite/SQLiteBase.cs
+++ System.Data.SQLite/SQLiteBase.cs
@@ -361,10 +361,11 @@
#if INTEROP_CODEC
internal abstract void SetPassword(byte[] passwordBytes);
internal abstract void ChangePassword(byte[] newPasswordBytes);
#endif
+ internal abstract void SetAuthorizerHook(SQLiteAuthorizerCallback func);
internal abstract void SetUpdateHook(SQLiteUpdateCallback func);
internal abstract void SetCommitHook(SQLiteCommitCallback func);
internal abstract void SetTraceCallback(SQLiteTraceCallback func);
internal abstract void SetRollbackHook(SQLiteRollbackCallback func);
internal abstract SQLiteErrorCode SetLogCallback(SQLiteLogCallback func);
Index: System.Data.SQLite/SQLiteConnection.cs
==================================================================
--- System.Data.SQLite/SQLiteConnection.cs
+++ System.Data.SQLite/SQLiteConnection.cs
@@ -476,15 +476,17 @@
internal bool _binaryGuid;
internal long _version;
+ private event SQLiteAuthorizerEventHandler _authorizerHandler;
private event SQLiteUpdateEventHandler _updateHandler;
private event SQLiteCommitHandler _commitHandler;
private event SQLiteTraceEventHandler _traceHandler;
private event EventHandler _rollbackHandler;
+ private SQLiteAuthorizerCallback _authorizerCallback;
private SQLiteUpdateCallback _updateCallback;
private SQLiteCommitCallback _commitCallback;
private SQLiteTraceCallback _traceCallback;
private SQLiteRollbackCallback _rollbackCallback;
#endregion
@@ -2222,10 +2224,13 @@
cmd.ExecuteNonQuery();
}
}
}
+ if (_authorizerHandler != null)
+ _sql.SetAuthorizerHook(_authorizerCallback);
+
if (_commitHandler != null)
_sql.SetCommitHook(_commitCallback);
if (_updateHandler != null)
_sql.SetUpdateHook(_updateCallback);
@@ -3935,10 +3940,44 @@
tbl.EndLoadData();
tbl.AcceptChanges();
return tbl;
}
+
+ ///
+ /// This event is raised whenever SQLite encounters an action covered by the
+ /// authorizer during query preparation. Changing the value of the
+ /// property will determine if
+ /// the specific action will be allowed, ignored, or denied. For the entire
+ /// duration of the event, the associated connection and statement objects
+ /// must not be modified, either directly or indirectly, by the called code.
+ ///
+ public event SQLiteAuthorizerEventHandler Authorize
+ {
+ add
+ {
+ CheckDisposed();
+
+ if (_authorizerHandler == null)
+ {
+ _authorizerCallback = new SQLiteAuthorizerCallback(AuthorizerCallback);
+ if (_sql != null) _sql.SetAuthorizerHook(_authorizerCallback);
+ }
+ _authorizerHandler += value;
+ }
+ remove
+ {
+ CheckDisposed();
+
+ _authorizerHandler -= value;
+ if (_authorizerHandler == null)
+ {
+ if (_sql != null) _sql.SetAuthorizerHook(null);
+ _authorizerCallback = null;
+ }
+ }
+ }
///
/// This event is raised whenever SQLite makes an update/delete/insert into the database on
/// this connection. It only applies to the given connection.
///
@@ -3965,10 +4004,29 @@
if (_sql != null) _sql.SetUpdateHook(null);
_updateCallback = null;
}
}
}
+
+ private SQLiteAuthorizerReturnCode AuthorizerCallback(
+ IntPtr pUserData,
+ SQLiteAuthorizerActionCode actionCode,
+ IntPtr pArgument1,
+ IntPtr pArgument2,
+ IntPtr pDatabase,
+ IntPtr pAuthContext)
+ {
+ AuthorizerEventArgs eventArgs = new AuthorizerEventArgs(pUserData, actionCode,
+ SQLiteBase.UTF8ToString(pArgument1, -1), SQLiteBase.UTF8ToString(pArgument2, -1),
+ SQLiteBase.UTF8ToString(pDatabase, -1), SQLiteBase.UTF8ToString(pAuthContext, -1),
+ SQLiteAuthorizerReturnCode.Ok);
+
+ if (_authorizerHandler != null)
+ _authorizerHandler(this, eventArgs);
+
+ return eventArgs.ReturnCode;
+ }
private void UpdateCallback(IntPtr puser, int type, IntPtr database, IntPtr table, Int64 rowid)
{
_updateHandler(this, new UpdateEventArgs(
SQLiteBase.UTF8ToString(database, -1),
@@ -4106,10 +4164,22 @@
Off = 2,
}
#if !PLATFORM_COMPACTFRAMEWORK
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+#endif
+ internal delegate SQLiteAuthorizerReturnCode SQLiteAuthorizerCallback(
+ IntPtr pUserData,
+ SQLiteAuthorizerActionCode actionCode,
+ IntPtr pArgument1,
+ IntPtr pArgument2,
+ IntPtr pDatabase,
+ IntPtr pAuthContext
+ );
+
+#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)]
@@ -4124,10 +4194,19 @@
#if !PLATFORM_COMPACTFRAMEWORK
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
#endif
internal delegate void SQLiteRollbackCallback(IntPtr puser);
+ ///
+ /// Raised when authorization is required to perform an action contained
+ /// within a SQL query.
+ ///
+ /// The connection performing the action.
+ /// A that contains the
+ /// event data.
+ public delegate void SQLiteAuthorizerEventHandler(object sender, AuthorizerEventArgs e);
+
///
/// Raised when a transaction is about to be committed. To roll back a transaction, set the
/// rollbackTrans boolean value to true.
///
/// The connection committing the transaction
@@ -4192,10 +4271,127 @@
int remainingPages,
int totalPages,
bool retry
);
#endregion
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+
+ ///
+ /// The data associated with a call into the authorizer.
+ ///
+ public class AuthorizerEventArgs : EventArgs
+ {
+ ///
+ /// The user-defined native data associated with this event. Currently,
+ /// this will always contain the value of .
+ ///
+ public readonly IntPtr UserData;
+
+ ///
+ /// The action code responsible for the current call into the authorizer.
+ ///
+ public readonly SQLiteAuthorizerActionCode ActionCode;
+
+ ///
+ /// The first string argument for the current call into the authorizer.
+ /// The exact value will vary based on the action code, see the
+ /// enumeration for possible
+ /// values.
+ ///
+ public readonly string Argument1;
+
+ ///
+ /// The second string argument for the current call into the authorizer.
+ /// The exact value will vary based on the action code, see the
+ /// enumeration for possible
+ /// values.
+ ///
+ public readonly string Argument2;
+
+ ///
+ /// The database name for the current call into the authorizer, if
+ /// applicable.
+ ///
+ public readonly string Database;
+
+ ///
+ /// The name of the inner-most trigger or view that is responsible for
+ /// the access attempt or a null value if this access attempt is directly
+ /// from top-level SQL code.
+ ///
+ public readonly string Context;
+
+ ///
+ /// The return code for the current call into the authorizer.
+ ///
+ public SQLiteAuthorizerReturnCode ReturnCode;
+
+ ///////////////////////////////////////////////////////////////////////////////////////////
+
+ ///
+ /// Constructs an instance of this class with default property values.
+ ///
+ private AuthorizerEventArgs()
+ {
+ this.UserData = IntPtr.Zero;
+ this.ActionCode = SQLiteAuthorizerActionCode.None;
+ this.Argument1 = null;
+ this.Argument2 = null;
+ this.Database = null;
+ this.Context = null;
+ this.ReturnCode = SQLiteAuthorizerReturnCode.Ok;
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////////////////
+
+ ///
+ /// Constructs an instance of this class with specific property values.
+ ///
+ ///
+ /// The user-defined native data associated with this event.
+ ///
+ ///
+ /// The authorizer action code.
+ ///
+ ///
+ /// The first authorizer argument.
+ ///
+ ///
+ /// The second authorizer argument.
+ ///
+ ///
+ /// The database name, if applicable.
+ ///
+ ///
+ /// The name of the inner-most trigger or view that is responsible for
+ /// the access attempt or a null value if this access attempt is directly
+ /// from top-level SQL code.
+ ///
+ ///
+ /// The authorizer return code.
+ ///
+ internal AuthorizerEventArgs(
+ IntPtr pUserData,
+ SQLiteAuthorizerActionCode actionCode,
+ string argument1,
+ string argument2,
+ string database,
+ string context,
+ SQLiteAuthorizerReturnCode returnCode
+ )
+ : this()
+ {
+ this.UserData = pUserData;
+ this.ActionCode = actionCode;
+ this.Argument1 = argument1;
+ this.Argument2 = argument2;
+ this.Database = database;
+ this.Context = context;
+ this.ReturnCode = returnCode;
+ }
+ }
///////////////////////////////////////////////////////////////////////////////////////////////
///
/// Whenever an update event is triggered on a connection, this enum will indicate
Index: System.Data.SQLite/SQLiteConvert.cs
==================================================================
--- System.Data.SQLite/SQLiteConvert.cs
+++ System.Data.SQLite/SQLiteConvert.cs
@@ -1500,10 +1500,247 @@
/// Use the default command execution type. Using this value is the same
/// as using the value.
///
Default = NonQuery /* TODO: Good default? */
}
+
+ ///
+ /// The action code responsible for the current call into the authorizer.
+ ///
+ public enum SQLiteAuthorizerActionCode
+ {
+ ///
+ /// No action is being performed. This value should not be used from
+ /// external code.
+ ///
+ None = -1,
+
+ ///
+ /// No longer used.
+ ///
+ Copy = 0,
+
+ ///
+ /// An index will be created. The action-specific arguments are the
+ /// index name and the table name.
+ ///
+ ///
+ CreateIndex = 1,
+
+ ///
+ /// A table will be created. The action-specific arguments are the
+ /// table name and a null value.
+ ///
+ CreateTable = 2,
+
+ ///
+ /// A temporary index will be created. The action-specific arguments
+ /// are the index name and the table name.
+ ///
+ CreateTempIndex = 3,
+
+ ///
+ /// A temporary table will be created. The action-specific arguments
+ /// are the table name and a null value.
+ ///
+ CreateTempTable = 4,
+
+ ///
+ /// A temporary trigger will be created. The action-specific arguments
+ /// are the trigger name and the table name.
+ ///
+ CreateTempTrigger = 5,
+
+ ///
+ /// A temporary view will be created. The action-specific arguments are
+ /// the view name and a null value.
+ ///
+ CreateTempView = 6,
+
+ ///
+ /// A trigger will be created. The action-specific arguments are the
+ /// trigger name and the table name.
+ ///
+ CreateTrigger = 7,
+
+ ///
+ /// A view will be created. The action-specific arguments are the view
+ /// name and a null value.
+ ///
+ CreateView = 8,
+
+ ///
+ /// A DELETE statement will be executed. The action-specific arguments
+ /// are the table name and a null value.
+ ///
+ Delete = 9,
+
+ ///
+ /// An index will be dropped. The action-specific arguments are the
+ /// index name and the table name.
+ ///
+ DropIndex = 10,
+
+ ///
+ /// A table will be dropped. The action-specific arguments are the tables
+ /// name and a null value.
+ ///
+ DropTable = 11,
+
+ ///
+ /// A temporary index will be dropped. The action-specific arguments are
+ /// the index name and the table name.
+ ///
+ DropTempIndex = 12,
+
+ ///
+ /// A temporary table will be dropped. The action-specific arguments are
+ /// the table name and a null value.
+ ///
+ DropTempTable = 13,
+
+ ///
+ /// A temporary trigger will be dropped. The action-specific arguments
+ /// are the trigger name and the table name.
+ ///
+ DropTempTrigger = 14,
+
+ ///
+ /// A temporary view will be dropped. The action-specific arguments are
+ /// the view name and a null value.
+ ///
+ DropTempView = 15,
+
+ ///
+ /// A trigger will be dropped. The action-specific arguments are the
+ /// trigger name and the table name.
+ ///
+ DropTrigger = 16,
+
+ ///
+ /// A view will be dropped. The action-specific arguments are the view
+ /// name and a null value.
+ ///
+ DropView = 17,
+
+ ///
+ /// An INSERT statement will be executed. The action-specific arguments
+ /// are the table name and a null value.
+ ///
+ Insert = 18,
+
+ ///
+ /// A PRAGMA statement will be executed. The action-specific arguments
+ /// are the name of the PRAGMA and the new value or a null value.
+ ///
+ Pragma = 19,
+
+ ///
+ /// A table column will be read. The action-specific arguments are the
+ /// table name and the column name.
+ ///
+ Read = 20,
+
+ ///
+ /// A SELECT statement will be executed. The action-specific arguments
+ /// are both null values.
+ ///
+ Select = 21,
+
+ ///
+ /// A transaction will be started, committed, or rolled back. The
+ /// action-specific arguments are the name of the operation (BEGIN,
+ /// COMMIT, or ROLLBACK) and a null value.
+ ///
+ Transaction = 22,
+
+ ///
+ /// An UPDATE statement will be executed. The action-specific arguments
+ /// are the table name and the column name.
+ ///
+ Update = 23,
+
+ ///
+ /// A database will be attached to the connection. The action-specific
+ /// arguments are the database file name and a null value.
+ ///
+ Attach = 24,
+
+ ///
+ /// A database will be detached from the connection. The action-specific
+ /// arguments are the database name and a null value.
+ ///
+ Detach = 25,
+
+ ///
+ /// The schema of a table will be altered. The action-specific arguments
+ /// are the database name and the table name.
+ ///
+ AlterTable = 26,
+
+ ///
+ /// An index will be deleted and then recreated. The action-specific
+ /// arguments are the index name and a null value.
+ ///
+ Reindex = 27,
+
+ ///
+ /// A table will be analyzed to gathers statistics about it. The
+ /// action-specific arguments are the table name and a null value.
+ ///
+ Analyze = 28,
+
+ ///
+ /// A virtual table will be created. The action-specific arguments are
+ /// the table name and the module name.
+ ///
+ CreateVtable = 29,
+
+ ///
+ /// A virtual table will be dropped. The action-specific arguments are
+ /// the table name and the module name.
+ ///
+ DropVtable = 30,
+
+ ///
+ /// A SQL function will be called. The action-specific arguments are a
+ /// null value and the function name.
+ ///
+ Function = 31,
+
+ ///
+ /// A savepoint will be created, released, or rolled back. The
+ /// action-specific arguments are the name of the operation (BEGIN,
+ /// RELEASE, or ROLLBACK) and the savepoint name.
+ ///
+ Savepoint = 32
+ }
+
+ ///
+ /// The return code for the current call into the authorizer.
+ ///
+ public enum SQLiteAuthorizerReturnCode
+ {
+ ///
+ /// The action will be allowed.
+ ///
+ Ok = 0,
+
+ ///
+ /// The overall action will be disallowed and an error message will be
+ /// returned from the query preparation method.
+ ///
+ Deny = 1,
+
+ ///
+ /// The specific action will be disallowed; however, the overall action
+ /// will continue. The exact effects of this return code vary depending
+ /// on the specific action, please refer to the SQLite core library
+ /// documentation for futher details.
+ ///
+ Ignore = 2
+ }
///
/// Class used internally to determine the datatype of a column in a resultset
///
internal sealed class SQLiteType
Index: System.Data.SQLite/UnsafeNativeMethods.cs
==================================================================
--- System.Data.SQLite/UnsafeNativeMethods.cs
+++ System.Data.SQLite/UnsafeNativeMethods.cs
@@ -1467,10 +1467,17 @@
[DllImport(SQLITE_DLL)]
#endif
internal static extern SQLiteErrorCode sqlite3_rekey(IntPtr db, byte[] key, int keylen);
#endif
+#if !PLATFORM_COMPACTFRAMEWORK
+ [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)]
+#else
+ [DllImport(SQLITE_DLL)]
+#endif
+ internal static extern IntPtr sqlite3_set_authorizer(IntPtr db, SQLiteAuthorizerCallback func, IntPtr pvUser);
+
#if !PLATFORM_COMPACTFRAMEWORK
[DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)]
#else
[DllImport(SQLITE_DLL)]
#endif
ADDED Tests/authorizer.eagle
Index: Tests/authorizer.eagle
==================================================================
--- /dev/null
+++ Tests/authorizer.eagle
@@ -0,0 +1,160 @@
+###############################################################################
+#
+# authorizer.eagle --
+#
+# Written by Joe Mistachkin.
+# Released to the public domain, use at your own risk!
+#
+###############################################################################
+
+package require Eagle
+package require Eagle.Library
+package require Eagle.Test
+
+runTestPrologue
+
+###############################################################################
+
+package require System.Data.SQLite.Test
+runSQLiteTestPrologue
+
+###############################################################################
+
+runTest {test authorizer-1.1 {SQLiteConnection Authorize event} -setup {
+ proc onAuthorize { sender e } {
+ #
+ # NOTE: Filter out the "noise" by allowing all standard
+ # events on the "sqlite_*" tables.
+ #
+ set noiseActionCodes [list \
+ CreateTable CreateIndex Read Insert Update Delete]
+
+ if {[$e ActionCode] in $noiseActionCodes && \
+ [string match "sqlite_*" [$e Argument1]]} then {
+ return
+ }
+
+ lappend ::data [list \
+ [$e UserData] [$e ActionCode] [$e Argument1] \
+ [$e Argument2] [$e Database] [$e Context]]
+
+ if {[$e ActionCode] eq "CreateTable" && \
+ [$e Argument1] eq "tDeny"} then {
+ $e ReturnCode Deny
+ }
+ }
+
+ setupDb [set fileName authorizer-1.1.db]
+} -body {
+ set connection [getDbConnection]
+
+ set callback onAuthorize
+ object invoke $connection add_Authorize $callback
+
+ set results [list]
+
+ set sql [list \
+ CreateTable {CREATE TABLE t1(x);} \
+ CreateIndex {CREATE INDEX i1 ON t1(x);} \
+ CreateTrigger {CREATE TRIGGER tr1 BEFORE INSERT ON t1
+ BEGIN
+ SELECT RAISE(IGNORE);
+ END;} \
+ CreateView {CREATE VIEW v1 AS SELECT * FROM t1;} \
+ CreateTempTable {CREATE TEMPORARY TABLE t2(x);} \
+ CreateTempIndex {CREATE INDEX i2 ON t2(x);} \
+ CreateTempTrigger {CREATE TEMPORARY TRIGGER tr2 BEFORE INSERT ON t2
+ BEGIN
+ SELECT RAISE(IGNORE);
+ END;} \
+ CreateTempView {CREATE TEMPORARY VIEW v2 AS SELECT * FROM t2;} \
+ Pragma {PRAGMA journal_mode=WAL;} \
+ Function {SELECT julianday('now');} \
+ Read {SELECT x FROM t1;} \
+ Select {SELECT * FROM t1;} \
+ Insert {INSERT INTO t1(x) VALUES(1);} \
+ Update {UPDATE t1 SET x = x - 1;} \
+ Delete {DELETE FROM t1;} \
+ AlterTable {ALTER TABLE t1 ADD COLUMN y;} \
+ Reindex {REINDEX t1;} \
+ Analyze {ANALYZE t1;} \
+ DropTempView {DROP VIEW v2;} \
+ DropTempTrigger {DROP TRIGGER tr2;} \
+ DropTempIndex {DROP INDEX i2;} \
+ DropTempTable {DROP TABLE t2;} \
+ DropView {DROP VIEW v1;} \
+ DropTrigger {DROP TRIGGER tr1;} \
+ DropIndex {DROP INDEX i1;} \
+ DropTable {DROP TABLE t1;} \
+ Transaction {BEGIN; SELECT 0; COMMIT;} \
+ Savepoint {SAVEPOINT s1; RELEASE SAVEPOINT s1;} \
+ Attach {ATTACH DATABASE ':memory:' AS d1;} \
+ Detach {DETACH DATABASE d1;} \
+ CreateVtable {CREATE VIRTUAL TABLE t3 USING fts4(x TEXT);} \
+ DropVtable {DROP TABLE t3;} \
+ CreateTable {CREATE TABLE tDeny(x);}]
+
+ foreach {name value} $sql {
+ set data [list]; set code [catch {sql execute $db $value} result]
+ set result [lindex [split [string map [list \r\n \n] $result] \n] 0]
+ lappend results [list $name $data $code $result]
+ }
+ lappend results [isTableInDb tDeny]
+
+ set results
+} -cleanup {
+ catch {object invoke $connection remove_Authorize $callback}
+ catch {object removecallback $callback}
+
+ cleanupDb $fileName
+
+ freeDbConnection
+
+ unset -nocomplain data result code value name sql results callback \
+ connection db fileName
+
+ rename onAuthorize ""
+} -constraints \
+{eagle monoBug28 command.sql compile.DATA SQLite System.Data.SQLite} -result \
+{{CreateTable {{0 CreateTable t1 {} main {}}} 0 0} {CreateIndex {{0 CreateIndex\
+i1 t1 main {}} {0 Reindex i1 {} main {}}} 0 0} {CreateTrigger {{0 CreateTrigger\
+tr1 t1 main {}}} 0 0} {CreateView {{0 CreateView v1 {} main {}}} 0 0}\
+{CreateTempTable {{0 CreateTempTable t2 {} temp {}}} 0 0} {CreateTempIndex {{0\
+CreateTempIndex i2 t2 temp {}} {0 Reindex i2 {} temp {}}} 0 0}\
+{CreateTempTrigger {{0 CreateTempTrigger tr2 t2 temp {}}} 0 0} {CreateTempView\
+{{0 CreateTempView v2 {} temp {}}} 0 0} {Pragma {{0 Pragma journal_mode WAL {}\
+{}}} 0 0} {Function {{0 Select {} {} {} {}} {0 Function {} julianday {} {}}} 0\
+0} {Read {{0 Select {} {} {} {}} {0 Read t1 x main {}}} 0 0} {Select {{0 Select\
+{} {} {} {}} {0 Read t1 x main {}}} 0 0} {Insert {{0 Insert t1 {} main {}} {0\
+Select {} {} {} tr1}} 0 0} {Update {{0 Read t1 x main {}} {0 Update t1 x main\
+{}}} 0 0} {Delete {{0 Delete t1 {} main {}}} 0 0} {AlterTable {{0 AlterTable\
+main t1 {} {}} {0 Function {} substr {} {}} {0 Function {} substr {} {}}} 0 0}\
+{Reindex {{0 Reindex i1 {} main {}}} 0 0} {Analyze {{0 Analyze t1 {} main {}}\
+{0 Select {} {} {} {}} {0 Select {} {} {} {}} {0 Function {} count {} {}} {0\
+Select {} {} {} {}}} 0 0} {DropTempView {{0 DropTempView v2 {} temp {}} {0\
+Delete v2 {} temp {}}} 0 0} {DropTempTrigger {{0 DropTempTrigger tr2 t2 temp\
+{}}} 0 0} {DropTempIndex {{0 DropTempIndex i2 t2 temp {}}} 0 0} {DropTempTable\
+{{0 DropTempTable t2 {} temp {}} {0 Delete t2 {} temp {}}} 0 0} {DropView {{0\
+DropView v1 {} main {}} {0 Delete v1 {} main {}}} 0 0} {DropTrigger {{0\
+DropTrigger tr1 t1 main {}}} 0 0} {DropIndex {{0 DropIndex i1 t1 main {}}} 0 0}\
+{DropTable {{0 DropTable t1 {} main {}} {0 Delete t1 {} main {}}} 0 0}\
+{Transaction {{0 Transaction BEGIN {} {} {}} {0 Select {} {} {} {}} {0\
+Transaction COMMIT {} {} {}}} 0 0} {Savepoint {{0 Savepoint BEGIN s1 {} {}} {0\
+Savepoint RELEASE s1 {} {}}} 0 0} {Attach {{0 Attach :memory: {} {} {}}} 0 0}\
+{Detach {{0 Detach d1 {} {} {}}} 0 0} {CreateVtable {{0 CreateVtable t3 fts4\
+main {}} {0 CreateTable t3_content {} main {}} {0 CreateTable t3_segments {}\
+main {}} {0 CreateTable t3_segdir {} main {}} {0 CreateTable t3_docsize {} main\
+{}} {0 CreateTable t3_stat {} main {}} {0 Pragma page_size {} main {}}} 0 0}\
+{DropVtable {{0 DropVtable t3 fts4 main {}} {0 Delete t3 {} main {}} {0\
+DropTable t3_content {} main {}} {0 Delete t3_content {} main {}} {0 DropTable\
+t3_segments {} main {}} {0 Delete t3_segments {} main {}} {0 DropTable\
+t3_segdir {} main {}} {0 Delete t3_segdir {} main {}} {0 DropTable t3_docsize\
+{} main {}} {0 Delete t3_docsize {} main {}} {0 DropTable t3_stat {} main {}}\
+{0 Delete t3_stat {} main {}}} 0 0} {CreateTable {{0 CreateTable tDeny {} main\
+{}}} 1 {System.Data.SQLite.SQLiteException (0x80004005): authorization denied}}\
+False}}
+
+###############################################################################
+
+runSQLiteTestEpilogue
+runTestEpilogue
Index: Tests/common.eagle
==================================================================
--- Tests/common.eagle
+++ Tests/common.eagle
@@ -997,10 +997,20 @@
#
return [expr {[sql execute -execute scalar $db \
"SELECT COUNT(*) FROM sqlite_master WHERE type = 'table' AND name = ?;" \
[list param1 String $name]] > 0}]
}
+
+ proc trimSql { sql } {
+ set result [string map [list \r\n " " \r " " \n " "] $sql]
+
+ while {[string first " " $result] != -1} {
+ set result [string map [list " " " "] $result]
+ }
+
+ return $result
+ }
proc executeSql { sql {execute none} {fileName ""} } {
if {[string length $fileName] == 0} then {set fileName :memory:}
setupDb $fileName "" "" "" "" "" false false false false memDb