From 135039b1373eb5ae5bb1f36f84e5ebf377d0750c Mon Sep 17 00:00:00 2001 From: Jarred Sumner Date: Fri, 29 Dec 2023 18:37:16 -0800 Subject: [PATCH] Better errors for bun:sqlite (#7906) * Better errors for bun:sqlite * Add `byteOffset` * Add `code` property * Fix error * Update test * Add a couple more tests for errors * Add file with sqlite error codes * Report extra memory from `sqlite3_stmt` * Add polyfills for macOS x64 * Use -1 when unavailable --------- Co-authored-by: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> --- packages/bun-types/sqlite.d.ts | 39 + src/bun.js/bindings/sqlite/JSSQLStatement.cpp | 182 ++++- src/bun.js/bindings/sqlite/lazy_sqlite3.h | 41 + .../bindings/sqlite/sqlite3_error_codes.h | 763 ++++++++++++++++++ src/bun.js/javascript.zig | 2 +- src/js/bun/sqlite.ts | 9 + test/js/bun/sqlite/sqlite.test.js | 32 +- 7 files changed, 1023 insertions(+), 45 deletions(-) create mode 100644 src/bun.js/bindings/sqlite/sqlite3_error_codes.h diff --git a/packages/bun-types/sqlite.d.ts b/packages/bun-types/sqlite.d.ts index 133aae0855..7648df477b 100644 --- a/packages/bun-types/sqlite.d.ts +++ b/packages/bun-types/sqlite.d.ts @@ -809,4 +809,43 @@ declare module "bun:sqlite" { | Record; export default Database; + + /** + * Errors from SQLite have a name `SQLiteError`. + * + * This class does not exist! It is not a class. It is just a type. + * + * To check if an `Error` is an SQLiteError, use `error.name === "SQLiteError"` + */ + export interface SQLiteError extends Error { + readonly name: "SQLiteError"; + + /** + * The SQLite3 extended error code + * + * This corresponds to `sqlite3_extended_errcode`. + * + * @since v1.0.21 + */ + errno: number; + + /** + * The name of the SQLite3 error code + * + * @example + * "SQLITE_CONSTRAINT_UNIQUE" + * + * @since v1.0.21 + */ + code?: string; + + /** + * The UTF-8 byte offset of the sqlite3 query that failed, if known + * + * This corresponds to `sqlite3_error_offset`. + * + * @since v1.0.21 + */ + readonly byteOffset: number; + } } diff --git a/src/bun.js/bindings/sqlite/JSSQLStatement.cpp b/src/bun.js/bindings/sqlite/JSSQLStatement.cpp index ff82d13251..7fd9e6a14d 100644 --- a/src/bun.js/bindings/sqlite/JSSQLStatement.cpp +++ b/src/bun.js/bindings/sqlite/JSSQLStatement.cpp @@ -27,6 +27,9 @@ #include #include "simdutf.h" #include +#include "BunBuiltinNames.h" +#include "sqlite3_error_codes.h" +#include /* ******************************************************************************** */ // Lazy Load SQLite on macOS @@ -40,6 +43,7 @@ // i.e. it shouldn't have any impact on startup time #if LAZY_LOAD_SQLITE #include "lazy_sqlite3.h" + #else static inline int lazyLoadSQLite() { @@ -54,16 +58,31 @@ static inline int lazyLoadSQLite() #define ENABLE_SQLITE_FAST_MALLOC (BENABLE(MALLOC_SIZE) && BENABLE(MALLOC_GOOD_SIZE)) #endif +static std::atomic sqlite_malloc_amount = 0; + static void enableFastMallocForSQLite() { #if ENABLE(SQLITE_FAST_MALLOC) - // int returnCode = sqlite3_config(SQLITE_CONFIG_LOOKASIDE, 0, 0); - // ASSERT_WITH_MESSAGE(returnCode == SQLITE_OK, "Unable to reduce lookaside buffer size"); + int returnCode = sqlite3_config(SQLITE_CONFIG_LOOKASIDE, 0, 0); + ASSERT_WITH_MESSAGE(returnCode == SQLITE_OK, "Unable to reduce lookaside buffer size"); static sqlite3_mem_methods fastMallocMethods = { - [](int n) { return fastMalloc(n); }, - fastFree, - [](void* p, int n) { return fastRealloc(p, n); }, + [](int n) { + auto* ret = fastMalloc(n); + sqlite_malloc_amount += fastMallocSize(ret); + return ret; + }, + [](void* p) { + sqlite_malloc_amount -= fastMallocSize(p); + return fastFree(p); + }, + [](void* p, int n) { + sqlite_malloc_amount -= fastMallocSize(p); + auto* out = fastRealloc(p, n); + sqlite_malloc_amount += fastMallocSize(out); + + return out; + }, [](void* p) { return static_cast(fastMallocSize(p)); }, [](int n) { return static_cast(fastMallocGoodSize(n)); }, [](void*) { return SQLITE_OK; }, @@ -71,7 +90,7 @@ static void enableFastMallocForSQLite() nullptr }; - int returnCode = sqlite3_config(SQLITE_CONFIG_MALLOC, &fastMallocMethods); + returnCode = sqlite3_config(SQLITE_CONFIG_MALLOC, &fastMallocMethods); ASSERT_WITH_MESSAGE(returnCode == SQLITE_OK, "Unable to replace SQLite malloc"); #endif @@ -116,7 +135,7 @@ static inline JSC::JSValue jsNumberFromSQLite(sqlite3_stmt* stmt, unsigned int i #define DO_REBIND(param) \ if (param.isObject()) { \ - JSC::JSValue reb = castedThis->rebind(lexicalGlobalObject, param, true); \ + JSC::JSValue reb = castedThis->rebind(lexicalGlobalObject, param, true, castedThis->version_db->db); \ if (UNLIKELY(!reb.isNumber())) { \ return JSValue::encode(reb); /* this means an error */ \ } \ @@ -204,6 +223,39 @@ JSC_DECLARE_CUSTOM_GETTER(jsSqlStatementGetColumnNames); JSC_DECLARE_CUSTOM_GETTER(jsSqlStatementGetColumnCount); JSC_DECLARE_CUSTOM_GETTER(jsSqlStatementGetParamCount); +static JSValue createSQLiteError(JSC::JSGlobalObject* globalObject, sqlite3* db) +{ + auto& vm = globalObject->vm(); + int code = sqlite3_extended_errcode(db); + int byteOffset = sqlite3_error_offset(db); + + const char* msg = sqlite3_errmsg(db); + WTF::String str = WTF::String::fromUTF8(msg); + JSC::JSObject* object = JSC::createError(globalObject, str); + auto& builtinNames = WebCore::builtinNames(vm); + object->putDirect(vm, vm.propertyNames->name, jsString(vm, String("SQLiteError"_s)), JSC::PropertyAttribute::DontEnum | 0); + + String codeStr; + + switch (code) { +#define MACRO(SQLITE_DEF) \ + case SQLITE_DEF: { \ + codeStr = #SQLITE_DEF##_s; \ + break; \ + } + FOR_EACH_SQLITE_ERROR(MACRO) + +#undef MACRO + } + if (!codeStr.isEmpty()) + object->putDirect(vm, builtinNames.codePublicName(), jsString(vm, codeStr), PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly | 0); + + object->putDirect(vm, builtinNames.errnoPublicName(), jsNumber(code), PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly | 0); + object->putDirect(vm, vm.propertyNames->byteOffset, jsNumber(byteOffset), PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly | 0); + + return object; +} + class JSSQLStatement : public JSC::JSDestructibleObject { public: using Base = JSC::JSDestructibleObject; @@ -214,10 +266,10 @@ public: return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::ObjectType, StructureFlags), info()); } - static JSSQLStatement* create(JSDOMGlobalObject* globalObject, sqlite3_stmt* stmt, VersionSqlite3* version_db) + static JSSQLStatement* create(JSDOMGlobalObject* globalObject, sqlite3_stmt* stmt, VersionSqlite3* version_db, ssize_t memorySizeChange = 0) { Structure* structure = globalObject->JSSQLStatementStructure(); - JSSQLStatement* ptr = new (NotNull, JSC::allocateCell(globalObject->vm())) JSSQLStatement(structure, *globalObject, stmt, version_db); + JSSQLStatement* ptr = new (NotNull, JSC::allocateCell(globalObject->vm())) JSSQLStatement(structure, *globalObject, stmt, version_db, memorySizeChange); ptr->finishCreation(globalObject->vm()); return ptr; } @@ -236,9 +288,15 @@ public: template void visitAdditionalChildren(Visitor&); template static void visitOutputConstraints(JSCell*, Visitor&); - // static void analyzeHeap(JSCell*, JSC::HeapAnalyzer&); + size_t static estimatedSize(JSCell* cell, VM& vm) + { + auto* thisObject = jsCast(cell); + return Base::estimatedSize(thisObject, vm) + thisObject->extraMemorySize; + } - JSC::JSValue rebind(JSGlobalObject* globalObject, JSC::JSValue values, bool clone); + static void analyzeHeap(JSCell*, JSC::HeapAnalyzer&); + + JSC::JSValue rebind(JSGlobalObject* globalObject, JSC::JSValue values, bool clone, sqlite3* db); bool need_update() { return version_db->version.load() != version; } void update_version() { version = version_db->version.load(); } @@ -252,13 +310,15 @@ public: std::unique_ptr columnNames; mutable JSC::WriteBarrier _prototype; mutable JSC::WriteBarrier _structure; + size_t extraMemorySize = 0; protected: - JSSQLStatement(JSC::Structure* structure, JSDOMGlobalObject& globalObject, sqlite3_stmt* stmt, VersionSqlite3* version_db) + JSSQLStatement(JSC::Structure* structure, JSDOMGlobalObject& globalObject, sqlite3_stmt* stmt, VersionSqlite3* version_db, ssize_t memorySizeChange = 0) : Base(globalObject.vm(), structure) , stmt(stmt) , version_db(version_db) , columnNames(new PropertyNameArray(globalObject.vm(), PropertyNameMode::Strings, PrivateSymbolMode::Exclude)) + , extraMemorySize(memorySizeChange > 0 ? memorySizeChange : 0) { } @@ -330,6 +390,7 @@ const ClassInfo JSSQLStatementPrototype::s_info = { "SQLStatement"_s, &Base::s_i Structure* createJSSQLStatementStructure(JSGlobalObject* globalObject) { Structure* prototypeStructure = JSSQLStatementPrototype::createStructure(globalObject->vm(), globalObject, globalObject->objectPrototype()); + prototypeStructure->setMayBePrototype(true); JSSQLStatementPrototype* prototype = JSSQLStatementPrototype::create(globalObject->vm(), globalObject, prototypeStructure); return JSSQLStatement::createStructure(globalObject->vm(), globalObject, prototype); } @@ -456,14 +517,19 @@ void JSSQLStatement::destroy(JSC::JSCell* cell) thisObject->~JSSQLStatement(); } -static inline bool rebindValue(JSC::JSGlobalObject* lexicalGlobalObject, sqlite3_stmt* stmt, int i, JSC::JSValue value, JSC::ThrowScope& scope, bool clone) +static inline bool rebindValue(JSC::JSGlobalObject* lexicalGlobalObject, sqlite3* db, sqlite3_stmt* stmt, int i, JSC::JSValue value, JSC::ThrowScope& scope, bool clone) { -#define CHECK_BIND(param) \ - int result = param; \ - if (UNLIKELY(result != SQLITE_OK)) { \ - throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, WTF::String::fromUTF8(sqlite3_errstr(result)))); \ - return false; \ + auto throwSQLiteError = [&]() -> void { + throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, WTF::String::fromUTF8(sqlite3_errmsg(db)))); + }; + +#define CHECK_BIND(param) \ + int result = param; \ + if (UNLIKELY(result != SQLITE_OK)) { \ + throwSQLiteError(); \ + return false; \ } + // only clone if necessary // SQLite has a way to call a destructor // but there doesn't seem to be a way to pass a pointer? @@ -521,7 +587,7 @@ static inline bool rebindValue(JSC::JSGlobalObject* lexicalGlobalObject, sqlite3 // this function does the equivalent of // Object.entries(obj) // except without the intermediate array of arrays -static JSC::JSValue rebindObject(JSC::JSGlobalObject* globalObject, JSC::JSValue targetValue, JSC::ThrowScope& scope, sqlite3_stmt* stmt, bool clone) +static JSC::JSValue rebindObject(JSC::JSGlobalObject* globalObject, JSC::JSValue targetValue, JSC::ThrowScope& scope, sqlite3* db, sqlite3_stmt* stmt, bool clone) { JSObject* target = targetValue.toObject(globalObject); RETURN_IF_EXCEPTION(scope, {}); @@ -557,7 +623,7 @@ static JSC::JSValue rebindObject(JSC::JSGlobalObject* globalObject, JSC::JSValue return JSValue(); } - if (!rebindValue(globalObject, stmt, index, value, scope, clone)) + if (!rebindValue(globalObject, db, stmt, index, value, scope, clone)) return JSValue(); RETURN_IF_EXCEPTION(scope, {}); count++; @@ -566,7 +632,7 @@ static JSC::JSValue rebindObject(JSC::JSGlobalObject* globalObject, JSC::JSValue return jsNumber(count); } -static JSC::JSValue rebindStatement(JSC::JSGlobalObject* lexicalGlobalObject, JSC::JSValue values, JSC::ThrowScope& scope, sqlite3_stmt* stmt, bool clone) +static JSC::JSValue rebindStatement(JSC::JSGlobalObject* lexicalGlobalObject, JSC::JSValue values, JSC::ThrowScope& scope, sqlite3* db, sqlite3_stmt* stmt, bool clone) { sqlite3_clear_bindings(stmt); JSC::JSArray* array = jsDynamicCast(values); @@ -574,7 +640,7 @@ static JSC::JSValue rebindStatement(JSC::JSGlobalObject* lexicalGlobalObject, JS if (!array) { if (JSC::JSObject* object = values.getObject()) { - auto res = rebindObject(lexicalGlobalObject, object, scope, stmt, clone); + auto res = rebindObject(lexicalGlobalObject, object, scope, db, stmt, clone); RETURN_IF_EXCEPTION(scope, {}); return res; } @@ -597,7 +663,7 @@ static JSC::JSValue rebindStatement(JSC::JSGlobalObject* lexicalGlobalObject, JS int i = 0; for (; i < count; i++) { JSC::JSValue value = array->getIndexQuickly(i); - rebindValue(lexicalGlobalObject, stmt, i + 1, value, scope, clone); + rebindValue(lexicalGlobalObject, db, stmt, i + 1, value, scope, clone); RETURN_IF_EXCEPTION(scope, {}); } @@ -826,7 +892,7 @@ JSC_DEFINE_HOST_FUNCTION(jsSQLStatementLoadExtensionFunction, (JSC::JSGlobalObje // TODO: can we disable loading extensions after this? if (rc != SQLITE_OK) { - throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, sqliteString(error != nullptr ? error : sqlite3_errmsg(db)))); + throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, error ? sqliteString(error) : String::fromUTF8(reinterpret_cast(sqlite3_errmsg(db))))); return JSValue::encode(JSC::jsUndefined()); } @@ -907,7 +973,7 @@ JSC_DEFINE_HOST_FUNCTION(jsSQLStatementExecuteFunction, (JSC::JSGlobalObject * l if (!bindingsAliveScope.value().isUndefinedOrNull()) { if (bindingsAliveScope.value().isObject()) { - JSC::JSValue reb = rebindStatement(lexicalGlobalObject, bindingsAliveScope.value(), scope, statement, false); + JSC::JSValue reb = rebindStatement(lexicalGlobalObject, bindingsAliveScope.value(), scope, db, statement, false); if (UNLIKELY(!reb.isNumber())) { sqlite3_finalize(statement); return JSValue::encode(reb); /* this means an error */ @@ -929,7 +995,7 @@ JSC_DEFINE_HOST_FUNCTION(jsSQLStatementExecuteFunction, (JSC::JSGlobalObject * l } if (UNLIKELY(rc != SQLITE_DONE && rc != SQLITE_OK)) { - throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, WTF::String::fromUTF8(sqlite3_errstr(rc)))); + throwException(lexicalGlobalObject, scope, createSQLiteError(lexicalGlobalObject, db)); // we finalize after just incase something about error messages in // sqlite depends on the existence of the most recent statement i don't // think that's actually how this works - just being cautious @@ -1030,6 +1096,10 @@ JSC_DEFINE_HOST_FUNCTION(jsSQLStatementPrepareStatementFunction, (JSC::JSGlobalO sqlite3_stmt* statement = nullptr; + // This is inherently somewhat racy if using Worker + // but that should be okay. + ssize_t currentMemoryUsage = sqlite_malloc_amount; + int rc = SQLITE_OK; if ( // fast path: ascii latin1 string is utf8 @@ -1042,12 +1112,15 @@ JSC_DEFINE_HOST_FUNCTION(jsSQLStatementPrepareStatementFunction, (JSC::JSGlobalO } if (rc != SQLITE_OK) { - throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, WTF::String::fromUTF8(sqlite3_errmsg(db)))); + throwException(lexicalGlobalObject, scope, createSQLiteError(lexicalGlobalObject, db)); return JSValue::encode(JSC::jsUndefined()); } + ssize_t memoryChange = sqlite_malloc_amount - currentMemoryUsage; + JSSQLStatement* sqlStatement = JSSQLStatement::create( - reinterpret_cast(lexicalGlobalObject), statement, databases()[handle]); + reinterpret_cast(lexicalGlobalObject), statement, databases()[handle], memoryChange); + if (bindings.isObject()) { auto* castedThis = sqlStatement; DO_REBIND(bindings) @@ -1057,7 +1130,7 @@ JSC_DEFINE_HOST_FUNCTION(jsSQLStatementPrepareStatementFunction, (JSC::JSGlobalO JSSQLStatementConstructor* JSSQLStatementConstructor::create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure) { - NativeExecutable* executable = vm.getHostFunction(jsSQLStatementPrepareStatementFunction, ImplementationVisibility::Public, callHostFunctionAsConstructor, String("SQLStatement"_s)); + NativeExecutable* executable = vm.getHostFunction(jsSQLStatementPrepareStatementFunction, ImplementationVisibility::Private, callHostFunctionAsConstructor, String("SQLStatement"_s)); JSSQLStatementConstructor* ptr = new (NotNull, JSC::allocateCell(vm)) JSSQLStatementConstructor(vm, executable, globalObject, structure); ptr->finishCreation(vm); @@ -1113,8 +1186,9 @@ JSC_DEFINE_HOST_FUNCTION(jsSQLStatementOpenStatementFunction, (JSC::JSGlobalObje sqlite3* db = nullptr; int statusCode = sqlite3_open_v2(path.utf8().data(), &db, openFlags, nullptr); + if (statusCode != SQLITE_OK) { - throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, WTF::String::fromUTF8(sqlite3_errmsg(db)))); + throwException(lexicalGlobalObject, scope, createSQLiteError(lexicalGlobalObject, db)); return JSValue::encode(jsUndefined()); } @@ -1129,6 +1203,7 @@ JSC_DEFINE_HOST_FUNCTION(jsSQLStatementOpenStatementFunction, (JSC::JSGlobalObje // TODO: log a warning here that defensive mode is unsupported. } auto count = databases().size(); + sqlite3_extended_result_codes(db, 1); databases().append(new VersionSqlite3(db)); RELEASE_AND_RETURN(scope, JSValue::encode(jsNumber(count))); } @@ -1173,7 +1248,7 @@ JSC_DEFINE_HOST_FUNCTION(jsSQLStatementCloseStatementFunction, (JSC::JSGlobalObj int statusCode = sqlite3_close_v2(db); if (statusCode != SQLITE_OK) { - throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, WTF::String::fromUTF8(sqlite3_errmsg(db)))); + throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, WTF::String::fromUTF8(sqlite3_errstr(statusCode)))); return JSValue::encode(jsUndefined()); } @@ -1407,10 +1482,12 @@ JSC_DEFINE_HOST_FUNCTION(jsSQLStatementExecuteStatementFunctionAll, (JSC::JSGlob int statusCode = sqlite3_reset(stmt); if (UNLIKELY(statusCode != SQLITE_OK)) { - throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, WTF::String::fromUTF8(sqlite3_errstr(statusCode)))); + throwException(lexicalGlobalObject, scope, createSQLiteError(lexicalGlobalObject, castedThis->version_db->db)); return JSValue::encode(jsUndefined()); } + ssize_t currentMemoryUsage = sqlite_malloc_amount; + if (callFrame->argumentCount() > 0) { auto arg0 = callFrame->argument(0); DO_REBIND(arg0); @@ -1452,11 +1529,16 @@ JSC_DEFINE_HOST_FUNCTION(jsSQLStatementExecuteStatementFunctionAll, (JSC::JSGlob } if (UNLIKELY(status != SQLITE_DONE && status != SQLITE_OK)) { - throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, WTF::String::fromUTF8(sqlite3_errstr(status)))); + throwException(lexicalGlobalObject, scope, createSQLiteError(lexicalGlobalObject, castedThis->version_db->db)); sqlite3_reset(stmt); return JSValue::encode(jsUndefined()); } + ssize_t memoryChange = sqlite_malloc_amount - currentMemoryUsage; + if (memoryChange > 255) { + vm.heap.deprecatedReportExtraMemory(memoryChange); + } + RELEASE_AND_RETURN(scope, JSC::JSValue::encode(result)); } @@ -1474,7 +1556,7 @@ JSC_DEFINE_HOST_FUNCTION(jsSQLStatementExecuteStatementFunctionGet, (JSC::JSGlob int statusCode = sqlite3_reset(stmt); if (UNLIKELY(statusCode != SQLITE_OK)) { - throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, WTF::String::fromUTF8(sqlite3_errstr(statusCode)))); + throwException(lexicalGlobalObject, scope, createSQLiteError(lexicalGlobalObject, castedThis->version_db->db)); return JSValue::encode(jsUndefined()); } @@ -1503,7 +1585,7 @@ JSC_DEFINE_HOST_FUNCTION(jsSQLStatementExecuteStatementFunctionGet, (JSC::JSGlob if (status == SQLITE_DONE || status == SQLITE_OK) { RELEASE_AND_RETURN(scope, JSValue::encode(result)); } else { - throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, WTF::String::fromUTF8(sqlite3_errstr(status)))); + throwException(lexicalGlobalObject, scope, createSQLiteError(lexicalGlobalObject, castedThis->version_db->db)); sqlite3_reset(stmt); return JSValue::encode(jsUndefined()); } @@ -1523,7 +1605,7 @@ JSC_DEFINE_JIT_OPERATION(jsSQLStatementExecuteStatementFunctionGetWithoutTypeChe int statusCode = sqlite3_reset(stmt); if (UNLIKELY(statusCode != SQLITE_OK)) { - throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, WTF::String::fromUTF8(sqlite3_errstr(statusCode)))); + throwException(lexicalGlobalObject, scope, createSQLiteError(lexicalGlobalObject, castedThis->version_db->db)); return JSValue::encode(jsUndefined()); } @@ -1547,7 +1629,7 @@ JSC_DEFINE_JIT_OPERATION(jsSQLStatementExecuteStatementFunctionGetWithoutTypeChe if (status == SQLITE_DONE || status == SQLITE_OK) { RELEASE_AND_RETURN(scope, JSValue::encode(result)); } else { - throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, WTF::String::fromUTF8(sqlite3_errstr(status)))); + throwException(lexicalGlobalObject, scope, createSQLiteError(lexicalGlobalObject, castedThis->version_db->db)); sqlite3_reset(stmt); return JSValue::encode(jsUndefined()); } @@ -1567,7 +1649,7 @@ JSC_DEFINE_HOST_FUNCTION(jsSQLStatementExecuteStatementFunctionRows, (JSC::JSGlo int statusCode = sqlite3_reset(stmt); if (UNLIKELY(statusCode != SQLITE_OK)) { - throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, WTF::String::fromUTF8(sqlite3_errstr(statusCode)))); + throwException(lexicalGlobalObject, scope, createSQLiteError(lexicalGlobalObject, castedThis->version_db->db)); sqlite3_reset(stmt); return JSValue::encode(jsUndefined()); } @@ -1620,7 +1702,7 @@ JSC_DEFINE_HOST_FUNCTION(jsSQLStatementExecuteStatementFunctionRows, (JSC::JSGlo } if (UNLIKELY(status != SQLITE_DONE && status != SQLITE_OK)) { - throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, WTF::String::fromUTF8(sqlite3_errstr(status)))); + throwException(lexicalGlobalObject, scope, createSQLiteError(lexicalGlobalObject, castedThis->version_db->db)); sqlite3_reset(stmt); return JSValue::encode(jsUndefined()); } @@ -1643,7 +1725,7 @@ JSC_DEFINE_HOST_FUNCTION(jsSQLStatementExecuteStatementFunctionRun, (JSC::JSGlob int statusCode = sqlite3_reset(stmt); if (UNLIKELY(statusCode != SQLITE_OK)) { - throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, WTF::String::fromUTF8(sqlite3_errstr(statusCode)))); + throwException(lexicalGlobalObject, scope, createSQLiteError(lexicalGlobalObject, castedThis->version_db->db)); return JSValue::encode(jsUndefined()); } @@ -1666,8 +1748,8 @@ JSC_DEFINE_HOST_FUNCTION(jsSQLStatementExecuteStatementFunctionRun, (JSC::JSGlob } if (UNLIKELY(status != SQLITE_DONE && status != SQLITE_OK)) { + throwException(lexicalGlobalObject, scope, createSQLiteError(lexicalGlobalObject, castedThis->version_db->db)); sqlite3_reset(stmt); - throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, WTF::String::fromUTF8(sqlite3_errstr(status)))); return JSValue::encode(jsUndefined()); } @@ -1766,6 +1848,8 @@ void JSSQLStatement::finishCreation(VM& vm) { Base::finishCreation(vm); ASSERT(inherits(info())); + + vm.heap.reportExtraMemoryAllocated(this, this->extraMemorySize); } JSSQLStatement::~JSSQLStatement() @@ -1780,12 +1864,21 @@ JSSQLStatement::~JSSQLStatement() } } -JSC::JSValue JSSQLStatement::rebind(JSC::JSGlobalObject* lexicalGlobalObject, JSC::JSValue values, bool clone) +void JSSQLStatement::analyzeHeap(JSCell* cell, HeapAnalyzer& analyzer) +{ + auto* thisObject = jsCast(cell); + if (thisObject->stmt) + analyzer.setWrappedObjectForCell(cell, thisObject->stmt); + + Base::analyzeHeap(cell, analyzer); +} + +JSC::JSValue JSSQLStatement::rebind(JSC::JSGlobalObject* lexicalGlobalObject, JSC::JSValue values, bool clone, sqlite3* db) { JSC::VM& vm = lexicalGlobalObject->vm(); auto scope = DECLARE_THROW_SCOPE(vm); auto* stmt = this->stmt; - auto val = rebindStatement(lexicalGlobalObject, values, scope, stmt, clone); + auto val = rebindStatement(lexicalGlobalObject, values, scope, this->version_db->db, stmt, clone); if (val.isNumber()) { RELEASE_AND_RETURN(scope, val); } else { @@ -1799,6 +1892,9 @@ void JSSQLStatement::visitChildrenImpl(JSCell* cell, Visitor& visitor) JSSQLStatement* thisObject = jsCast(cell); ASSERT_GC_OBJECT_INHERITS(thisObject, info()); Base::visitChildren(thisObject, visitor); + + visitor.reportExtraMemoryVisited(thisObject->extraMemorySize); + visitor.append(thisObject->_structure); visitor.append(thisObject->_prototype); } diff --git a/src/bun.js/bindings/sqlite/lazy_sqlite3.h b/src/bun.js/bindings/sqlite/lazy_sqlite3.h index 77a1b82b5c..06c0a83e55 100644 --- a/src/bun.js/bindings/sqlite/lazy_sqlite3.h +++ b/src/bun.js/bindings/sqlite/lazy_sqlite3.h @@ -20,6 +20,7 @@ typedef int (*lazy_sqlite3_bind_parameter_index_type)(sqlite3_stmt*, const char* typedef int (*lazy_sqlite3_changes_type)(sqlite3*); typedef int (*lazy_sqlite3_clear_bindings_type)(sqlite3_stmt*); typedef int (*lazy_sqlite3_close_v2_type)(sqlite3*); +typedef int (*lazy_sqlite3_extended_result_codes_type)(sqlite3*, int onoff); typedef const void* (*lazy_sqlite3_column_blob_type)(sqlite3_stmt*, int iCol); typedef double (*lazy_sqlite3_column_double_type)(sqlite3_stmt*, int iCol); typedef int (*lazy_sqlite3_column_int_type)(sqlite3_stmt*, int iCol); @@ -32,6 +33,9 @@ typedef int (*lazy_sqlite3_column_count_type)(sqlite3_stmt* pStmt); typedef const char* (*lazy_sqlite3_column_decltype_type)(sqlite3_stmt*, int); typedef const char* (*lazy_sqlite3_column_name_type)(sqlite3_stmt*, int N); typedef const char* (*lazy_sqlite3_errmsg_type)(sqlite3*); +typedef int (*lazy_sqlite3_extended_errcode_type)(sqlite3*); +typedef int (*lazy_sqlite3_error_offset_type)(sqlite3*); +typedef int64_t (*lazy_sqlite3_memory_used_type)(); typedef const char* (*lazy_sqlite3_errstr_type)(int); typedef char* (*lazy_sqlite3_expanded_sql_type)(sqlite3_stmt* pStmt); typedef int (*lazy_sqlite3_finalize_type)(sqlite3_stmt* pStmt); @@ -57,6 +61,7 @@ typedef int (*lazy_sqlite3_step_type)(sqlite3_stmt*); typedef int (*lazy_sqlite3_clear_bindings_type)(sqlite3_stmt*); typedef int (*lazy_sqlite3_column_type_type)(sqlite3_stmt*, int iCol); typedef int (*lazy_sqlite3_db_config_type)(sqlite3*, int op, ...); + typedef int (*lazy_sqlite3_load_extension_type)( sqlite3* db, /* Load the extension into this database connection */ const char* zFile, /* Name of the shared library containing extension */ @@ -125,6 +130,10 @@ static lazy_sqlite3_deserialize_type lazy_sqlite3_deserialize; static lazy_sqlite3_stmt_readonly_type lazy_sqlite3_stmt_readonly; static lazy_sqlite3_compileoption_used_type lazy_sqlite3_compileoption_used; static lazy_sqlite3_config_type lazy_sqlite3_config; +static lazy_sqlite3_extended_result_codes_type lazy_sqlite3_extended_result_codes; +static lazy_sqlite3_extended_errcode_type lazy_sqlite3_extended_errcode; +static lazy_sqlite3_error_offset_type lazy_sqlite3_error_offset; +static lazy_sqlite3_memory_used_type lazy_sqlite3_memory_used; #define sqlite3_bind_blob lazy_sqlite3_bind_blob #define sqlite3_bind_double lazy_sqlite3_bind_double @@ -167,6 +176,10 @@ static lazy_sqlite3_config_type lazy_sqlite3_config; #define sqlite3_column_int64 lazy_sqlite3_column_int64 #define sqlite3_compileoption_used lazy_sqlite3_compileoption_used #define sqlite3_config lazy_sqlite3_config +#define sqlite3_extended_result_codes lazy_sqlite3_extended_result_codes +#define sqlite3_extended_errcode lazy_sqlite3_extended_errcode +#define sqlite3_error_offset lazy_sqlite3_error_offset +#define sqlite3_memory_used lazy_sqlite3_memory_used #if !OS(WINDOWS) #define HMODULE void* @@ -242,6 +255,34 @@ static int lazyLoadSQLite() lazy_sqlite3_stmt_readonly = (lazy_sqlite3_stmt_readonly_type)dlsym(sqlite3_handle, "sqlite3_stmt_readonly"); lazy_sqlite3_compileoption_used = (lazy_sqlite3_compileoption_used_type)dlsym(sqlite3_handle, "sqlite3_compileoption_used"); lazy_sqlite3_config = (lazy_sqlite3_config_type)dlsym(sqlite3_handle, "sqlite3_config"); + lazy_sqlite3_extended_result_codes = (lazy_sqlite3_extended_result_codes_type)dlsym(sqlite3_handle, "sqlite3_extended_result_codes"); + lazy_sqlite3_extended_errcode = (lazy_sqlite3_extended_errcode_type)dlsym(sqlite3_handle, "sqlite3_extended_errcode"); + lazy_sqlite3_error_offset = (lazy_sqlite3_error_offset_type)dlsym(sqlite3_handle, "sqlite3_error_offset"); + lazy_sqlite3_memory_used = (lazy_sqlite3_memory_used_type)dlsym(sqlite3_handle, "sqlite3_memory_used"); + + if (!lazy_sqlite3_extended_result_codes) { + lazy_sqlite3_extended_result_codes = [](sqlite3*, int) -> int { + return 0; + }; + } + + if (!lazy_sqlite3_extended_errcode) { + lazy_sqlite3_extended_errcode = [](sqlite3*) -> int { + return 0; + }; + } + + if (!lazy_sqlite3_error_offset) { + lazy_sqlite3_error_offset = [](sqlite3*) -> int { + return -1; + }; + } + + if (!lazy_sqlite3_memory_used) { + lazy_sqlite3_memory_used = []() -> int64_t { + return 0; + }; + } return 0; } diff --git a/src/bun.js/bindings/sqlite/sqlite3_error_codes.h b/src/bun.js/bindings/sqlite/sqlite3_error_codes.h new file mode 100644 index 0000000000..25f52630ed --- /dev/null +++ b/src/bun.js/bindings/sqlite/sqlite3_error_codes.h @@ -0,0 +1,763 @@ +// This file must be updated whenever we update SQLite3 +// +// When loading a system version of sqlite, some of these error codes will never +// be returned because the system version of SQLite may predate the error codes +// in this file. +// +// For code simplicity we always use the error codes from this file, even when +// +#pragma once + +#ifndef SQLITE_OK + +#define SQLITE_OK 0 /* Successful result */ + +#endif + +#ifndef SQLITE_ERROR + +#define SQLITE_ERROR 1 /* Generic error */ + +#endif + +#ifndef SQLITE_INTERNAL + +#define SQLITE_INTERNAL 2 /* Internal logic error in SQLite */ + +#endif + +#ifndef SQLITE_PERM + +#define SQLITE_PERM 3 /* Access permission denied */ + +#endif + +#ifndef SQLITE_ABORT + +#define SQLITE_ABORT 4 /* Callback routine requested an abort */ + +#endif + +#ifndef SQLITE_BUSY + +#define SQLITE_BUSY 5 /* The database file is locked */ + +#endif + +#ifndef SQLITE_LOCKED + +#define SQLITE_LOCKED 6 /* A table in the database is locked */ + +#endif + +#ifndef SQLITE_NOMEM + +#define SQLITE_NOMEM 7 /* A malloc() failed */ + +#endif + +#ifndef SQLITE_READONLY + +#define SQLITE_READONLY 8 /* Attempt to write a readonly database */ + +#endif + +#ifndef SQLITE_INTERRUPT + +#define SQLITE_INTERRUPT 9 /* Operation terminated by sqlite3_interrupt()*/ + +#endif + +#ifndef SQLITE_IOERR + +#define SQLITE_IOERR 10 /* Some kind of disk I/O error occurred */ + +#endif + +#ifndef SQLITE_CORRUPT + +#define SQLITE_CORRUPT 11 /* The database disk image is malformed */ + +#endif + +#ifndef SQLITE_NOTFOUND + +#define SQLITE_NOTFOUND 12 /* Unknown opcode in sqlite3_file_control() */ + +#endif + +#ifndef SQLITE_FULL + +#define SQLITE_FULL 13 /* Insertion failed because database is full */ + +#endif + +#ifndef SQLITE_CANTOPEN + +#define SQLITE_CANTOPEN 14 /* Unable to open the database file */ + +#endif + +#ifndef SQLITE_PROTOCOL + +#define SQLITE_PROTOCOL 15 /* Database lock protocol error */ + +#endif + +#ifndef SQLITE_EMPTY + +#define SQLITE_EMPTY 16 /* Internal use only */ + +#endif + +#ifndef SQLITE_SCHEMA + +#define SQLITE_SCHEMA 17 /* The database schema changed */ + +#endif + +#ifndef SQLITE_TOOBIG + +#define SQLITE_TOOBIG 18 /* String or BLOB exceeds size limit */ + +#endif + +#ifndef SQLITE_CONSTRAINT + +#define SQLITE_CONSTRAINT 19 /* Abort due to constraint violation */ + +#endif + +#ifndef SQLITE_MISMATCH + +#define SQLITE_MISMATCH 20 /* Data type mismatch */ + +#endif + +#ifndef SQLITE_MISUSE + +#define SQLITE_MISUSE 21 /* Library used incorrectly */ + +#endif + +#ifndef SQLITE_NOLFS + +#define SQLITE_NOLFS 22 /* Uses OS features not supported on host */ + +#endif + +#ifndef SQLITE_AUTH + +#define SQLITE_AUTH 23 /* Authorization denied */ + +#endif + +#ifndef SQLITE_FORMAT + +#define SQLITE_FORMAT 24 /* Not used */ + +#endif + +#ifndef SQLITE_RANGE + +#define SQLITE_RANGE 25 /* 2nd parameter to sqlite3_bind out of range */ + +#endif + +#ifndef SQLITE_NOTADB + +#define SQLITE_NOTADB 26 /* File opened that is not a database file */ + +#endif + +#ifndef SQLITE_NOTICE + +#define SQLITE_NOTICE 27 /* Notifications from sqlite3_log() */ + +#endif + +#ifndef SQLITE_WARNING + +#define SQLITE_WARNING 28 /* Warnings from sqlite3_log() */ + +#endif + +#ifndef SQLITE_ROW + +#define SQLITE_ROW 100 /* sqlite3_step() has another row ready */ + +#endif + +#ifndef SQLITE_DONE + +#define SQLITE_DONE 101 /* sqlite3_step() has finished executing */ + +#endif + +#ifndef SQLITE_ERROR_MISSING_COLLSEQ + +#define SQLITE_ERROR_MISSING_COLLSEQ (SQLITE_ERROR | (1 << 8)) + +#endif + +#ifndef SQLITE_ERROR_RETRY + +#define SQLITE_ERROR_RETRY (SQLITE_ERROR | (2 << 8)) + +#endif + +#ifndef SQLITE_ERROR_SNAPSHOT + +#define SQLITE_ERROR_SNAPSHOT (SQLITE_ERROR | (3 << 8)) + +#endif + +#ifndef SQLITE_IOERR_READ + +#define SQLITE_IOERR_READ (SQLITE_IOERR | (1 << 8)) + +#endif + +#ifndef SQLITE_IOERR_SHORT_READ + +#define SQLITE_IOERR_SHORT_READ (SQLITE_IOERR | (2 << 8)) + +#endif + +#ifndef SQLITE_IOERR_WRITE + +#define SQLITE_IOERR_WRITE (SQLITE_IOERR | (3 << 8)) + +#endif + +#ifndef SQLITE_IOERR_FSYNC + +#define SQLITE_IOERR_FSYNC (SQLITE_IOERR | (4 << 8)) + +#endif + +#ifndef SQLITE_IOERR_DIR_FSYNC + +#define SQLITE_IOERR_DIR_FSYNC (SQLITE_IOERR | (5 << 8)) + +#endif + +#ifndef SQLITE_IOERR_TRUNCATE + +#define SQLITE_IOERR_TRUNCATE (SQLITE_IOERR | (6 << 8)) + +#endif + +#ifndef SQLITE_IOERR_FSTAT + +#define SQLITE_IOERR_FSTAT (SQLITE_IOERR | (7 << 8)) + +#endif + +#ifndef SQLITE_IOERR_UNLOCK + +#define SQLITE_IOERR_UNLOCK (SQLITE_IOERR | (8 << 8)) + +#endif + +#ifndef SQLITE_IOERR_RDLOCK + +#define SQLITE_IOERR_RDLOCK (SQLITE_IOERR | (9 << 8)) + +#endif + +#ifndef SQLITE_IOERR_DELETE + +#define SQLITE_IOERR_DELETE (SQLITE_IOERR | (10 << 8)) + +#endif + +#ifndef SQLITE_IOERR_BLOCKED + +#define SQLITE_IOERR_BLOCKED (SQLITE_IOERR | (11 << 8)) + +#endif + +#ifndef SQLITE_IOERR_NOMEM + +#define SQLITE_IOERR_NOMEM (SQLITE_IOERR | (12 << 8)) + +#endif + +#ifndef SQLITE_IOERR_ACCESS + +#define SQLITE_IOERR_ACCESS (SQLITE_IOERR | (13 << 8)) + +#endif + +#ifndef SQLITE_IOERR_CHECKRESERVEDLOCK + +#define SQLITE_IOERR_CHECKRESERVEDLOCK (SQLITE_IOERR | (14 << 8)) + +#endif + +#ifndef SQLITE_IOERR_LOCK + +#define SQLITE_IOERR_LOCK (SQLITE_IOERR | (15 << 8)) + +#endif + +#ifndef SQLITE_IOERR_CLOSE + +#define SQLITE_IOERR_CLOSE (SQLITE_IOERR | (16 << 8)) + +#endif + +#ifndef SQLITE_IOERR_DIR_CLOSE + +#define SQLITE_IOERR_DIR_CLOSE (SQLITE_IOERR | (17 << 8)) + +#endif + +#ifndef SQLITE_IOERR_SHMOPEN + +#define SQLITE_IOERR_SHMOPEN (SQLITE_IOERR | (18 << 8)) + +#endif + +#ifndef SQLITE_IOERR_SHMSIZE + +#define SQLITE_IOERR_SHMSIZE (SQLITE_IOERR | (19 << 8)) + +#endif + +#ifndef SQLITE_IOERR_SHMLOCK + +#define SQLITE_IOERR_SHMLOCK (SQLITE_IOERR | (20 << 8)) + +#endif + +#ifndef SQLITE_IOERR_SHMMAP + +#define SQLITE_IOERR_SHMMAP (SQLITE_IOERR | (21 << 8)) + +#endif + +#ifndef SQLITE_IOERR_SEEK + +#define SQLITE_IOERR_SEEK (SQLITE_IOERR | (22 << 8)) + +#endif + +#ifndef SQLITE_IOERR_DELETE_NOENT + +#define SQLITE_IOERR_DELETE_NOENT (SQLITE_IOERR | (23 << 8)) + +#endif + +#ifndef SQLITE_IOERR_MMAP + +#define SQLITE_IOERR_MMAP (SQLITE_IOERR | (24 << 8)) + +#endif + +#ifndef SQLITE_IOERR_GETTEMPPATH + +#define SQLITE_IOERR_GETTEMPPATH (SQLITE_IOERR | (25 << 8)) + +#endif + +#ifndef SQLITE_IOERR_CONVPATH + +#define SQLITE_IOERR_CONVPATH (SQLITE_IOERR | (26 << 8)) + +#endif + +#ifndef SQLITE_IOERR_VNODE + +#define SQLITE_IOERR_VNODE (SQLITE_IOERR | (27 << 8)) + +#endif + +#ifndef SQLITE_IOERR_AUTH + +#define SQLITE_IOERR_AUTH (SQLITE_IOERR | (28 << 8)) + +#endif + +#ifndef SQLITE_IOERR_BEGIN_ATOMIC + +#define SQLITE_IOERR_BEGIN_ATOMIC (SQLITE_IOERR | (29 << 8)) + +#endif + +#ifndef SQLITE_IOERR_COMMIT_ATOMIC + +#define SQLITE_IOERR_COMMIT_ATOMIC (SQLITE_IOERR | (30 << 8)) + +#endif + +#ifndef SQLITE_IOERR_ROLLBACK_ATOMIC + +#define SQLITE_IOERR_ROLLBACK_ATOMIC (SQLITE_IOERR | (31 << 8)) + +#endif + +#ifndef SQLITE_IOERR_DATA + +#define SQLITE_IOERR_DATA (SQLITE_IOERR | (32 << 8)) + +#endif + +#ifndef SQLITE_IOERR_CORRUPTFS + +#define SQLITE_IOERR_CORRUPTFS (SQLITE_IOERR | (33 << 8)) + +#endif + +#ifndef SQLITE_IOERR_IN_PAGE + +#define SQLITE_IOERR_IN_PAGE (SQLITE_IOERR | (34 << 8)) + +#endif + +#ifndef SQLITE_LOCKED_SHAREDCACHE + +#define SQLITE_LOCKED_SHAREDCACHE (SQLITE_LOCKED | (1 << 8)) + +#endif + +#ifndef SQLITE_LOCKED_VTAB + +#define SQLITE_LOCKED_VTAB (SQLITE_LOCKED | (2 << 8)) + +#endif + +#ifndef SQLITE_BUSY_RECOVERY + +#define SQLITE_BUSY_RECOVERY (SQLITE_BUSY | (1 << 8)) + +#endif + +#ifndef SQLITE_BUSY_SNAPSHOT + +#define SQLITE_BUSY_SNAPSHOT (SQLITE_BUSY | (2 << 8)) + +#endif + +#ifndef SQLITE_BUSY_TIMEOUT + +#define SQLITE_BUSY_TIMEOUT (SQLITE_BUSY | (3 << 8)) + +#endif + +#ifndef SQLITE_CANTOPEN_NOTEMPDIR + +#define SQLITE_CANTOPEN_NOTEMPDIR (SQLITE_CANTOPEN | (1 << 8)) + +#endif + +#ifndef SQLITE_CANTOPEN_ISDIR + +#define SQLITE_CANTOPEN_ISDIR (SQLITE_CANTOPEN | (2 << 8)) + +#endif + +#ifndef SQLITE_CANTOPEN_FULLPATH + +#define SQLITE_CANTOPEN_FULLPATH (SQLITE_CANTOPEN | (3 << 8)) + +#endif + +#ifndef SQLITE_CANTOPEN_CONVPATH + +#define SQLITE_CANTOPEN_CONVPATH (SQLITE_CANTOPEN | (4 << 8)) + +#endif + +#ifndef SQLITE_CANTOPEN_DIRTYWAL + +#define SQLITE_CANTOPEN_DIRTYWAL (SQLITE_CANTOPEN | (5 << 8)) /* Not Used */ + +#endif + +#ifndef SQLITE_CANTOPEN_SYMLINK + +#define SQLITE_CANTOPEN_SYMLINK (SQLITE_CANTOPEN | (6 << 8)) + +#endif + +#ifndef SQLITE_CORRUPT_VTAB + +#define SQLITE_CORRUPT_VTAB (SQLITE_CORRUPT | (1 << 8)) + +#endif + +#ifndef SQLITE_CORRUPT_SEQUENCE + +#define SQLITE_CORRUPT_SEQUENCE (SQLITE_CORRUPT | (2 << 8)) + +#endif + +#ifndef SQLITE_CORRUPT_INDEX + +#define SQLITE_CORRUPT_INDEX (SQLITE_CORRUPT | (3 << 8)) + +#endif + +#ifndef SQLITE_READONLY_RECOVERY + +#define SQLITE_READONLY_RECOVERY (SQLITE_READONLY | (1 << 8)) + +#endif + +#ifndef SQLITE_READONLY_CANTLOCK + +#define SQLITE_READONLY_CANTLOCK (SQLITE_READONLY | (2 << 8)) + +#endif + +#ifndef SQLITE_READONLY_ROLLBACK + +#define SQLITE_READONLY_ROLLBACK (SQLITE_READONLY | (3 << 8)) + +#endif + +#ifndef SQLITE_READONLY_DBMOVED + +#define SQLITE_READONLY_DBMOVED (SQLITE_READONLY | (4 << 8)) + +#endif + +#ifndef SQLITE_READONLY_CANTINIT + +#define SQLITE_READONLY_CANTINIT (SQLITE_READONLY | (5 << 8)) + +#endif + +#ifndef SQLITE_READONLY_DIRECTORY + +#define SQLITE_READONLY_DIRECTORY (SQLITE_READONLY | (6 << 8)) + +#endif + +#ifndef SQLITE_ABORT_ROLLBACK + +#define SQLITE_ABORT_ROLLBACK (SQLITE_ABORT | (2 << 8)) + +#endif + +#ifndef SQLITE_CONSTRAINT_CHECK + +#define SQLITE_CONSTRAINT_CHECK (SQLITE_CONSTRAINT | (1 << 8)) + +#endif + +#ifndef SQLITE_CONSTRAINT_COMMITHOOK + +#define SQLITE_CONSTRAINT_COMMITHOOK (SQLITE_CONSTRAINT | (2 << 8)) + +#endif + +#ifndef SQLITE_CONSTRAINT_FOREIGNKEY + +#define SQLITE_CONSTRAINT_FOREIGNKEY (SQLITE_CONSTRAINT | (3 << 8)) + +#endif + +#ifndef SQLITE_CONSTRAINT_FUNCTION + +#define SQLITE_CONSTRAINT_FUNCTION (SQLITE_CONSTRAINT | (4 << 8)) + +#endif + +#ifndef SQLITE_CONSTRAINT_NOTNULL + +#define SQLITE_CONSTRAINT_NOTNULL (SQLITE_CONSTRAINT | (5 << 8)) + +#endif + +#ifndef SQLITE_CONSTRAINT_PRIMARYKEY + +#define SQLITE_CONSTRAINT_PRIMARYKEY (SQLITE_CONSTRAINT | (6 << 8)) + +#endif + +#ifndef SQLITE_CONSTRAINT_TRIGGER + +#define SQLITE_CONSTRAINT_TRIGGER (SQLITE_CONSTRAINT | (7 << 8)) + +#endif + +#ifndef SQLITE_CONSTRAINT_UNIQUE + +#define SQLITE_CONSTRAINT_UNIQUE (SQLITE_CONSTRAINT | (8 << 8)) + +#endif + +#ifndef SQLITE_CONSTRAINT_VTAB + +#define SQLITE_CONSTRAINT_VTAB (SQLITE_CONSTRAINT | (9 << 8)) + +#endif + +#ifndef SQLITE_CONSTRAINT_ROWID + +#define SQLITE_CONSTRAINT_ROWID (SQLITE_CONSTRAINT | (10 << 8)) + +#endif + +#ifndef SQLITE_CONSTRAINT_PINNED + +#define SQLITE_CONSTRAINT_PINNED (SQLITE_CONSTRAINT | (11 << 8)) + +#endif + +#ifndef SQLITE_CONSTRAINT_DATATYPE + +#define SQLITE_CONSTRAINT_DATATYPE (SQLITE_CONSTRAINT | (12 << 8)) + +#endif + +#ifndef SQLITE_NOTICE_RECOVER_WAL + +#define SQLITE_NOTICE_RECOVER_WAL (SQLITE_NOTICE | (1 << 8)) + +#endif + +#ifndef SQLITE_NOTICE_RECOVER_ROLLBACK + +#define SQLITE_NOTICE_RECOVER_ROLLBACK (SQLITE_NOTICE | (2 << 8)) + +#endif + +#ifndef SQLITE_NOTICE_RBU + +#define SQLITE_NOTICE_RBU (SQLITE_NOTICE | (3 << 8)) + +#endif + +#ifndef SQLITE_WARNING_AUTOINDEX + +#define SQLITE_WARNING_AUTOINDEX (SQLITE_WARNING | (1 << 8)) + +#endif + +#ifndef SQLITE_AUTH_USER + +#define SQLITE_AUTH_USER (SQLITE_AUTH | (1 << 8)) + +#endif + +#ifndef SQLITE_OK_LOAD_PERMANENTLY + +#define SQLITE_OK_LOAD_PERMANENTLY (SQLITE_OK | (1 << 8)) + +#endif + +#ifndef SQLITE_OK_SYMLINK + +#define SQLITE_OK_SYMLINK (SQLITE_OK | (2 << 8)) + +#endif + +#define FOR_EACH_SQLITE_ERROR(MACRO) \ + MACRO(SQLITE_INTERNAL) \ + MACRO(SQLITE_PERM) \ + MACRO(SQLITE_ABORT) \ + MACRO(SQLITE_BUSY) \ + MACRO(SQLITE_LOCKED) \ + MACRO(SQLITE_NOMEM) \ + MACRO(SQLITE_READONLY) \ + MACRO(SQLITE_INTERRUPT) \ + MACRO(SQLITE_IOERR) \ + MACRO(SQLITE_CORRUPT) \ + MACRO(SQLITE_NOTFOUND) \ + MACRO(SQLITE_FULL) \ + MACRO(SQLITE_CANTOPEN) \ + MACRO(SQLITE_PROTOCOL) \ + MACRO(SQLITE_EMPTY) \ + MACRO(SQLITE_SCHEMA) \ + MACRO(SQLITE_TOOBIG) \ + MACRO(SQLITE_CONSTRAINT) \ + MACRO(SQLITE_MISMATCH) \ + MACRO(SQLITE_MISUSE) \ + MACRO(SQLITE_NOLFS) \ + MACRO(SQLITE_AUTH) \ + MACRO(SQLITE_FORMAT) \ + MACRO(SQLITE_RANGE) \ + MACRO(SQLITE_NOTADB) \ + MACRO(SQLITE_NOTICE) \ + MACRO(SQLITE_WARNING) \ + MACRO(SQLITE_ERROR_MISSING_COLLSEQ) \ + MACRO(SQLITE_ERROR_RETRY) \ + MACRO(SQLITE_ERROR_SNAPSHOT) \ + MACRO(SQLITE_IOERR_READ) \ + MACRO(SQLITE_IOERR_SHORT_READ) \ + MACRO(SQLITE_IOERR_WRITE) \ + MACRO(SQLITE_IOERR_FSYNC) \ + MACRO(SQLITE_IOERR_DIR_FSYNC) \ + MACRO(SQLITE_IOERR_TRUNCATE) \ + MACRO(SQLITE_IOERR_FSTAT) \ + MACRO(SQLITE_IOERR_UNLOCK) \ + MACRO(SQLITE_IOERR_RDLOCK) \ + MACRO(SQLITE_IOERR_DELETE) \ + MACRO(SQLITE_IOERR_BLOCKED) \ + MACRO(SQLITE_IOERR_NOMEM) \ + MACRO(SQLITE_IOERR_ACCESS) \ + MACRO(SQLITE_IOERR_CHECKRESERVEDLOCK) \ + MACRO(SQLITE_IOERR_LOCK) \ + MACRO(SQLITE_IOERR_CLOSE) \ + MACRO(SQLITE_IOERR_DIR_CLOSE) \ + MACRO(SQLITE_IOERR_SHMOPEN) \ + MACRO(SQLITE_IOERR_SHMSIZE) \ + MACRO(SQLITE_IOERR_SHMLOCK) \ + MACRO(SQLITE_IOERR_SHMMAP) \ + MACRO(SQLITE_IOERR_SEEK) \ + MACRO(SQLITE_IOERR_DELETE_NOENT) \ + MACRO(SQLITE_IOERR_MMAP) \ + MACRO(SQLITE_IOERR_GETTEMPPATH) \ + MACRO(SQLITE_IOERR_CONVPATH) \ + MACRO(SQLITE_IOERR_VNODE) \ + MACRO(SQLITE_IOERR_AUTH) \ + MACRO(SQLITE_IOERR_BEGIN_ATOMIC) \ + MACRO(SQLITE_IOERR_COMMIT_ATOMIC) \ + MACRO(SQLITE_IOERR_ROLLBACK_ATOMIC) \ + MACRO(SQLITE_IOERR_DATA) \ + MACRO(SQLITE_IOERR_CORRUPTFS) \ + MACRO(SQLITE_IOERR_IN_PAGE) \ + MACRO(SQLITE_LOCKED_SHAREDCACHE) \ + MACRO(SQLITE_LOCKED_VTAB) \ + MACRO(SQLITE_BUSY_RECOVERY) \ + MACRO(SQLITE_BUSY_SNAPSHOT) \ + MACRO(SQLITE_BUSY_TIMEOUT) \ + MACRO(SQLITE_CANTOPEN_NOTEMPDIR) \ + MACRO(SQLITE_CANTOPEN_ISDIR) \ + MACRO(SQLITE_CANTOPEN_FULLPATH) \ + MACRO(SQLITE_CANTOPEN_CONVPATH) \ + MACRO(SQLITE_CANTOPEN_DIRTYWAL) \ + MACRO(SQLITE_CANTOPEN_SYMLINK) \ + MACRO(SQLITE_CORRUPT_VTAB) \ + MACRO(SQLITE_CORRUPT_SEQUENCE) \ + MACRO(SQLITE_CORRUPT_INDEX) \ + MACRO(SQLITE_READONLY_RECOVERY) \ + MACRO(SQLITE_READONLY_CANTLOCK) \ + MACRO(SQLITE_READONLY_ROLLBACK) \ + MACRO(SQLITE_READONLY_DBMOVED) \ + MACRO(SQLITE_READONLY_CANTINIT) \ + MACRO(SQLITE_READONLY_DIRECTORY) \ + MACRO(SQLITE_ABORT_ROLLBACK) \ + MACRO(SQLITE_CONSTRAINT_CHECK) \ + MACRO(SQLITE_CONSTRAINT_COMMITHOOK) \ + MACRO(SQLITE_CONSTRAINT_FOREIGNKEY) \ + MACRO(SQLITE_CONSTRAINT_FUNCTION) \ + MACRO(SQLITE_CONSTRAINT_NOTNULL) \ + MACRO(SQLITE_CONSTRAINT_PRIMARYKEY) \ + MACRO(SQLITE_CONSTRAINT_TRIGGER) \ + MACRO(SQLITE_CONSTRAINT_UNIQUE) \ + MACRO(SQLITE_CONSTRAINT_VTAB) \ + MACRO(SQLITE_CONSTRAINT_ROWID) \ + MACRO(SQLITE_CONSTRAINT_PINNED) \ + MACRO(SQLITE_CONSTRAINT_DATATYPE) \ + MACRO(SQLITE_NOTICE_RECOVER_WAL) \ + MACRO(SQLITE_NOTICE_RECOVER_ROLLBACK) \ + MACRO(SQLITE_NOTICE_RBU) \ + MACRO(SQLITE_WARNING_AUTOINDEX) \ + MACRO(SQLITE_AUTH_USER) \ + MACRO(SQLITE_OK_LOAD_PERMANENTLY) \ + MACRO(SQLITE_OK_SYMLINK) \ No newline at end of file diff --git a/src/bun.js/javascript.zig b/src/bun.js/javascript.zig index 6aa09174fd..11e74ce474 100644 --- a/src/bun.js/javascript.zig +++ b/src/bun.js/javascript.zig @@ -2878,7 +2878,7 @@ pub const VirtualMachine = struct { const show = Show{ .system_code = !exception.system_code.eql(name) and !exception.system_code.isEmpty(), .syscall = !exception.syscall.isEmpty(), - .errno = exception.errno < 0, + .errno = exception.errno != 0, .path = !exception.path.isEmpty(), .fd = exception.fd != -1, }; diff --git a/src/js/bun/sqlite.ts b/src/js/bun/sqlite.ts index 2e8a3c8591..bddada4fec 100644 --- a/src/js/bun/sqlite.ts +++ b/src/js/bun/sqlite.ts @@ -426,10 +426,19 @@ const wrapTransaction = (fn, db, { begin, commit, rollback, savepoint, release, } }; +// This class is never actually thrown +// so we implement instanceof so that it could theoretically be caught +class SQLiteError extends Error { + static [Symbol.hasInstance](instance) { + return instance?.name === "SQLiteError"; + } +} + export default { __esModule: true, Database, Statement, constants, default: Database, + SQLiteError, }; diff --git a/test/js/bun/sqlite/sqlite.test.js b/test/js/bun/sqlite/sqlite.test.js index 165ee2cc0b..0d57b650c6 100644 --- a/test/js/bun/sqlite/sqlite.test.js +++ b/test/js/bun/sqlite/sqlite.test.js @@ -401,7 +401,10 @@ it("db.transaction()", () => { ]); throw new Error("Should have thrown"); } catch (exception) { - expect(exception.message).toBe("constraint failed"); + expect(exception.message).toEqual("UNIQUE constraint failed: cats.name"); + expect(exception.code).toEqual("SQLITE_CONSTRAINT_UNIQUE"); + expect(exception.errno).toEqual(2067); + expect(exception.byteOffset).toEqual(-1); } expect(db.inTransaction).toBe(false); @@ -622,3 +625,30 @@ it("latin1 sqlite3 column name", () => { }, ]); }); + +it("syntax error sets the byteOffset", () => { + const db = new Database(":memory:"); + try { + db.query("SELECT * FROM foo!!").all(); + throw new Error("Expected error"); + } catch (error) { + if (process.platform === "darwin" && process.arch === "x64") { + if (error.byteOffset === -1) { + // older versions of macOS don't have the function which returns the byteOffset + // we internally use a polyfill, so we need to allow that. + return; + } + } + + expect(error.byteOffset).toBe(17); + } +}); + +it("Missing DB throws SQLITE_CANTOPEN", () => { + try { + new Database("/definitely/not/found"); + expect.unreachable(); + } catch (error) { + expect(error.code).toBe("SQLITE_CANTOPEN"); + } +});