Index: System.Data.SQLite/SQLiteBase.cs ================================================================== --- System.Data.SQLite/SQLiteBase.cs +++ System.Data.SQLite/SQLiteBase.cs @@ -591,14 +591,14 @@ /////////////////////////////////////////////////////////////////////////////////////////////// private static string[] _errorMessages = { /* SQLITE_OK */ "not an error", - /* SQLITE_ERROR */ "SQL logic error or missing database", + /* SQLITE_ERROR */ "SQL logic error", /* SQLITE_INTERNAL */ "internal logic error", /* SQLITE_PERM */ "access permission denied", - /* SQLITE_ABORT */ "callback requested query abort", + /* SQLITE_ABORT */ "query aborted", /* SQLITE_BUSY */ "database is locked", /* SQLITE_LOCKED */ "database table is locked", /* SQLITE_NOMEM */ "out of memory", /* SQLITE_READONLY */ "attempt to write a readonly database", /* SQLITE_INTERRUPT */ "interrupted", @@ -611,16 +611,16 @@ /* SQLITE_EMPTY */ "table contains no data", /* SQLITE_SCHEMA */ "database schema has changed", /* SQLITE_TOOBIG */ "string or blob too big", /* SQLITE_CONSTRAINT */ "constraint failed", /* SQLITE_MISMATCH */ "datatype mismatch", - /* SQLITE_MISUSE */ "library routine called out of sequence", + /* SQLITE_MISUSE */ "bad parameter or other API misuse", /* SQLITE_NOLFS */ "large file support is disabled", /* SQLITE_AUTH */ "authorization denied", /* SQLITE_FORMAT */ "auxiliary database format error", - /* SQLITE_RANGE */ "bind or column index out of range", - /* SQLITE_NOTADB */ "file is encrypted or is not a database", + /* SQLITE_RANGE */ "column index out of range", + /* SQLITE_NOTADB */ "file is not a database", /* SQLITE_NOTICE */ "notification message", /* SQLITE_WARNING */ "warning message" }; /////////////////////////////////////////////////////////////////////////////////////////////// @@ -631,14 +631,24 @@ /// /// The SQLite return code. /// The error message or null if it cannot be found. protected static string FallbackGetErrorString(SQLiteErrorCode rc) { + switch (rc) + { + case SQLiteErrorCode.Abort_Rollback: + return "abort due to ROLLBACK"; + case SQLiteErrorCode.Row: + return "another row available"; + case SQLiteErrorCode.Done: + return "no more rows available"; + } + if (_errorMessages == null) return null; - int index = (int)rc; + int index = (int)(rc & SQLiteErrorCode.NonExtendedMask); if ((index < 0) || (index >= _errorMessages.Length)) index = (int)SQLiteErrorCode.Error; /* Make into generic error. */ return _errorMessages[index]; Index: Tests/basic.eagle ================================================================== --- Tests/basic.eagle +++ Tests/basic.eagle @@ -4755,10 +4755,99 @@ } -constraints [fixConstraints {eagle !System.Data.SQLite.Encryption monoBug28\ command.sql compile.DATA SQLite System.Data.SQLite}] -result {1 {SQL logic\ error -- Cannot use "HexPassword" connection string property: library was not\ built with encryption support, please see "https://www.sqlite.org/see" for more\ information}}} + +############################################################################### + +runTest {test data-1.93 {extended error code messages} -setup { + # + # HACK: Temporarily disable automatic detection (and use) of the + # native sqlite3_errstr() API. + # + object invoke -flags +NonPublic \ + System.Data.SQLite.SQLite3 have_errstr false + + setupDb [set fileName data-1.93.db] +} -body { + set errCodes [list] + + lappend errCodes 256; # SQLITE_OK_LOAD_PERMANENTLY + lappend errCodes 513; # SQLITE_ERROR_RETRY + lappend errCodes 522; # SQLITE_IOERR_SHORT_READ + lappend errCodes 270; # SQLITE_CANTOPEN_NOTEMPDIR + lappend errCodes 539; # SQLITE_NOTICE_RECOVER_ROLLBACK + lappend errCodes 284; # SQLITE_WARNING_AUTOINDEX + + set connection [getDbConnection] + + lappend result [catchAndReturn {$connection EnableExtensions true}] + + lappend result [catchAndReturn {$connection LoadExtension \ + [getCoreExtensionBinaryFileName null] interop_test_extension_init}] + + lappend result [catchAndReturn {$connection SetExtendedResultCodes false}] + + foreach errCode $errCodes { + # + # HACK: Without extended error codes, SQLITE_OK_* cannot be handled via + # the semantics in the System.Data.SQLite.SQLite3.Prepare method; + # it will always assume that more results are available when the + # error code is Ok, thereby looping forever. + # + if {($errCode & 0xFF) == 0} then {continue} + + resetException; catchAndSetException { + sql execute -execute scalar $db \ + "SELECT interopError(?);" [list param1 Int32 $errCode] + } exception + + lappend result [$exception ResultCode] \ + [normalizeExceptionMessage [$exception Message]] + } + + lappend result [catchAndReturn {$connection SetExtendedResultCodes true}] + + foreach errCode $errCodes { + resetException; catchAndSetException { + sql execute -execute scalar $db \ + "SELECT interopError(?);" [list param1 Int32 $errCode] + } exception + + lappend result [$exception ResultCode] \ + [normalizeExceptionMessage [$exception Message]] + } + + set result +} -cleanup { + cleanupDb $fileName + + freeDbConnection + + catch { + # + # HACK: Restore automatic detection (and use) of the native + # sqlite3_errstr() API. + # + object invoke -flags +NonPublic \ + System.Data.SQLite.SQLite3 have_errstr null + } + + unset -nocomplain result exception errCode errCodes + unset -nocomplain connection db fileName +} -constraints {eagle command.object monoBug28\ +compile.DATA defineConstant.System.Data.SQLite.INTEROP_TEST_EXTENSION\ +command.sql SQLite System.Data.SQLite} -result {{0 {}} {0 {}} {0 {}} Error {SQL\ +logic error ==> SQL logic error} IoErr {disk I/O error ==> disk I/O error}\ +CantOpen {unable to open database file ==> unable to open database file} Notice\ +{notification message ==> unknown error} Warning {warning message ==> unknown\ +error} {0 {}} Ok_Load_Permanently {not an error ==> not an error} Error_Retry\ +{SQL logic error ==> SQL logic error} IoErr_Short_Read {disk I/O error ==> disk\ +I/O error} CantOpen_NoTempDir {unable to open database file ==> unable to open\ +database file} Notice_Recover_Rollback {notification message ==> unknown error}\ +Warning_AutoIndex {warning message ==> unknown error}}} ############################################################################### reportSQLiteResources $test_channel Index: lib/System.Data.SQLite/common.eagle ================================================================== --- lib/System.Data.SQLite/common.eagle +++ lib/System.Data.SQLite/common.eagle @@ -2169,10 +2169,40 @@ # return [list $code $::errorCode \ [extractSystemDataSQLiteExceptionMessage $result]] } } + + proc resetException {} { + # + # NOTE: Reset exception associated with this interpreter (to null). + # This (private) property is maintained on a per-thread basis. + # + object invoke -flags +NonPublic Interpreter.GetActive Exception null + return "" + } + + proc catchAndSetException { script {varName ""} } { + # + # NOTE: Evaluate the script provided by our caller in their context. + # + catch {uplevel 1 $script} + + # + # NOTE: Grab the (private) exception property from this interpreter, + # for this thread, and add as an opaque object handle in the + # context of our caller. + # + if {[string length $varName] > 0} then { + upvar 1 $varName exception + } + + set exception [object invoke \ + -alias -flags +NonPublic Interpreter.GetActive Exception] + + return "" + } proc compileCSharpWith { text memory symbols strict resultsVarName errorsVarName fileNames args } { # @@ -2286,10 +2316,15 @@ return [list \ [sql execute -execute scalar $db "PRAGMA page_size;"] \ [sql execute -execute scalar $db "PRAGMA cache_size;"]] } + + proc normalizeExceptionMessage { value } { + if {[string length $value] == 0} then {return $value} + return [string map [list \r\n " ==> "] $value] + } proc extractSystemDataSQLiteExceptionMessage { value } { # # NOTE: If the string conforms to format of the normal exception # error strings, extract and return only the error message