#include "root.h" #include "JavaScriptCore/Error.h" #include "JavaScriptCore/JSBigInt.h" #include "JavaScriptCore/Structure.h" #include "JavaScriptCore/ThrowScope.h" #include "JavaScriptCore/JSArray.h" #include "JavaScriptCore/ExceptionScope.h" #include "JavaScriptCore/JSArrayBufferView.h" #include "JavaScriptCore/JSType.h" #include "JSSQLStatement.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "GCDefferalContext.h" #include #include "DOMJITIDLConvert.h" #include "DOMJITIDLType.h" #include "JSBuffer.h" #include "DOMJITIDLTypeFilter.h" #include "DOMJITHelpers.h" #include #include "wtf/SIMDUTF.h" #include #include "BunBuiltinNames.h" #include "sqlite3_error_codes.h" #include "wtf/BitVector.h" #include "wtf/FastBitVector.h" #include "wtf/Vector.h" #include #include "wtf/LazyRef.h" #include "wtf/text/StringToIntegerConversion.h" #include #include "BunString.h" static constexpr int32_t kSafeIntegersFlag = 1 << 1; static constexpr int32_t kStrictFlag = 1 << 2; #ifndef BREAKING_CHANGES_BUN_1_2 #define BREAKING_CHANGES_BUN_1_2 0 #endif /* ******************************************************************************** */ // Lazy Load SQLite on macOS // This seemed to be about 3% faster on macOS // but it might be noise // it's kind of hard to tell // it should be strictly better though because // instead of two pointers, one for DYLD_STUB$$ and one for the actual library // we only call one pointer for the actual library // and it means there's less work for DYLD to do on startup // i.e. it shouldn't have any impact on startup time #if LAZY_LOAD_SQLITE #include "lazy_sqlite3.h" #else static inline int lazyLoadSQLite() { return 0; } #endif /* ******************************************************************************** */ #if !USE(SYSTEM_MALLOC) #include #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"); static sqlite3_mem_methods fastMallocMethods = { [](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; }, [](void*) {}, nullptr }; returnCode = sqlite3_config(SQLITE_CONFIG_MALLOC, &fastMallocMethods); ASSERT_WITH_MESSAGE(returnCode == SQLITE_OK, "Unable to replace SQLite malloc"); #endif } class AutoDestructingSQLiteStatement { public: sqlite3_stmt* stmt { nullptr }; ~AutoDestructingSQLiteStatement() { sqlite3_finalize(stmt); } }; static void initializeSQLite() { static std::once_flag onceFlag; std::call_once(onceFlag, [] { enableFastMallocForSQLite(); }); } static WTF::String sqliteString(const char* str) { auto res = WTF::String::fromUTF8(str); sqlite3_free((void*)str); return res; } static void sqlite_free_typed_array(void* buf, void* ctx) { sqlite3_free((void*)buf); } static constexpr int DEFAULT_SQLITE_FLAGS = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE; static constexpr unsigned int DEFAULT_SQLITE_PREPARE_FLAGS = SQLITE_PREPARE_PERSISTENT; static constexpr int MAX_SQLITE_PREPARE_FLAG = SQLITE_PREPARE_PERSISTENT | SQLITE_PREPARE_NORMALIZE | SQLITE_PREPARE_NO_VTAB; static inline JSC::JSValue jsNumberFromSQLite(sqlite3_stmt* stmt, unsigned int i) { int64_t num = sqlite3_column_int64(stmt, i); return JSC::jsNumber(num); } static inline JSC::JSValue jsBigIntFromSQLite(JSC::JSGlobalObject* globalObject, sqlite3_stmt* stmt, unsigned int i) { int64_t num = sqlite3_column_int64(stmt, i); return JSC::JSBigInt::createFrom(globalObject, num); } #define CHECK_THIS \ if (!castedThis) [[unlikely]] { \ throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, "Expected SQLStatement"_s)); \ return {}; \ } #define DO_REBIND(param) \ if (param.isObject()) { \ JSC::JSValue reb = castedThis->rebind(lexicalGlobalObject, param, true, castedThis->version_db->db); \ RETURN_IF_EXCEPTION(scope, {}); \ if (!reb.isNumber()) [[unlikely]] { \ return JSValue::encode(reb); /* this means an error */ \ } \ } else { \ throwException(lexicalGlobalObject, scope, createTypeError(lexicalGlobalObject, "Expected object or array"_s)); \ return {}; \ } #define CHECK_PREPARED \ if (castedThis->stmt == nullptr || castedThis->version_db == nullptr) [[unlikely]] { \ throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, "Statement has finalized"_s)); \ return {}; \ } #define CHECK_PREPARED_JIT \ if (castedThis->stmt == nullptr || castedThis->version_db == nullptr) [[unlikely]] { \ throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, "Statement has finalized"_s)); \ return {}; \ } DECLARE_ALLOCATOR_WITH_HEAP_IDENTIFIER(VersionSqlite3); class VersionSqlite3 { WTF_DEPRECATED_MAKE_FAST_ALLOCATED_WITH_HEAP_IDENTIFIER(VersionSqlite3, VersionSqlite3); public: explicit VersionSqlite3(sqlite3* db) : db(db) , version(0) , reference_count(1) { } sqlite3* db; std::atomic version; size_t reference_count; void release() { ASSERT(reference_count > 0); --reference_count; if (reference_count == 0) { if (!db) { return; } sqlite3_close_v2(db); db = nullptr; } }; }; DEFINE_ALLOCATOR_WITH_HEAP_IDENTIFIER(VersionSqlite3); class SQLiteSingleton { public: Vector databases; Vector> schema_versions; }; static SQLiteSingleton* _instance = nullptr; static Vector& databases() { if (!_instance) { _instance = new SQLiteSingleton(); _instance->databases = Vector(); _instance->databases.reserveInitialCapacity(4); _instance->schema_versions = Vector>(); } return _instance->databases; } extern "C" void Bun__closeAllSQLiteDatabasesForTermination() { if (!_instance) { return; } auto& dbs = _instance->databases; for (auto& db : dbs) { if (db->db) sqlite3_close(db->db); } } namespace WebCore { using namespace JSC; JSC_DECLARE_HOST_FUNCTION(jsSQLStatementPrepareStatementFunction); JSC_DECLARE_HOST_FUNCTION(jsSQLStatementExecuteFunction); JSC_DECLARE_HOST_FUNCTION(jsSQLStatementOpenStatementFunction); JSC_DECLARE_HOST_FUNCTION(jsSQLStatementIsInTransactionFunction); JSC_DECLARE_HOST_FUNCTION(jsSQLStatementLoadExtensionFunction); JSC_DECLARE_HOST_FUNCTION(jsSQLStatementExecuteStatementFunction); JSC_DECLARE_HOST_FUNCTION(jsSQLStatementExecuteStatementFunctionRun); JSC_DECLARE_HOST_FUNCTION(jsSQLStatementExecuteStatementFunctionGet); JSC_DECLARE_HOST_FUNCTION(jsSQLStatementExecuteStatementFunctionAll); JSC_DECLARE_HOST_FUNCTION(jsSQLStatementExecuteStatementFunctionIterate); JSC_DECLARE_HOST_FUNCTION(jsSQLStatementExecuteStatementFunctionRows); JSC_DECLARE_HOST_FUNCTION(jsSQLStatementExecuteStatementFunctionRawRows); JSC_DECLARE_CUSTOM_GETTER(jsSqlStatementGetColumnNames); JSC_DECLARE_CUSTOM_GETTER(jsSqlStatementGetColumnCount); JSC_DECLARE_HOST_FUNCTION(jsSQLStatementSerialize); JSC_DECLARE_HOST_FUNCTION(jsSQLStatementDeserialize); JSC_DECLARE_HOST_FUNCTION(jsSQLStatementSetPrototypeFunction); JSC_DECLARE_HOST_FUNCTION(jsSQLStatementFunctionFinalize); JSC_DECLARE_HOST_FUNCTION(jsSQLStatementToStringFunction); JSC_DECLARE_CUSTOM_GETTER(jsSqlStatementGetColumnNames); JSC_DECLARE_CUSTOM_GETTER(jsSqlStatementGetColumnCount); JSC_DECLARE_CUSTOM_GETTER(jsSqlStatementGetParamCount); JSC_DECLARE_CUSTOM_GETTER(jsSqlStatementGetHasMultipleStatements); JSC_DECLARE_CUSTOM_GETTER(jsSqlStatementGetColumnTypes); JSC_DECLARE_CUSTOM_GETTER(jsSqlStatementGetColumnDeclaredTypes); JSC_DECLARE_CUSTOM_GETTER(jsSqlStatementGetSafeIntegers); JSC_DECLARE_CUSTOM_SETTER(jsSqlStatementSetSafeIntegers); static JSValue createSQLiteError(JSC::JSGlobalObject* globalObject, sqlite3* db) { auto& vm = JSC::getVM(globalObject); 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 SQLiteBindingsMap { public: SQLiteBindingsMap() = default; SQLiteBindingsMap(uint16_t count = 0, bool trimLeadingPrefix = false) { this->trimLeadingPrefix = trimLeadingPrefix; hasLoadedNames = false; reset(count); } void reset(uint16_t count = 0) { ASSERT(count <= std::numeric_limits::max()); if (this->count != count) { hasLoadedNames = false; bindingNames.clear(); } this->count = count; } void ensureNamesLoaded(JSC::VM& vm, sqlite3_stmt* stmt) { if (hasLoadedNames) return; hasLoadedNames = true; hasOutOfOrderNames = false; size_t count = this->count; size_t prefixOffset = trimLeadingPrefix ? 1 : 0; bindingNames.clear(); bool hasLoadedBindingNames = false; size_t indexedCount = 0; for (size_t i = 0; i < count; i++) { const unsigned char* name = reinterpret_cast(sqlite3_bind_parameter_name(stmt, i + 1)); // INSERT INTO cats (name, age) VALUES (?, ?) RETURNING name if (name == nullptr) { indexedCount++; if (hasLoadedBindingNames) { bindingNames[i] = Identifier(Identifier::EmptyIdentifierFlag::EmptyIdentifier); } continue; } if (!hasLoadedBindingNames) { bindingNames.resize(count); hasLoadedBindingNames = true; } name += prefixOffset; size_t namelen = strlen(reinterpret_cast(name)); if (prefixOffset == 1 && name[0] >= '0' && name[0] <= '9') { auto integer = WTF::parseInteger(StringView({ name, namelen }), 10); if (integer.has_value()) { hasOutOfOrderNames = true; bindingNames.clear(); break; } } WTF::String wtfString = WTF::String::fromUTF8ReplacingInvalidSequences({ name, namelen }); bindingNames[i] = Identifier::fromString(vm, wtfString); } isOnlyIndexed = indexedCount == count; } Vector bindingNames; uint16_t count = 0; bool hasLoadedNames : 1 = false; bool isOnlyIndexed : 1 = false; bool trimLeadingPrefix : 1 = false; bool hasOutOfOrderNames : 1 = false; }; class JSSQLStatement : public JSC::JSDestructibleObject { public: using Base = JSC::JSDestructibleObject; static constexpr JSC::DestructionMode needsDestruction = NeedsDestruction; static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype) { return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::ObjectType, StructureFlags), info()); } static JSSQLStatement* create(JSDOMGlobalObject* globalObject, sqlite3_stmt* stmt, VersionSqlite3* version_db, int64_t memorySizeChange = 0) { Structure* structure = globalObject->JSSQLStatementStructure(); JSSQLStatement* ptr = new (NotNull, JSC::allocateCell(globalObject->vm())) JSSQLStatement(structure, *globalObject, stmt, version_db, memorySizeChange); if (version_db) { ++version_db->reference_count; } ptr->finishCreation(globalObject->vm()); return ptr; } static void destroy(JSC::JSCell*); template static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) { return WebCore::subspaceForImpl( vm, [](auto& spaces) { return spaces.m_clientSubspaceForJSSQLStatement.get(); }, [](auto& spaces, auto&& space) { spaces.m_clientSubspaceForJSSQLStatement = std::forward(space); }, [](auto& spaces) { return spaces.m_subspaceForJSSQLStatement.get(); }, [](auto& spaces, auto&& space) { spaces.m_subspaceForJSSQLStatement = std::forward(space); }); } DECLARE_VISIT_CHILDREN; DECLARE_EXPORT_INFO; template void visitAdditionalChildren(Visitor&); template static void visitOutputConstraints(JSCell*, Visitor&); size_t static estimatedSize(JSCell* cell, VM& vm) { auto* thisObject = jsCast(cell); return Base::estimatedSize(thisObject, vm) + thisObject->extraMemorySize; } 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(); } ~JSSQLStatement(); sqlite3_stmt* stmt; VersionSqlite3* version_db; uint64_t version = 0; // Tracks which columns are valid in the current result set. Used to handle duplicate column names. // The bit at index i is set if the column at index i is valid. WTF::BitVector validColumns; std::unique_ptr columnNames; mutable JSC::WriteBarrier _prototype; mutable JSC::WriteBarrier _structure; mutable JSC::WriteBarrier userPrototype; size_t extraMemorySize = 0; SQLiteBindingsMap m_bindingNames = { 0, false }; bool hasExecuted : 1 = false; bool useBigInt64 : 1 = false; protected: JSSQLStatement(JSC::Structure* structure, JSDOMGlobalObject& globalObject, sqlite3_stmt* stmt, VersionSqlite3* version_db, int64_t memorySizeChange = 0) : Base(globalObject.vm(), structure) , stmt(stmt) , version_db(version_db) , columnNames(new PropertyNameArrayBuilder(globalObject.vm(), PropertyNameMode::Strings, PrivateSymbolMode::Exclude)) , extraMemorySize(memorySizeChange > 0 ? memorySizeChange : 0) { } void finishCreation(JSC::VM& vm); }; static JSValue toJSAsBuffer(JSC::VM& vm, JSC::JSGlobalObject* globalObject, sqlite3_stmt* stmt, int i) { auto scope = DECLARE_THROW_SCOPE(vm); switch (sqlite3_column_type(stmt, i)) { case SQLITE_INTEGER: { int64_t value = sqlite3_column_int64(stmt, i); JSC::JSUint8Array* array = JSC::JSUint8Array::createUninitialized(globalObject, globalObject->m_typedArrayUint8.get(globalObject), 8); RETURN_IF_EXCEPTION(scope, {}); uint8_t* data = array->typedVector(); for (int j = 0; j < 8; j++) { data[j] = (value >> (j * 8)) & 0xFF; } return array; } case SQLITE_FLOAT: { double value = sqlite3_column_double(stmt, i); JSC::JSUint8Array* array = JSC::JSUint8Array::createUninitialized(globalObject, globalObject->m_typedArrayUint8.get(globalObject), 8); RETURN_IF_EXCEPTION(scope, {}); memcpy(array->typedVector(), &value, 8); return array; } case SQLITE3_TEXT: { size_t len = sqlite3_column_bytes(stmt, i); const unsigned char* text = len > 0 ? sqlite3_column_text(stmt, i) : nullptr; if (text == nullptr || len == 0) [[unlikely]] { JSC::JSUint8Array* array = JSC::JSUint8Array::createUninitialized(globalObject, globalObject->m_typedArrayUint8.get(globalObject), 0); RETURN_IF_EXCEPTION(scope, {}); return array; } JSC::JSUint8Array* array = JSC::JSUint8Array::createUninitialized(globalObject, globalObject->m_typedArrayUint8.get(globalObject), len); RETURN_IF_EXCEPTION(scope, {}); memcpy(array->typedVector(), text, len); return array; } case SQLITE_BLOB: { size_t len = sqlite3_column_bytes(stmt, i); const void* blob = len > 0 ? sqlite3_column_blob(stmt, i) : nullptr; if (len > 0 && blob != nullptr) [[likely]] { JSC::JSUint8Array* array = JSC::JSUint8Array::createUninitialized(globalObject, globalObject->m_typedArrayUint8.get(globalObject), len); RETURN_IF_EXCEPTION(scope, {}); memcpy(array->vector(), blob, len); return array; } JSC::JSUint8Array* array = JSC::JSUint8Array::createUninitialized(globalObject, globalObject->m_typedArrayUint8.get(globalObject), 0); RETURN_IF_EXCEPTION(scope, {}); return array; } case SQLITE_NULL: default: return jsNull(); } } template static JSValue toJS(JSC::VM& vm, JSC::JSGlobalObject* globalObject, sqlite3_stmt* stmt, int i) { auto throwScope = DECLARE_THROW_SCOPE(vm); switch (sqlite3_column_type(stmt, i)) { case SQLITE_INTEGER: { if constexpr (!useBigInt64) { // https://github.com/oven-sh/bun/issues/1536 return jsNumberFromSQLite(stmt, i); } else { // https://github.com/oven-sh/bun/issues/1536 auto bint = jsBigIntFromSQLite(globalObject, stmt, i); RETURN_IF_EXCEPTION(throwScope, {}); return bint; } } case SQLITE_FLOAT: { return jsNumber(sqlite3_column_double(stmt, i)); } // > Note that the SQLITE_TEXT constant was also used in SQLite version // > 2 for a completely different meaning. Software that links against // > both SQLite version 2 and SQLite version 3 should use SQLITE3_TEXT, // > not SQLITE_TEXT. case SQLITE3_TEXT: { size_t len = sqlite3_column_bytes(stmt, i); const unsigned char* text = len > 0 ? sqlite3_column_text(stmt, i) : nullptr; if (text == nullptr || len == 0) [[unlikely]] { return jsEmptyString(vm); } if (len < 64) { return jsString(vm, WTF::String::fromUTF8({ text, len })); } auto encoded = Bun__encoding__toStringUTF8(text, len, globalObject); RETURN_IF_EXCEPTION(throwScope, {}); return JSC::JSValue::decode(encoded); } case SQLITE_BLOB: { size_t len = sqlite3_column_bytes(stmt, i); const void* blob = len > 0 ? sqlite3_column_blob(stmt, i) : nullptr; if (len > 0 && blob != nullptr) [[likely]] { JSC::JSUint8Array* array = JSC::JSUint8Array::createUninitialized(globalObject, globalObject->m_typedArrayUint8.get(globalObject), len); RETURN_IF_EXCEPTION(throwScope, {}); memcpy(array->vector(), blob, len); return array; } auto array = JSC::JSUint8Array::create(globalObject, globalObject->m_typedArrayUint8.get(globalObject), 0); RETURN_IF_EXCEPTION(throwScope, {}); return array; } default: { break; } } return jsNull(); } static const HashTableValue JSSQLStatementPrototypeTableValues[] = { { "run"_s, static_cast(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsSQLStatementExecuteStatementFunctionRun, 1 } }, { "get"_s, static_cast(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsSQLStatementExecuteStatementFunctionGet, 1 } }, { "all"_s, static_cast(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsSQLStatementExecuteStatementFunctionAll, 1 } }, { "iterate"_s, static_cast(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsSQLStatementExecuteStatementFunctionIterate, 1 } }, { "as"_s, static_cast(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsSQLStatementSetPrototypeFunction, 1 } }, { "values"_s, static_cast(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsSQLStatementExecuteStatementFunctionRows, 1 } }, { "raw"_s, static_cast(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsSQLStatementExecuteStatementFunctionRawRows, 1 } }, { "finalize"_s, static_cast(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsSQLStatementFunctionFinalize, 0 } }, { "toString"_s, static_cast(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsSQLStatementToStringFunction, 0 } }, { "columns"_s, static_cast(JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, jsSqlStatementGetColumnNames, 0 } }, { "columnsCount"_s, static_cast(JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, jsSqlStatementGetColumnCount, 0 } }, { "paramsCount"_s, static_cast(JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, jsSqlStatementGetParamCount, 0 } }, { "columnTypes"_s, static_cast(JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, jsSqlStatementGetColumnTypes, 0 } }, { "declaredTypes"_s, static_cast(JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, jsSqlStatementGetColumnDeclaredTypes, 0 } }, { "safeIntegers"_s, static_cast(JSC::PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, jsSqlStatementGetSafeIntegers, jsSqlStatementSetSafeIntegers } }, }; class JSSQLStatementPrototype final : public JSC::JSNonFinalObject { public: using Base = JSC::JSNonFinalObject; static JSSQLStatementPrototype* create(JSC::VM& vm, JSGlobalObject* globalObject, JSC::Structure* structure) { JSSQLStatementPrototype* ptr = new (NotNull, JSC::allocateCell(vm)) JSSQLStatementPrototype(vm, globalObject, structure); ptr->finishCreation(vm, globalObject); return ptr; } DECLARE_INFO; template static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) { STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(JSSQLStatementPrototype, Base); return &vm.plainObjectSpace(); } static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype) { return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::ObjectType, StructureFlags), info()); } private: JSSQLStatementPrototype(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure) : Base(vm, structure) { } void finishCreation(JSC::VM& vm, JSC::JSGlobalObject* globalObject) { Base::finishCreation(vm); reifyStaticProperties(vm, JSSQLStatementPrototype::info(), JSSQLStatementPrototypeTableValues, *this); } }; const ClassInfo JSSQLStatementPrototype::s_info = { "SQLStatement"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSSQLStatementPrototype) }; 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); } static void initializeColumnNames(JSC::JSGlobalObject* lexicalGlobalObject, JSSQLStatement* castedThis) { if (!castedThis->hasExecuted) { castedThis->hasExecuted = true; } else { // reinitialize column castedThis->columnNames.reset(new PropertyNameArrayBuilder( castedThis->columnNames->vm(), castedThis->columnNames->propertyNameMode(), castedThis->columnNames->privateSymbolMode())); } castedThis->validColumns.clearAll(); castedThis->update_version(); auto& vm = JSC::getVM(lexicalGlobalObject); auto scope = DECLARE_THROW_SCOPE(vm); auto* stmt = castedThis->stmt; castedThis->_structure.clear(); castedThis->_prototype.clear(); int count = sqlite3_column_count(stmt); if (count < 1) return; // Fast path: if (count <= JSFinalObject::maxInlineCapacity) { // 64 is the maximum we can preallocate here // see https://github.com/oven-sh/bun/issues/987 // also see https://github.com/oven-sh/bun/issues/1646 auto& globalObject = *lexicalGlobalObject; auto columnNames = castedThis->columnNames.get(); bool anyHoles = false; for (int i = count - 1; i >= 0; i--) { const char* name = sqlite3_column_name(stmt, i); if (name == nullptr) { anyHoles = true; break; } size_t len = strlen(name); if (len == 0) { anyHoles = true; break; } // When joining multiple tables, the same column names can appear multiple times // columnNames de-dupes property names internally // We can't have two properties with the same name, so we use validColumns to track this. auto preCount = columnNames->size(); columnNames->add( Identifier::fromString(vm, WTF::String::fromUTF8({ name, len }))); auto curCount = columnNames->size(); if (preCount != curCount) { castedThis->validColumns.set(i); } } if (!anyHoles) [[likely]] { PropertyOffset offset; JSObject* prototype = castedThis->userPrototype ? castedThis->userPrototype.get() : globalObject.objectPrototype(); Structure* structure = globalObject.structureCache().emptyObjectStructureForPrototype(&globalObject, prototype, columnNames->size()); vm.writeBarrier(castedThis, structure); // We iterated over the columns in reverse order so we need to reverse the columnNames here // Importantly we reverse before adding the properties to the structure to ensure that index accesses // later refer to the correct property. columnNames->data()->propertyNameVector().reverse(); for (const auto& propertyName : *columnNames) { structure = Structure::addPropertyTransition(vm, structure, propertyName, 0, offset); } castedThis->_structure.set(vm, castedThis, structure); // We are done. return; } else { // If for any reason we do not have column names, disable the fast path. columnNames->releaseData(); castedThis->columnNames.reset(new PropertyNameArrayBuilder( castedThis->columnNames->vm(), castedThis->columnNames->propertyNameMode(), castedThis->columnNames->privateSymbolMode())); castedThis->validColumns.clearAll(); } } // Slow path: // 64 is the maximum we can preallocate here // see https://github.com/oven-sh/bun/issues/987 JSObject* prototype = castedThis->userPrototype ? castedThis->userPrototype.get() : lexicalGlobalObject->objectPrototype(); JSC::JSObject* object = JSC::constructEmptyObject(lexicalGlobalObject, prototype, std::min(static_cast(count), JSFinalObject::maxInlineCapacity)); for (int i = count - 1; i >= 0; i--) { const char* name = sqlite3_column_name(stmt, i); if (name == nullptr) break; size_t len = strlen(name); if (len == 0) break; const auto key = Identifier::fromString(vm, WTF::String::fromUTF8({ name, len })); JSC::JSValue primitive = JSC::jsUndefined(); auto decl = sqlite3_column_decltype(stmt, i); if (decl != nullptr) { switch (decl[0]) { case 'F': case 'D': case 'I': { primitive = jsNumber(0); break; } case 'V': case 'T': { primitive = jsEmptyString(vm); break; } } } auto preCount = castedThis->columnNames->size(); castedThis->columnNames->add(key); auto curCount = castedThis->columnNames->size(); // only put the property if it's not a duplicate if (preCount != curCount) { castedThis->validColumns.set(i); object->putDirect(vm, key, primitive, 0); } } // We iterated over the columns in reverse order so we need to reverse the columnNames here castedThis->columnNames->data()->propertyNameVector().reverse(); castedThis->_prototype.set(vm, castedThis, object); } void JSSQLStatement::destroy(JSC::JSCell* cell) { JSSQLStatement* thisObject = static_cast(cell); thisObject->~JSSQLStatement(); } static inline bool rebindValue(JSC::JSGlobalObject* lexicalGlobalObject, sqlite3* db, sqlite3_stmt* stmt, int i, JSC::JSValue value, JSC::ThrowScope& scope, bool clone, bool isSafeInteger) { auto throwSQLiteError = [&]() -> void { throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, WTF::String::fromUTF8(sqlite3_errmsg(db)))); }; #define CHECK_BIND(param) \ int result = param; \ if (result != SQLITE_OK) [[unlikely]] { \ 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? // we can't use it if there's no pointer to ref/unref auto transientOrStatic = (void (*)(void*))(clone ? SQLITE_TRANSIENT : SQLITE_STATIC); if (value.isUndefinedOrNull()) { CHECK_BIND(sqlite3_bind_null(stmt, i)); } else if (value.isBoolean()) { CHECK_BIND(sqlite3_bind_int(stmt, i, value.toBoolean(lexicalGlobalObject) ? 1 : 0)); } else if (value.isAnyInt()) { int64_t val = value.asAnyInt(); if (val < INT_MIN || val > INT_MAX) { CHECK_BIND(sqlite3_bind_int64(stmt, i, val)); } else { CHECK_BIND(sqlite3_bind_int(stmt, i, val)) } } else if (value.isNumber()) { CHECK_BIND(sqlite3_bind_double(stmt, i, value.asDouble())) } else if (value.isString()) { auto* str = value.toStringOrNull(lexicalGlobalObject); if (!str) [[unlikely]] { throwException(lexicalGlobalObject, scope, createTypeError(lexicalGlobalObject, "Expected string"_s)); return false; } const auto roped = str->view(lexicalGlobalObject); if (roped->isNull()) [[unlikely]] { throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, "Out of memory :("_s)); return false; } if (roped->is8Bit() && roped->containsOnlyASCII()) { CHECK_BIND(sqlite3_bind_text(stmt, i, reinterpret_cast(roped->span8().data()), roped->length(), transientOrStatic)); } else if (!roped->is8Bit()) { CHECK_BIND(sqlite3_bind_text16(stmt, i, roped->span16().data(), roped->length() * 2, transientOrStatic)); } else { auto utf8 = roped->utf8(); CHECK_BIND(sqlite3_bind_text(stmt, i, utf8.data(), utf8.length(), SQLITE_TRANSIENT)); } } else if (value.isHeapBigInt()) [[unlikely]] { if (!isSafeInteger) { CHECK_BIND(sqlite3_bind_int64(stmt, i, JSBigInt::toBigInt64(value))); } else { JSBigInt* bigInt = value.asHeapBigInt(); const auto min = JSBigInt::compare(bigInt, std::numeric_limits::min()); const auto max = JSBigInt::compare(bigInt, std::numeric_limits::max()); if ((min == JSBigInt::ComparisonResult::GreaterThan || min == JSBigInt::ComparisonResult::Equal) && (max == JSBigInt::ComparisonResult::LessThan || max == JSBigInt::ComparisonResult::Equal)) [[likely]] { CHECK_BIND(sqlite3_bind_int64(stmt, i, JSBigInt::toBigInt64(value))); } else { throwRangeError(lexicalGlobalObject, scope, makeString("BigInt value '"_s, bigInt->toString(lexicalGlobalObject, 10), "' is out of range"_s)); sqlite3_clear_bindings(stmt); return false; } } } else if (JSC::JSArrayBufferView* buffer = JSC::jsDynamicCast(value)) { CHECK_BIND(sqlite3_bind_blob(stmt, i, buffer->vector(), buffer->byteLength(), transientOrStatic)); } else { throwException(lexicalGlobalObject, scope, createTypeError(lexicalGlobalObject, "Binding expected string, TypedArray, boolean, number, bigint or null"_s)); return false; } return true; #undef CHECK_BIND } static JSC::JSValue rebindObject(JSC::JSGlobalObject* globalObject, SQLiteBindingsMap& bindings, JSC::JSObject* target, JSC::ThrowScope& scope, sqlite3* db, sqlite3_stmt* stmt, bool clone, bool safeIntegers) { int count = 0; auto& vm = JSC::getVM(globalObject); auto& structure = *target->structure(); bindings.ensureNamesLoaded(vm, stmt); const auto& bindingNames = bindings.bindingNames; size_t size = bindings.count; const bool trimLeadingPrefix = bindings.trimLeadingPrefix; const bool throwOnMissing = trimLeadingPrefix; // Did they reorder the columns? // // { ?2: "foo", ?1: "bar" } // if (bindings.hasOutOfOrderNames) [[unlikely]] { const auto& getValue = [&](const char* name, size_t i) -> JSValue { JSValue value = {}; if (name == nullptr) { return target->getDirectIndex(globalObject, i); } if (trimLeadingPrefix) { name += 1; } const WTF::String str = WTF::String::fromUTF8ReplacingInvalidSequences({ reinterpret_cast(name), strlen(name) }); if (trimLeadingPrefix && name[0] >= '0' && name[0] <= '9') { auto integer = WTF::parseInteger(str, 10); if (integer.has_value()) { return target->getDirectIndex(globalObject, integer.value() - 1); } } const auto identifier = Identifier::fromString(vm, str); PropertySlot slot(target, PropertySlot::InternalMethodType::GetOwnProperty); if (!target->getOwnNonIndexPropertySlot(vm, &structure, identifier, slot)) { return {}; } if (!slot.isTaintedByOpaqueObject()) [[likely]] { return slot.getValue(globalObject, identifier); } return target->get(globalObject, identifier); }; for (size_t i = 0; i < size; i++) { auto* name = sqlite3_bind_parameter_name(stmt, i + 1); JSValue value = getValue(name, i); if (!value && !scope.exception()) { if (throwOnMissing) { throwException(globalObject, scope, createError(globalObject, makeString("Missing parameter \""_s, WTF::String::fromUTF8ReplacingInvalidSequences({ reinterpret_cast(name), strlen(name) }), "\""_s))); } else { continue; } } RETURN_IF_EXCEPTION(scope, {}); if (!rebindValue(globalObject, db, stmt, i + 1, value, scope, clone, safeIntegers)) { return {}; } RETURN_IF_EXCEPTION(scope, {}); count++; } } // Does it only contain indexed properties? // // { 0: "foo", 1: "bar", "2": "baz" } // else if (bindings.isOnlyIndexed) [[unlikely]] { for (size_t i = 0; i < size; i++) { JSValue value = target->getDirectIndex(globalObject, i); if (!value && !scope.exception()) { if (throwOnMissing) { throwException(globalObject, scope, createError(globalObject, makeString("Missing parameter \""_s, i + 1, "\""_s))); } else { continue; } } RETURN_IF_EXCEPTION(scope, {}); if (!rebindValue(globalObject, db, stmt, i + 1, value, scope, clone, safeIntegers)) { return {}; } RETURN_IF_EXCEPTION(scope, {}); count++; } } // Is it a simple object with no getters or setters? // // { foo: "bar", baz: "qux" } // else if (target->canUseFastGetOwnProperty(structure)) { for (size_t i = 0; i < size; i++) { const auto& property = bindingNames[i]; JSValue value = property.isEmpty() ? target->getDirectIndex(globalObject, i) : target->fastGetOwnProperty(vm, structure, bindingNames[i]); if (!value && !scope.exception()) { if (throwOnMissing) { throwException(globalObject, scope, createError(globalObject, makeString("Missing parameter \""_s, property.isEmpty() ? String::number(i) : property.string(), "\""_s))); } else { continue; } } RETURN_IF_EXCEPTION(scope, {}); if (!rebindValue(globalObject, db, stmt, i + 1, value, scope, clone, safeIntegers)) { return {}; } RETURN_IF_EXCEPTION(scope, {}); count++; } } else { for (size_t i = 0; i < size; i++) { PropertySlot slot(target, PropertySlot::InternalMethodType::GetOwnProperty); const auto& property = bindingNames[i]; bool hasProperty = property.isEmpty() ? target->methodTable()->getOwnPropertySlotByIndex(target, globalObject, i, slot) : target->methodTable()->getOwnPropertySlot(target, globalObject, property, slot); if (!hasProperty && !scope.exception()) { if (throwOnMissing) { throwException(globalObject, scope, createError(globalObject, makeString("Missing parameter \""_s, property.isEmpty() ? String::number(i) : property.string(), "\""_s))); } else { continue; } } RETURN_IF_EXCEPTION(scope, {}); JSValue value; if (!slot.isTaintedByOpaqueObject()) [[likely]] value = slot.getValue(globalObject, property); else { value = target->get(globalObject, property); RETURN_IF_EXCEPTION(scope, {}); } RETURN_IF_EXCEPTION(scope, {}); if (!rebindValue(globalObject, db, stmt, i + 1, value, scope, clone, safeIntegers)) { return {}; } RETURN_IF_EXCEPTION(scope, {}); count++; } } return jsNumber(count); } static JSC::JSValue rebindStatement(JSC::JSGlobalObject* lexicalGlobalObject, JSC::JSValue values, JSC::ThrowScope& scope, sqlite3* db, sqlite3_stmt* stmt, bool clone, SQLiteBindingsMap& bindings, bool safeIntegers) { sqlite3_clear_bindings(stmt); JSC::JSArray* array = jsDynamicCast(values); bindings.reset(sqlite3_bind_parameter_count(stmt)); if (!array) { if (JSC::JSObject* object = values.getObject()) { auto res = rebindObject(lexicalGlobalObject, bindings, object, scope, db, stmt, clone, safeIntegers); RETURN_IF_EXCEPTION(scope, {}); return res; } throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, "Expected array"_s)); return {}; } int count = array->length(); if (count == 0) { return jsNumber(0); } int required = bindings.count; if (count != required) { throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, makeString("SQLite query expected "_s, required, " values, received "_s, count))); return {}; } int i = 0; for (; i < count; i++) { JSC::JSValue value = array->getIndexQuickly(i); if (!rebindValue(lexicalGlobalObject, db, stmt, i + 1, value, scope, clone, safeIntegers)) { return {}; } RETURN_IF_EXCEPTION(scope, {}); } return jsNumber(i); } JSC_DEFINE_HOST_FUNCTION(jsSQLStatementSetCustomSQLite, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::CallFrame* callFrame)) { auto& vm = JSC::getVM(lexicalGlobalObject); auto scope = DECLARE_THROW_SCOPE(vm); JSValue thisValue = callFrame->thisValue(); JSSQLStatementConstructor* thisObject = jsDynamicCast(thisValue.getObject()); if (!thisObject) [[unlikely]] { throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, "Expected SQL"_s)); return {}; } if (callFrame->argumentCount() < 1) { throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, "Expected 1 argument"_s)); return {}; } JSC::JSValue sqliteStrValue = callFrame->argument(0); if (!sqliteStrValue.isString()) [[unlikely]] { throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, "Expected SQLite path"_s)); return {}; } #if LAZY_LOAD_SQLITE if (sqlite3_handle) { throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, "SQLite already loaded\nThis function can only be called before SQLite has been loaded and exactly once. SQLite auto-loads when the first time you open a Database."_s)); return {}; } // Use a static CString to keep the string alive for the lifetime of the process static CString sqlite3_lib_path_storage; sqlite3_lib_path_storage = sqliteStrValue.toWTFString(lexicalGlobalObject).utf8(); RETURN_IF_EXCEPTION(scope, {}); sqlite3_lib_path = sqlite3_lib_path_storage.data(); if (lazyLoadSQLite() == -1) { sqlite3_handle = nullptr; WTF::String msg = WTF::String::fromUTF8(dlerror()); throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, msg)); return {}; } #endif initializeSQLite(); RELEASE_AND_RETURN(scope, JSValue::encode(JSC::jsBoolean(true))); } JSC_DEFINE_HOST_FUNCTION(jsSQLStatementDeserialize, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::CallFrame* callFrame)) { auto& vm = JSC::getVM(lexicalGlobalObject); auto scope = DECLARE_THROW_SCOPE(vm); JSValue thisValue = callFrame->thisValue(); JSSQLStatementConstructor* thisObject = jsDynamicCast(thisValue.getObject()); JSC::JSArrayBufferView* array = jsDynamicCast(callFrame->argument(0)); unsigned int deserializeFlags = SQLITE_DESERIALIZE_FREEONCLOSE | SQLITE_DESERIALIZE_RESIZEABLE; unsigned int openFlags = DEFAULT_SQLITE_FLAGS; JSC::EnsureStillAliveScope ensureAliveArray(array); if (callFrame->argumentCount() > 1) { openFlags |= callFrame->argument(1).toInt32(lexicalGlobalObject); RETURN_IF_EXCEPTION(scope, {}); } if (callFrame->argumentCount() > 2) { deserializeFlags |= callFrame->argument(2).toInt32(lexicalGlobalObject); RETURN_IF_EXCEPTION(scope, {}); } if (!thisObject) [[unlikely]] { throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, "Expected SQL"_s)); return {}; } if (callFrame->argumentCount() < 1) { throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, "Expected 1 argument"_s)); return {}; } if (!array) [[unlikely]] { throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, "Expected Uint8Array or Buffer"_s)); return {}; } if (array->isDetached()) [[unlikely]] { throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, "TypedArray is detached"_s)); return {}; } #if LAZY_LOAD_SQLITE if (lazyLoadSQLite() < 0) [[unlikely]] { WTF::String msg = WTF::String::fromUTF8(dlerror()); throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, msg)); return {}; } #endif initializeSQLite(); size_t byteLength = array->byteLength(); void* ptr = array->vector(); if (ptr == nullptr || byteLength == 0) [[unlikely]] { throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, "ArrayBuffer must not be empty"_s)); return {}; } void* data = sqlite3_malloc64(byteLength); if (data == nullptr) [[unlikely]] { throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, "Failed to allocate memory"_s)); return {}; } if (byteLength) { memcpy(data, ptr, byteLength); } sqlite3* db = nullptr; if (sqlite3_open_v2(":memory:", &db, openFlags, nullptr) != SQLITE_OK) { throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, "Failed to open SQLite"_s)); return {}; } int status = sqlite3_db_config(db, SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION, 1, NULL); if (status != SQLITE_OK) { // TODO: log a warning here that we can't load extensions } status = sqlite3_db_config(db, SQLITE_DBCONFIG_DEFENSIVE, 1, NULL); if (status != SQLITE_OK) { // TODO: log a warning here that defensive mode is not enabled } status = sqlite3_deserialize(db, "main", reinterpret_cast(data), byteLength, byteLength, deserializeFlags); if (status == SQLITE_BUSY) { sqlite3_free(data); throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, "SQLITE_BUSY"_s)); return {}; } if (status != SQLITE_OK) { sqlite3_free(data); throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, status == SQLITE_ERROR ? "unable to deserialize database"_s : sqliteString(sqlite3_errstr(status)))); return {}; } auto count = databases().size(); databases().append(new VersionSqlite3(db)); RELEASE_AND_RETURN(scope, JSValue::encode(jsNumber(count))); } JSC_DEFINE_HOST_FUNCTION(jsSQLStatementSerialize, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::CallFrame* callFrame)) { auto& vm = JSC::getVM(lexicalGlobalObject); auto scope = DECLARE_THROW_SCOPE(vm); JSValue thisValue = callFrame->thisValue(); JSSQLStatementConstructor* thisObject = jsDynamicCast(thisValue.getObject()); if (!thisObject) [[unlikely]] { throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, "Expected SQL"_s)); return {}; } int32_t dbIndex = callFrame->argument(0).toInt32(lexicalGlobalObject); if (dbIndex < 0 || dbIndex >= databases().size()) [[unlikely]] { throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, "Invalid database handle"_s)); return {}; } sqlite3* db = databases()[dbIndex]->db; if (!db) [[unlikely]] { throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, "Can't do this on a closed database"_s)); return {}; } WTF::String attachedName = callFrame->argument(1).toWTFString(lexicalGlobalObject); RETURN_IF_EXCEPTION(scope, {}); if (attachedName.isEmpty()) { throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, "Expected attached database name"_s)); return {}; } sqlite3_int64 length = -1; unsigned char* data = sqlite3_serialize(db, attachedName.utf8().data(), &length, 0); if (data == nullptr && length) [[unlikely]] { throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, "Out of memory"_s)); return {}; } RELEASE_AND_RETURN(scope, JSBuffer__bufferFromPointerAndLengthAndDeinit(lexicalGlobalObject, reinterpret_cast(data), static_cast(length), NULL, sqlite_free_typed_array)); } JSC_DEFINE_HOST_FUNCTION(jsSQLStatementLoadExtensionFunction, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::CallFrame* callFrame)) { auto& vm = JSC::getVM(lexicalGlobalObject); auto scope = DECLARE_THROW_SCOPE(vm); JSValue thisValue = callFrame->thisValue(); JSSQLStatementConstructor* thisObject = jsDynamicCast(thisValue.getObject()); if (!thisObject) [[unlikely]] { throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, "Expected SQL"_s)); return {}; } int32_t dbIndex = callFrame->argument(0).toInt32(lexicalGlobalObject); if (dbIndex < 0 || dbIndex >= databases().size()) [[unlikely]] { throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, "Invalid database handle"_s)); return {}; } JSC::JSValue extension = callFrame->argument(1); if (!extension.isString()) [[unlikely]] { throwException(lexicalGlobalObject, scope, createTypeError(lexicalGlobalObject, "Expected string"_s)); return {}; } auto extensionString = extension.toWTFString(lexicalGlobalObject); RETURN_IF_EXCEPTION(scope, {}); sqlite3* db = databases()[dbIndex]->db; if (!db) [[unlikely]] { throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, "Can't do this on a closed database"_s)); return {}; } if (sqlite3_compileoption_used("SQLITE_OMIT_LOAD_EXTENSION")) { throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, "This build of sqlite3 does not support dynamic extension loading"_s)); return {}; } auto entryPointStr = callFrame->argumentCount() > 2 && callFrame->argument(2).isString() ? callFrame->argument(2).toWTFString(lexicalGlobalObject) : String(); RETURN_IF_EXCEPTION(scope, {}); auto entryPointUtf8 = entryPointStr.utf8(); const char* entryPoint = entryPointStr.length() == 0 ? NULL : entryPointUtf8.data(); auto extensionStringUtf8 = extensionString.utf8(); char* error; int rc = sqlite3_load_extension(db, extensionStringUtf8.data(), entryPoint, &error); // TODO: can we disable loading extensions after this? if (rc != SQLITE_OK) { throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, error ? sqliteString(error) : String::fromUTF8(sqlite3_errmsg(db)))); return {}; } RELEASE_AND_RETURN(scope, JSValue::encode(JSC::jsUndefined())); } static bool isSkippedInSQLiteQuery(const char c) { return c == ' ' || c == ';' || (c >= '\t' && c <= '\r'); } // This runs a query one-off // without the overhead of a long-lived statement object // does not return anything JSC_DEFINE_HOST_FUNCTION(jsSQLStatementExecuteFunction, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::CallFrame* callFrame)) { auto& vm = JSC::getVM(lexicalGlobalObject); auto scope = DECLARE_THROW_SCOPE(vm); JSValue thisValue = callFrame->thisValue(); JSSQLStatementConstructor* thisObject = jsDynamicCast(thisValue.getObject()); if (!thisObject) [[unlikely]] { throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, "Expected SQL"_s)); return {}; } if (callFrame->argumentCount() < 2) { throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, "Expected at least 2 arguments"_s)); return {}; } int32_t handle = callFrame->argument(0).toInt32(lexicalGlobalObject); if (databases().size() < handle) { throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, "Invalid database handle"_s)); return {}; } sqlite3* db = databases()[handle]->db; if (!db) [[unlikely]] { throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, "Database has closed"_s)); return {}; } JSC::JSValue internalFlagsValue = callFrame->argument(1); JSC::JSValue diffValue = callFrame->argument(2); JSC::JSValue sqlValue = callFrame->argument(3); if (!sqlValue.isString()) [[unlikely]] { throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, "Expected SQL string"_s)); return {}; } EnsureStillAliveScope bindingsAliveScope = callFrame->argument(4); auto* jsSqlString = sqlValue.toString(lexicalGlobalObject); RETURN_IF_EXCEPTION(scope, {}); if (jsSqlString->length() == 0) [[unlikely]] { throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, "SQL string mustn't be blank"_s)); return {}; } Bun::UTF8View utf8 = Bun::UTF8View(jsSqlString->view(lexicalGlobalObject)); const char* sqlStringHead = utf8.span().data(); const char* end = utf8.span().data() + utf8.span().size(); bool didSetBindings = false; bool didExecuteAny = false; int rc = SQLITE_OK; #if ASSERT_ENABLED int maxSqlStringBytes = end - sqlStringHead; #endif bool strict = internalFlagsValue.isInt32() && (internalFlagsValue.asInt32() & kStrictFlag) != 0; bool safeIntegers = internalFlagsValue.isInt32() && (internalFlagsValue.asInt32() & kSafeIntegersFlag) != 0; const int total_changes_before = sqlite3_total_changes(db); while (sqlStringHead && sqlStringHead < end) { if (isSkippedInSQLiteQuery(*sqlStringHead)) [[unlikely]] { sqlStringHead++; while (sqlStringHead < end && isSkippedInSQLiteQuery(*sqlStringHead)) sqlStringHead++; } AutoDestructingSQLiteStatement sql; const char* tail = nullptr; // Bounds checks ASSERT(end >= sqlStringHead); ASSERT(end - sqlStringHead >= 0); ASSERT(end - sqlStringHead <= maxSqlStringBytes); rc = sqlite3_prepare_v3(db, sqlStringHead, end - sqlStringHead, 0, &sql.stmt, &tail); if (rc != SQLITE_OK) break; if (!sql.stmt) { // this happens for an empty statement sqlStringHead = tail; continue; } // First statement gets the bindings. if (!didSetBindings && !bindingsAliveScope.value().isUndefinedOrNull()) { if (bindingsAliveScope.value().isObject()) { int count = sqlite3_bind_parameter_count(sql.stmt); SQLiteBindingsMap bindings { static_cast(count > -1 ? count : 0), strict }; JSC::JSValue reb = rebindStatement(lexicalGlobalObject, bindingsAliveScope.value(), scope, db, sql.stmt, false, bindings, safeIntegers); RETURN_IF_EXCEPTION(scope, {}); if (!reb.isNumber()) [[unlikely]] { return JSValue::encode(reb); /* this means an error */ } } else { throwException(lexicalGlobalObject, scope, createTypeError(lexicalGlobalObject, "Expected bindings to be an object or array"_s)); return {}; } didSetBindings = true; } do { rc = sqlite3_step(sql.stmt); } while (rc == SQLITE_ROW); didExecuteAny = true; sqlStringHead = tail; } if (rc != SQLITE_OK && rc != SQLITE_DONE) [[unlikely]] { throwException(lexicalGlobalObject, scope, createSQLiteError(lexicalGlobalObject, db)); return {}; } if (!didExecuteAny) { throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, "Query contained no valid SQL statement; likely empty query."_s)); return {}; } if (auto* diff = JSC::jsDynamicCast(diffValue)) { const int total_changes_after = sqlite3_total_changes(db); int64_t last_insert_rowid = sqlite3_last_insert_rowid(db); diff->putInternalField(vm, 0, JSC::jsNumber(total_changes_after - total_changes_before)); if (safeIntegers) { auto* bigInt = JSBigInt::createFrom(lexicalGlobalObject, last_insert_rowid); RETURN_IF_EXCEPTION(scope, {}); diff->putInternalField(vm, 1, bigInt); } else { diff->putInternalField(vm, 1, JSC::jsNumber(last_insert_rowid)); } } RELEASE_AND_RETURN(scope, JSValue::encode(jsUndefined())); } JSC_DEFINE_HOST_FUNCTION(jsSQLStatementIsInTransactionFunction, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::CallFrame* callFrame)) { auto& vm = JSC::getVM(lexicalGlobalObject); auto scope = DECLARE_THROW_SCOPE(vm); JSValue thisValue = callFrame->thisValue(); JSSQLStatementConstructor* thisObject = jsDynamicCast(thisValue.getObject()); if (!thisObject) [[unlikely]] { throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, "Expected SQLStatement"_s)); return {}; } JSC::JSValue dbNumber = callFrame->argument(0); if (!dbNumber.isNumber()) { throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, "Invalid database handle"_s)); return {}; } int32_t handle = dbNumber.toInt32(lexicalGlobalObject); if (handle < 0 || handle > databases().size()) { throwException(lexicalGlobalObject, scope, createRangeError(lexicalGlobalObject, "Invalid database handle"_s)); return {}; } sqlite3* db = databases()[handle]->db; if (!db) [[unlikely]] { throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, "Database has closed"_s)); return {}; } RELEASE_AND_RETURN(scope, JSValue::encode(jsBoolean(!sqlite3_get_autocommit(db)))); } JSC_DEFINE_HOST_FUNCTION(jsSQLStatementPrepareStatementFunction, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::CallFrame* callFrame)) { auto& vm = JSC::getVM(lexicalGlobalObject); auto scope = DECLARE_THROW_SCOPE(vm); JSValue thisValue = callFrame->thisValue(); JSSQLStatementConstructor* thisObject = jsDynamicCast(thisValue.getObject()); if (!thisObject) [[unlikely]] { throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, "Expected SQLStatement"_s)); return {}; } JSC::JSValue dbNumber = callFrame->argument(0); JSC::JSValue sqlValue = callFrame->argument(1); JSC::JSValue bindings = callFrame->argument(2); JSC::JSValue prepareFlagsValue = callFrame->argument(3); JSC::JSValue internalFlagsValue = callFrame->argument(4); if (!dbNumber.isNumber() || !sqlValue.isString()) { throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, "SQLStatement requires a number and a string"_s)); return {}; } int32_t handle = dbNumber.toInt32(lexicalGlobalObject); if (handle < 0 || handle > databases().size()) { throwException(lexicalGlobalObject, scope, createRangeError(lexicalGlobalObject, "Invalid database handle"_s)); return {}; } sqlite3* db = databases()[handle]->db; if (!db) { throwException(lexicalGlobalObject, scope, createRangeError(lexicalGlobalObject, "Cannot use a closed database"_s)); return {}; } auto* jsSqlString = sqlValue.toString(lexicalGlobalObject); RETURN_IF_EXCEPTION(scope, {}); if (!jsSqlString->length()) { throwException(lexicalGlobalObject, scope, createRangeError(lexicalGlobalObject, "Invalid SQL statement"_s)); return {}; } auto sqlString = jsSqlString->view(lexicalGlobalObject); RETURN_IF_EXCEPTION(scope, {}); Bun::UTF8View utf8 = Bun::UTF8View(sqlString); unsigned int flags = DEFAULT_SQLITE_PREPARE_FLAGS; if (prepareFlagsValue.isNumber()) { int prepareFlags = prepareFlagsValue.toInt32(lexicalGlobalObject); if (prepareFlags < 0 || prepareFlags > MAX_SQLITE_PREPARE_FLAG) { throwException(lexicalGlobalObject, scope, createRangeError(lexicalGlobalObject, "Invalid prepare flags"_s)); return {}; } flags = static_cast(prepareFlags); } sqlite3_stmt* statement = nullptr; // This is inherently somewhat racy if using Worker // but that should be okay. int64_t currentMemoryUsage = sqlite_malloc_amount; int rc = SQLITE_OK; rc = sqlite3_prepare_v3(db, reinterpret_cast(utf8.span().data()), utf8.span().size(), flags, &statement, nullptr); if (rc != SQLITE_OK) { throwException(lexicalGlobalObject, scope, createSQLiteError(lexicalGlobalObject, db)); return {}; } int64_t memoryChange = sqlite_malloc_amount - currentMemoryUsage; JSSQLStatement* sqlStatement = JSSQLStatement::create( static_cast(lexicalGlobalObject), statement, databases()[handle], memoryChange); if (internalFlagsValue.isInt32()) { const int32_t internalFlags = internalFlagsValue.asInt32(); sqlStatement->m_bindingNames.trimLeadingPrefix = (internalFlags & kStrictFlag) != 0; sqlStatement->useBigInt64 = (internalFlags & kSafeIntegersFlag) != 0; } if (bindings.isObject()) { auto* castedThis = sqlStatement; DO_REBIND(bindings) } return JSValue::encode(JSValue(sqlStatement)); } JSSQLStatementConstructor* JSSQLStatementConstructor::create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure) { 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); return ptr; } JSC_DEFINE_HOST_FUNCTION(jsSQLStatementOpenStatementFunction, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::CallFrame* callFrame)) { auto& vm = JSC::getVM(lexicalGlobalObject); auto scope = DECLARE_THROW_SCOPE(vm); JSValue thisValue = callFrame->thisValue(); JSSQLStatementConstructor* constructor = jsDynamicCast(thisValue.getObject()); if (!constructor) { throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, "Expected SQLStatement"_s)); return {}; } if (callFrame->argumentCount() < 1) { throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, "Expected 1 argument"_s)); return {}; } JSValue pathValue = callFrame->argument(0); if (!pathValue.isString()) { throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, "Expected string"_s)); return {}; } #if LAZY_LOAD_SQLITE if (lazyLoadSQLite() < 0) [[unlikely]] { WTF::String msg = WTF::String::fromUTF8(dlerror()); throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, msg)); return {}; } #endif initializeSQLite(); auto topExceptionScope = DECLARE_TOP_EXCEPTION_SCOPE(vm); String path = pathValue.toWTFString(lexicalGlobalObject); RETURN_IF_EXCEPTION(topExceptionScope, JSValue::encode(jsUndefined())); (void)topExceptionScope.tryClearException(); int openFlags = DEFAULT_SQLITE_FLAGS; if (callFrame->argumentCount() > 1) { JSValue flags = callFrame->argument(1); if (!flags.isNumber()) { throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, "Expected number"_s)); return {}; } openFlags = flags.toInt32(lexicalGlobalObject); } JSValue finalizationTarget = callFrame->argument(2); sqlite3* db = nullptr; int statusCode = sqlite3_open_v2(path.utf8().data(), &db, openFlags, nullptr); if (statusCode != SQLITE_OK) { throwException(lexicalGlobalObject, scope, createSQLiteError(lexicalGlobalObject, db)); return {}; } sqlite3_extended_result_codes(db, 1); int status = sqlite3_db_config(db, SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION, 1, NULL); if (status != SQLITE_OK) { // TODO: log a warning here that extensions are unsupported. } status = sqlite3_db_config(db, SQLITE_DBCONFIG_DEFENSIVE, 1, NULL); if (status != SQLITE_OK) { // TODO: log a warning here that defensive mode is unsupported. } auto index = databases().size(); databases().append(new VersionSqlite3(db)); if (finalizationTarget.isObject()) { vm.heap.addFinalizer(finalizationTarget.getObject(), [index](JSC::JSCell* ptr) -> void { databases()[index]->release(); }); } RELEASE_AND_RETURN(scope, JSValue::encode(jsNumber(index))); } JSC_DEFINE_HOST_FUNCTION(jsSQLStatementCloseStatementFunction, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::CallFrame* callFrame)) { auto& vm = JSC::getVM(lexicalGlobalObject); auto scope = DECLARE_THROW_SCOPE(vm); JSValue thisValue = callFrame->thisValue(); JSSQLStatementConstructor* constructor = jsDynamicCast(thisValue.getObject()); if (!constructor) { throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, "Expected SQLStatement"_s)); return {}; } if (callFrame->argumentCount() < 1) { throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, "Expected 1 argument"_s)); return {}; } JSValue dbNumber = callFrame->argument(0); JSValue throwOnError = callFrame->argument(1); if (!dbNumber.isNumber()) { throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, "Expected number"_s)); return {}; } int dbIndex = dbNumber.toInt32(lexicalGlobalObject); if (dbIndex < 0 || dbIndex >= databases().size()) { throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, "Invalid database handle"_s)); return {}; } bool shouldThrowOnError = (throwOnError.isEmpty() || throwOnError.isUndefined()) ? false : throwOnError.toBoolean(lexicalGlobalObject); sqlite3* db = databases()[dbIndex]->db; // no-op if already closed if (!db) { return JSValue::encode(jsUndefined()); } // sqlite3_close_v2 is used for automatic GC cleanup int statusCode = shouldThrowOnError ? sqlite3_close(db) : sqlite3_close_v2(db); if (statusCode != SQLITE_OK) { throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, WTF::String::fromUTF8(sqlite3_errstr(statusCode)))); return {}; } databases()[dbIndex]->db = nullptr; return JSValue::encode(jsUndefined()); } JSC_DEFINE_HOST_FUNCTION(jsSQLStatementFcntlFunction, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::CallFrame* callFrame)) { auto& vm = JSC::getVM(lexicalGlobalObject); auto scope = DECLARE_THROW_SCOPE(vm); JSValue thisValue = callFrame->thisValue(); JSSQLStatementConstructor* thisObject = jsDynamicCast(thisValue.getObject()); if (!thisObject) [[unlikely]] { throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, "Expected SQLStatement"_s)); return {}; } if (callFrame->argumentCount() < 2) { throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, "Expected 2 arguments"_s)); return {}; } JSValue dbNumber = callFrame->argument(0); JSValue databaseFileName = callFrame->argument(1); JSValue opNumber = callFrame->argument(2); JSValue resultValue = callFrame->argument(3); if (!dbNumber.isNumber() || !opNumber.isNumber()) { throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, "Expected number"_s)); return {}; } int dbIndex = dbNumber.toInt32(lexicalGlobalObject); int op = opNumber.toInt32(lexicalGlobalObject); if (dbIndex < 0 || dbIndex >= databases().size()) { throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, "Invalid database handle"_s)); return {}; } sqlite3* db = databases()[dbIndex]->db; // no-op if already closed if (!db) { return JSValue::encode(jsUndefined()); } CString fileNameStr; if (databaseFileName.isString()) { fileNameStr = databaseFileName.toWTFString(lexicalGlobalObject).utf8(); RETURN_IF_EXCEPTION(scope, {}); } int resultInt = -1; void* resultPtr = nullptr; if (resultValue.isObject()) { if (auto* view = jsDynamicCast(resultValue.getObject())) { if (view->isDetached()) { throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, "TypedArray is detached"_s)); return {}; } resultPtr = view->vector(); if (resultPtr == nullptr) { throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, "Expected buffer"_s)); return {}; } } } else if (resultValue.isNumber()) { resultInt = resultValue.toInt32(lexicalGlobalObject); RETURN_IF_EXCEPTION(scope, {}); resultPtr = &resultInt; } else if (resultValue.isNull()) { } else { throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, "Expected result to be a number, null or a TypedArray"_s)); return {}; } int statusCode = sqlite3_file_control(db, fileNameStr.isNull() ? nullptr : fileNameStr.data(), op, resultPtr); if (statusCode == SQLITE_ERROR) { throwException(lexicalGlobalObject, scope, createSQLiteError(lexicalGlobalObject, db)); return {}; } return JSValue::encode(jsNumber(statusCode)); } /* Hash table for constructor */ static const HashTableValue JSSQLStatementConstructorTableValues[] = { { "open"_s, static_cast(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsSQLStatementOpenStatementFunction, 2 } }, { "close"_s, static_cast(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsSQLStatementCloseStatementFunction, 1 } }, { "prepare"_s, static_cast(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsSQLStatementPrepareStatementFunction, 2 } }, { "run"_s, static_cast(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsSQLStatementExecuteFunction, 3 } }, { "isInTransaction"_s, static_cast(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsSQLStatementIsInTransactionFunction, 1 } }, { "loadExtension"_s, static_cast(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsSQLStatementLoadExtensionFunction, 2 } }, { "setCustomSQLite"_s, static_cast(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsSQLStatementSetCustomSQLite, 1 } }, { "serialize"_s, static_cast(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsSQLStatementSerialize, 1 } }, { "deserialize"_s, static_cast(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsSQLStatementDeserialize, 2 } }, { "fcntl"_s, static_cast(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsSQLStatementFcntlFunction, 2 } }, }; const ClassInfo JSSQLStatementConstructor::s_info = { "SQLStatement"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSSQLStatementConstructor) }; void JSSQLStatementConstructor::finishCreation(VM& vm) { Base::finishCreation(vm); // TODO: use LazyClassStructure? auto* instanceObject = JSSQLStatement::create(static_cast(globalObject()), nullptr, nullptr); JSValue proto = instanceObject->getPrototype(globalObject()); this->putDirect(vm, vm.propertyNames->prototype, proto, PropertyAttribute::DontEnum | PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly); reifyStaticProperties(vm, JSSQLStatementConstructor::info(), JSSQLStatementConstructorTableValues, *this); JSC_TO_STRING_TAG_WITHOUT_TRANSITION(); ASSERT(inherits(info())); } template static inline JSC::JSValue constructResultObject(JSC::JSGlobalObject* lexicalGlobalObject, JSSQLStatement* castedThis) { auto& columnNames = castedThis->columnNames->data()->propertyNameVector(); int count = columnNames.size(); auto& vm = JSC::getVM(lexicalGlobalObject); auto scope = DECLARE_THROW_SCOPE(vm); // 64 is the maximum we can preallocate here // see https://github.com/oven-sh/bun/issues/987 JSC::JSObject* result; auto* stmt = castedThis->stmt; if (auto* structure = castedThis->_structure.get()) { result = JSC::constructEmptyObject(vm, structure); // i: the index of columns returned from SQLite // j: the index of object property for (int i = 0, j = 0; j < count; i++, j++) { if (!castedThis->validColumns.get(i)) { // this column is duplicate, skip j -= 1; continue; } auto value = toJS(vm, lexicalGlobalObject, stmt, i); RETURN_IF_EXCEPTION(scope, {}); result->putDirectOffset(vm, j, value); } } else { if (count <= JSFinalObject::maxInlineCapacity) { result = JSC::JSFinalObject::create(vm, castedThis->_prototype.get()->structure()); } else { JSObject* prototype = castedThis->userPrototype ? castedThis->userPrototype.get() : lexicalGlobalObject->objectPrototype(); result = JSC::JSFinalObject::create(vm, JSC::JSFinalObject::createStructure(vm, lexicalGlobalObject, prototype, JSFinalObject::maxInlineCapacity)); } for (int i = 0, j = 0; j < count; i++, j++) { if (!castedThis->validColumns.get(i)) { j -= 1; continue; } const auto& name = columnNames[j]; auto value = toJS(vm, lexicalGlobalObject, stmt, i); RETURN_IF_EXCEPTION(scope, {}); result->putDirect(vm, name, value, 0); } } RELEASE_AND_RETURN(scope, result); } static inline JSC::JSArray* constructResultRowRaw(JSC::VM& vm, JSC::JSGlobalObject* lexicalGlobalObject, JSSQLStatement* castedThis, size_t columnCount) { auto throwScope = DECLARE_THROW_SCOPE(vm); auto* stmt = castedThis->stmt; MarkedArgumentBuffer arguments; arguments.ensureCapacity(columnCount); for (size_t i = 0; i < columnCount; i++) { JSValue value = toJSAsBuffer(vm, lexicalGlobalObject, stmt, i); RETURN_IF_EXCEPTION(throwScope, nullptr); arguments.append(value); } RELEASE_AND_RETURN(throwScope, JSC::constructArray(lexicalGlobalObject, static_cast(nullptr), arguments)); } static inline JSC::JSArray* constructResultRow(JSC::VM& vm, JSC::JSGlobalObject* lexicalGlobalObject, JSSQLStatement* castedThis, size_t columnCount) { auto throwScope = DECLARE_THROW_SCOPE(vm); auto* stmt = castedThis->stmt; MarkedArgumentBuffer arguments; arguments.ensureCapacity(columnCount); if (castedThis->useBigInt64) { for (size_t i = 0; i < columnCount; i++) { JSValue value = toJS(vm, lexicalGlobalObject, stmt, i); RETURN_IF_EXCEPTION(throwScope, nullptr); arguments.append(value); } } else { for (size_t i = 0; i < columnCount; i++) { JSValue value = toJS(vm, lexicalGlobalObject, stmt, i); RETURN_IF_EXCEPTION(throwScope, nullptr); arguments.append(value); } } JSC::ObjectInitializationScope initializationScope(vm); Structure* arrayStructure = lexicalGlobalObject->arrayStructureForIndexingTypeDuringAllocation(ArrayWithContiguous); JSC::JSArray* result; if ((result = JSC::JSArray::tryCreateUninitializedRestricted(initializationScope, arrayStructure, columnCount))) [[likely]] { for (size_t i = 0; i < columnCount; i++) { result->initializeIndex(initializationScope, i, arguments.at(i)); } } else { RETURN_IF_EXCEPTION(throwScope, nullptr); result = JSC::constructArray(lexicalGlobalObject, arrayStructure, arguments); RETURN_IF_EXCEPTION(throwScope, {}); } return result; } JSC_DEFINE_HOST_FUNCTION(jsSQLStatementSetPrototypeFunction, (JSGlobalObject * lexicalGlobalObject, JSC::CallFrame* callFrame)) { auto& vm = JSC::getVM(lexicalGlobalObject); auto scope = DECLARE_THROW_SCOPE(vm); auto* castedThis = jsCast(callFrame->thisValue()); CHECK_THIS JSValue classValue = callFrame->argument(0); if (classValue.isObject()) { JSObject* classObject = classValue.getObject(); if (classObject == lexicalGlobalObject->objectConstructor()) { castedThis->userPrototype.clear(); // Force the prototypes to be re-created if (castedThis->version_db) { castedThis->version_db->version++; } return JSValue::encode(jsUndefined()); } if (!classObject->isConstructor()) { throwTypeError(lexicalGlobalObject, scope, "Expected a constructor"_s); return {}; } JSValue prototype = classObject->getIfPropertyExists(lexicalGlobalObject, vm.propertyNames->prototype); RETURN_IF_EXCEPTION(scope, {}); if (!prototype && !scope.exception()) [[unlikely]] { throwTypeError(lexicalGlobalObject, scope, "Expected constructor to have a prototype"_s); return {}; } if (!prototype.isObject()) { throwTypeError(lexicalGlobalObject, scope, "Expected a constructor prototype to be an object"_s); return {}; } castedThis->userPrototype.set(vm, castedThis, prototype.getObject()); // Force the prototypes to be re-created if (castedThis->version_db) { castedThis->version_db->version++; } } else if (classValue.isUndefined()) { castedThis->userPrototype.clear(); // Force the prototypes to be re-created if (castedThis->version_db) { castedThis->version_db->version++; } } else { throwTypeError(lexicalGlobalObject, scope, "Expected class to be a constructor or undefined"_s); return {}; } return JSValue::encode(jsUndefined()); } JSC_DEFINE_HOST_FUNCTION(jsSQLStatementExecuteStatementFunctionIterate, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::CallFrame* callFrame)) { auto& vm = JSC::getVM(lexicalGlobalObject); auto scope = DECLARE_THROW_SCOPE(vm); auto castedThis = jsDynamicCast(callFrame->thisValue()); CHECK_THIS auto* stmt = castedThis->stmt; CHECK_PREPARED int busy = sqlite3_stmt_busy(stmt); if (!busy) { int statusCode = sqlite3_reset(stmt); if (statusCode != SQLITE_OK) [[unlikely]] { throwException(lexicalGlobalObject, scope, createSQLiteError(lexicalGlobalObject, castedThis->version_db->db)); return {}; } } if (callFrame->argumentCount() > 0) { auto arg0 = callFrame->argument(0); DO_REBIND(arg0); } int status = sqlite3_step(stmt); if (!sqlite3_stmt_readonly(stmt)) { castedThis->version_db->version++; } if (!castedThis->hasExecuted || castedThis->need_update()) { initializeColumnNames(lexicalGlobalObject, castedThis); RETURN_IF_EXCEPTION(scope, {}); } JSValue result = jsNull(); if (status == SQLITE_ROW) { bool useBigInt64 = castedThis->useBigInt64; result = useBigInt64 ? constructResultObject(lexicalGlobalObject, castedThis) : constructResultObject(lexicalGlobalObject, castedThis); RETURN_IF_EXCEPTION(scope, {}); } if (status == SQLITE_DONE || status == SQLITE_OK || status == SQLITE_ROW) { RELEASE_AND_RETURN(scope, JSValue::encode(result)); } else { throwException(lexicalGlobalObject, scope, createSQLiteError(lexicalGlobalObject, castedThis->version_db->db)); sqlite3_reset(stmt); return {}; } } JSC_DEFINE_HOST_FUNCTION(jsSQLStatementExecuteStatementFunctionAll, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::CallFrame* callFrame)) { auto& vm = JSC::getVM(lexicalGlobalObject); auto scope = DECLARE_THROW_SCOPE(vm); auto castedThis = jsDynamicCast(callFrame->thisValue()); CHECK_THIS auto* stmt = castedThis->stmt; CHECK_PREPARED int statusCode = sqlite3_reset(stmt); if (statusCode != SQLITE_OK) [[unlikely]] { throwException(lexicalGlobalObject, scope, createSQLiteError(lexicalGlobalObject, castedThis->version_db->db)); return {}; } int64_t currentMemoryUsage = sqlite_malloc_amount; if (callFrame->argumentCount() > 0) { auto arg0 = callFrame->argument(0); DO_REBIND(arg0); } int status = sqlite3_step(stmt); if (!sqlite3_stmt_readonly(stmt)) { castedThis->version_db->version++; } if (!castedThis->hasExecuted || castedThis->need_update()) { initializeColumnNames(lexicalGlobalObject, castedThis); RETURN_IF_EXCEPTION(scope, {}); } size_t columnCount = castedThis->columnNames->size(); JSValue result = jsUndefined(); if (status == SQLITE_ROW) { // this is a count from UPDATE or another query like that if (columnCount == 0) { result = jsNumber(sqlite3_changes(castedThis->version_db->db)); while (status == SQLITE_ROW) { status = sqlite3_step(stmt); } } else { bool useBigInt64 = castedThis->useBigInt64; JSC::JSArray* resultArray = JSC::constructEmptyArray(lexicalGlobalObject, static_cast(nullptr), 0); RETURN_IF_EXCEPTION(scope, {}); if (useBigInt64) { do { JSC::JSValue result = constructResultObject(lexicalGlobalObject, castedThis); RETURN_IF_EXCEPTION(scope, {}); resultArray->push(lexicalGlobalObject, result); RETURN_IF_EXCEPTION(scope, {}); status = sqlite3_step(stmt); } while (status == SQLITE_ROW); } else { do { JSC::JSValue result = constructResultObject(lexicalGlobalObject, castedThis); RETURN_IF_EXCEPTION(scope, {}); resultArray->push(lexicalGlobalObject, result); RETURN_IF_EXCEPTION(scope, {}); status = sqlite3_step(stmt); } while (status == SQLITE_ROW); } result = resultArray; } } else if (status == SQLITE_DONE) { result = JSC::constructEmptyArray(lexicalGlobalObject, static_cast(nullptr), 0); RETURN_IF_EXCEPTION(scope, {}); } if (status != SQLITE_DONE && status != SQLITE_OK) [[unlikely]] { throwException(lexicalGlobalObject, scope, createSQLiteError(lexicalGlobalObject, castedThis->version_db->db)); sqlite3_reset(stmt); return {}; } int64_t memoryChange = sqlite_malloc_amount - currentMemoryUsage; if (memoryChange > 255) { vm.heap.deprecatedReportExtraMemory(memoryChange); } RELEASE_AND_RETURN(scope, JSC::JSValue::encode(result)); } JSC_DEFINE_HOST_FUNCTION(jsSQLStatementExecuteStatementFunctionGet, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::CallFrame* callFrame)) { auto& vm = JSC::getVM(lexicalGlobalObject); auto scope = DECLARE_THROW_SCOPE(vm); auto castedThis = jsDynamicCast(callFrame->thisValue()); CHECK_THIS auto* stmt = castedThis->stmt; CHECK_PREPARED int statusCode = sqlite3_reset(stmt); if (statusCode != SQLITE_OK) [[unlikely]] { throwException(lexicalGlobalObject, scope, createSQLiteError(lexicalGlobalObject, castedThis->version_db->db)); return {}; } if (callFrame->argumentCount() > 0) { auto arg0 = callFrame->argument(0); DO_REBIND(arg0); } int status = sqlite3_step(stmt); if (!sqlite3_stmt_readonly(stmt)) { castedThis->version_db->version++; } if (!castedThis->hasExecuted || castedThis->need_update()) { initializeColumnNames(lexicalGlobalObject, castedThis); RETURN_IF_EXCEPTION(scope, {}); } JSValue result = jsNull(); if (status == SQLITE_ROW) { bool useBigInt64 = castedThis->useBigInt64; result = useBigInt64 ? constructResultObject(lexicalGlobalObject, castedThis) : constructResultObject(lexicalGlobalObject, castedThis); RETURN_IF_EXCEPTION(scope, {}); while (status == SQLITE_ROW) { status = sqlite3_step(stmt); } } if (status == SQLITE_DONE || status == SQLITE_OK) { RELEASE_AND_RETURN(scope, JSValue::encode(result)); } else { throwException(lexicalGlobalObject, scope, createSQLiteError(lexicalGlobalObject, castedThis->version_db->db)); sqlite3_reset(stmt); return {}; } } JSC_DEFINE_HOST_FUNCTION(jsSQLStatementExecuteStatementFunctionRows, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::CallFrame* callFrame)) { auto& vm = JSC::getVM(lexicalGlobalObject); auto scope = DECLARE_THROW_SCOPE(vm); auto castedThis = jsDynamicCast(callFrame->thisValue()); CHECK_THIS; auto* stmt = castedThis->stmt; CHECK_PREPARED int statusCode = sqlite3_reset(stmt); if (statusCode != SQLITE_OK) [[unlikely]] { throwException(lexicalGlobalObject, scope, createSQLiteError(lexicalGlobalObject, castedThis->version_db->db)); sqlite3_reset(stmt); return {}; } int count = callFrame->argumentCount(); if (count > 0) { auto arg0 = callFrame->argument(0); DO_REBIND(arg0); } int status = sqlite3_step(stmt); if (!sqlite3_stmt_readonly(stmt)) { castedThis->version_db->version++; } if (!castedThis->hasExecuted || castedThis->need_update()) { initializeColumnNames(lexicalGlobalObject, castedThis); if (scope.exception()) [[unlikely]] { // Don't forget to reset before releasing the exception. sqlite3_reset(stmt); RELEASE_AND_RETURN(scope, {}); } } size_t columnCount = castedThis->columnNames->size(); JSValue result = jsNull(); if (status == SQLITE_ROW) { // this is a count from UPDATE or another query like that if (columnCount == 0) { while (status == SQLITE_ROW) { status = sqlite3_step(stmt); } result = jsNumber(sqlite3_column_count(stmt)); } else { JSC::JSArray* resultArray = JSC::constructEmptyArray(lexicalGlobalObject, static_cast(nullptr), 0); RETURN_IF_EXCEPTION(scope, {}); { size_t columnCount = sqlite3_column_count(stmt); do { JSC::JSArray* row = constructResultRow(vm, lexicalGlobalObject, castedThis, columnCount); if (!row || scope.exception()) [[unlikely]] { sqlite3_reset(stmt); RELEASE_AND_RETURN(scope, {}); } resultArray->push(lexicalGlobalObject, row); RETURN_IF_EXCEPTION(scope, {}); status = sqlite3_step(stmt); } while (status == SQLITE_ROW); } result = resultArray; } } else if (status == SQLITE_DONE && columnCount != 0) { // breaking change in Bun v0.6.8 result = JSC::constructEmptyArray(lexicalGlobalObject, nullptr, 0); RETURN_IF_EXCEPTION(scope, {}); } if (status != SQLITE_DONE && status != SQLITE_OK) [[unlikely]] { throwException(lexicalGlobalObject, scope, createSQLiteError(lexicalGlobalObject, castedThis->version_db->db)); sqlite3_reset(stmt); return {}; } // sqlite3_reset(stmt); RELEASE_AND_RETURN(scope, JSC::JSValue::encode(result)); } JSC_DEFINE_HOST_FUNCTION(jsSQLStatementExecuteStatementFunctionRawRows, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::CallFrame* callFrame)) { auto& vm = JSC::getVM(lexicalGlobalObject); auto scope = DECLARE_THROW_SCOPE(vm); auto castedThis = jsDynamicCast(callFrame->thisValue()); CHECK_THIS; auto* stmt = castedThis->stmt; CHECK_PREPARED int statusCode = sqlite3_reset(stmt); if (statusCode != SQLITE_OK) [[unlikely]] { throwException(lexicalGlobalObject, scope, createSQLiteError(lexicalGlobalObject, castedThis->version_db->db)); sqlite3_reset(stmt); return {}; } int count = callFrame->argumentCount(); if (count > 0) { auto arg0 = callFrame->argument(0); DO_REBIND(arg0); } int status = sqlite3_step(stmt); if (!sqlite3_stmt_readonly(stmt)) { castedThis->version_db->version++; } if (!castedThis->hasExecuted || castedThis->need_update()) { initializeColumnNames(lexicalGlobalObject, castedThis); if (scope.exception()) [[unlikely]] { sqlite3_reset(stmt); RELEASE_AND_RETURN(scope, {}); } } size_t columnCount = castedThis->columnNames->size(); JSValue result = jsNull(); if (status == SQLITE_ROW) { // this is a count from UPDATE or another query like that if (columnCount == 0) { while (status == SQLITE_ROW) { status = sqlite3_step(stmt); } result = jsNumber(sqlite3_column_count(stmt)); } else { JSC::JSArray* resultArray = JSC::constructEmptyArray(lexicalGlobalObject, nullptr, 0); RETURN_IF_EXCEPTION(scope, {}); { size_t columnCount = sqlite3_column_count(stmt); do { JSC::JSArray* row = constructResultRowRaw(vm, lexicalGlobalObject, castedThis, columnCount); if (!row || scope.exception()) [[unlikely]] { sqlite3_reset(stmt); RELEASE_AND_RETURN(scope, {}); } resultArray->push(lexicalGlobalObject, row); if (scope.exception()) [[unlikely]] { sqlite3_reset(stmt); RELEASE_AND_RETURN(scope, {}); } status = sqlite3_step(stmt); } while (status == SQLITE_ROW); } result = resultArray; } } else if (status == SQLITE_DONE && columnCount != 0) { // breaking change in Bun v0.6.8 result = JSC::constructEmptyArray(lexicalGlobalObject, static_cast(nullptr), 0); RETURN_IF_EXCEPTION(scope, {}); } if (status != SQLITE_DONE && status != SQLITE_OK) [[unlikely]] { throwException(lexicalGlobalObject, scope, createSQLiteError(lexicalGlobalObject, castedThis->version_db->db)); sqlite3_reset(stmt); return {}; } // sqlite3_reset(stmt); RELEASE_AND_RETURN(scope, JSC::JSValue::encode(result)); } JSC_DEFINE_HOST_FUNCTION(jsSQLStatementExecuteStatementFunctionRun, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::CallFrame* callFrame)) { auto& vm = JSC::getVM(lexicalGlobalObject); auto scope = DECLARE_THROW_SCOPE(vm); auto castedThis = jsDynamicCast(callFrame->thisValue()); CHECK_THIS auto* stmt = castedThis->stmt; CHECK_PREPARED int statusCode = sqlite3_reset(stmt); if (statusCode != SQLITE_OK) [[unlikely]] { throwException(lexicalGlobalObject, scope, createSQLiteError(lexicalGlobalObject, castedThis->version_db->db)); return {}; } JSValue diffValue = callFrame->argument(0); if (callFrame->argumentCount() > 1) { auto arg0 = callFrame->argument(1); DO_REBIND(arg0); } if (!castedThis->version_db->db) [[unlikely]] { throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, "Database has closed"_s)); return {}; } int total_changes_before = sqlite3_total_changes(castedThis->version_db->db); int status = sqlite3_step(stmt); if (!sqlite3_stmt_readonly(stmt)) { castedThis->version_db->version++; } if (!castedThis->hasExecuted || castedThis->need_update()) { initializeColumnNames(lexicalGlobalObject, castedThis); if (scope.exception()) [[unlikely]] { sqlite3_reset(stmt); RELEASE_AND_RETURN(scope, {}); } } while (status == SQLITE_ROW) { status = sqlite3_step(stmt); } if (status != SQLITE_DONE && status != SQLITE_OK) [[unlikely]] { throwException(lexicalGlobalObject, scope, createSQLiteError(lexicalGlobalObject, castedThis->version_db->db)); sqlite3_reset(stmt); return {}; } if (auto* diff = JSC::jsDynamicCast(diffValue)) { auto* db = castedThis->version_db->db; const int total_changes_after = sqlite3_total_changes(db); int64_t last_insert_rowid = sqlite3_last_insert_rowid(db); diff->putInternalField(vm, 0, JSC::jsNumber(total_changes_after - total_changes_before)); if (castedThis->useBigInt64) { JSValue lastRowIdBigInt = JSBigInt::createFrom(lexicalGlobalObject, last_insert_rowid); RETURN_IF_EXCEPTION(scope, {}); diff->putInternalField(vm, 1, lastRowIdBigInt); } else { diff->putInternalField(vm, 1, JSC::jsNumber(last_insert_rowid)); } } RELEASE_AND_RETURN(scope, JSC::JSValue::encode(jsUndefined())); } JSC_DEFINE_HOST_FUNCTION(jsSQLStatementToStringFunction, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::CallFrame* callFrame)) { auto& vm = JSC::getVM(lexicalGlobalObject); JSSQLStatement* castedThis = jsDynamicCast(callFrame->thisValue()); auto scope = DECLARE_THROW_SCOPE(vm); CHECK_THIS char* string = sqlite3_expanded_sql(castedThis->stmt); if (!string) { RELEASE_AND_RETURN(scope, JSValue::encode(jsEmptyString(vm))); } size_t length = strlen(string); auto* jsString = JSC::jsString(vm, WTF::String::fromUTF8({ string, length })); sqlite3_free(string); RELEASE_AND_RETURN(scope, JSValue::encode(jsString)); } JSC_DEFINE_CUSTOM_GETTER(jsSqlStatementGetColumnNames, (JSGlobalObject * lexicalGlobalObject, JSC::EncodedJSValue thisValue, PropertyName attributeName)) { auto& vm = JSC::getVM(lexicalGlobalObject); JSSQLStatement* castedThis = jsDynamicCast(JSValue::decode(thisValue)); auto scope = DECLARE_THROW_SCOPE(vm); CHECK_THIS if (!castedThis->hasExecuted || castedThis->need_update()) { initializeColumnNames(lexicalGlobalObject, castedThis); RETURN_IF_EXCEPTION(scope, {}); } JSC::JSArray* array; auto* columnNames = castedThis->columnNames.get(); if (columnNames->size() > 0) { if (castedThis->_prototype) { array = ownPropertyKeys(lexicalGlobalObject, castedThis->_prototype.get(), PropertyNameMode::Strings, DontEnumPropertiesMode::Exclude); RETURN_IF_EXCEPTION(scope, {}); } else { array = JSC::constructEmptyArray(lexicalGlobalObject, static_cast(nullptr), columnNames->size()); RETURN_IF_EXCEPTION(scope, {}); unsigned int i = 0; for (const auto& column : *columnNames) { auto* string = jsString(vm, column.string()); RETURN_IF_EXCEPTION(scope, {}); array->putDirectIndex(lexicalGlobalObject, i++, string); } } } else { array = JSC::constructEmptyArray(lexicalGlobalObject, static_cast(nullptr), 0); RETURN_IF_EXCEPTION(scope, {}); } return JSC::JSValue::encode(array); } JSC_DEFINE_CUSTOM_GETTER(jsSqlStatementGetColumnCount, (JSGlobalObject * lexicalGlobalObject, JSC::EncodedJSValue thisValue, PropertyName attributeName)) { auto& vm = JSC::getVM(lexicalGlobalObject); JSSQLStatement* castedThis = jsDynamicCast(JSValue::decode(thisValue)); auto scope = DECLARE_THROW_SCOPE(vm); CHECK_THIS CHECK_PREPARED RELEASE_AND_RETURN(scope, JSValue::encode(JSC::jsNumber(sqlite3_column_count(castedThis->stmt)))); } JSC_DEFINE_CUSTOM_GETTER(jsSqlStatementGetParamCount, (JSGlobalObject * lexicalGlobalObject, JSC::EncodedJSValue thisValue, PropertyName attributeName)) { auto& vm = JSC::getVM(lexicalGlobalObject); JSSQLStatement* castedThis = jsDynamicCast(JSValue::decode(thisValue)); auto scope = DECLARE_THROW_SCOPE(vm); CHECK_THIS CHECK_PREPARED RELEASE_AND_RETURN(scope, JSC::JSValue::encode(JSC::jsNumber(sqlite3_bind_parameter_count(castedThis->stmt)))); } JSC_DEFINE_CUSTOM_GETTER(jsSqlStatementGetColumnTypes, (JSGlobalObject * lexicalGlobalObject, JSC::EncodedJSValue thisValue, PropertyName attributeName)) { auto& vm = JSC::getVM(lexicalGlobalObject); JSSQLStatement* castedThis = jsDynamicCast(JSValue::decode(thisValue)); auto scope = DECLARE_THROW_SCOPE(vm); CHECK_THIS CHECK_PREPARED int count = sqlite3_column_count(castedThis->stmt); // We need to reset and step the statement to get fresh types, // but only do this for read-only statements to avoid side effects bool isReadOnly = sqlite3_stmt_readonly(castedThis->stmt) != 0; if (!isReadOnly) { // For non-read-only statements, throw an error since column types don't make sense throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, "columnTypes is not available for non-read-only statements (INSERT, UPDATE, DELETE, etc.)"_s)); return {}; } // Reset the statement (safe for read-only statements) int resetStatus = sqlite3_reset(castedThis->stmt); if (resetStatus != SQLITE_OK) { throwException(lexicalGlobalObject, scope, createSQLiteError(lexicalGlobalObject, castedThis->version_db->db)); return {}; } MarkedArgumentBuffer args; // Step once to get to the first row (safe for read-only statements) int stepStatus = sqlite3_step(castedThis->stmt); // If we got a row, get types from it if (stepStatus == SQLITE_ROW) { for (int i = 0; i < count; i++) { JSC::JSValue typeValue; // Get the actual column type from the current row int columnType = sqlite3_column_type(castedThis->stmt, i); switch (columnType) { case SQLITE_INTEGER: typeValue = JSC::jsString(vm, makeAtomString("INTEGER"_s)); break; case SQLITE_FLOAT: typeValue = JSC::jsString(vm, makeAtomString("FLOAT"_s)); break; case SQLITE3_TEXT: typeValue = JSC::jsString(vm, makeAtomString("TEXT"_s)); break; case SQLITE_BLOB: typeValue = JSC::jsString(vm, makeAtomString("BLOB"_s)); break; case SQLITE_NULL: typeValue = JSC::jsString(vm, makeAtomString("NULL"_s)); break; default: typeValue = JSC::jsNull(); break; } args.append(typeValue); } } else if (stepStatus == SQLITE_DONE) { // No data rows to read, return 'NULL' for all columns JSC::JSValue typeValue = JSC::jsString(vm, makeAtomString("NULL"_s)); for (int i = 0; i < count; i++) { args.append(typeValue); } } else { // If there was an error stepping, throw it throwException(lexicalGlobalObject, scope, createSQLiteError(lexicalGlobalObject, castedThis->version_db->db)); sqlite3_reset(castedThis->stmt); return {}; } // Reset the statement back to its original state sqlite3_reset(castedThis->stmt); JSC::JSArray* array = constructArray(lexicalGlobalObject, static_cast(nullptr), args); RETURN_IF_EXCEPTION(scope, {}); RELEASE_AND_RETURN(scope, JSC::JSValue::encode(array)); } JSC_DEFINE_CUSTOM_GETTER(jsSqlStatementGetColumnDeclaredTypes, (JSGlobalObject * lexicalGlobalObject, JSC::EncodedJSValue thisValue, PropertyName attributeName)) { auto& vm = JSC::getVM(lexicalGlobalObject); JSSQLStatement* castedThis = jsDynamicCast(JSValue::decode(thisValue)); auto scope = DECLARE_THROW_SCOPE(vm); CHECK_THIS CHECK_PREPARED // Ensure the statement has been executed at least once if (!castedThis->hasExecuted) { throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, "Statement must be executed before accessing declaredTypes"_s)); return {}; } int count = sqlite3_column_count(castedThis->stmt); JSC::JSArray* array = JSC::constructEmptyArray(lexicalGlobalObject, static_cast(nullptr), count); RETURN_IF_EXCEPTION(scope, {}); // Use declared types only - return raw strings as-is, null when no declared type for (int i = 0; i < count; i++) { const char* declType = sqlite3_column_decltype(castedThis->stmt, i); JSC::JSValue typeValue; if (declType != nullptr) { String typeStr = String::fromUTF8(declType); typeValue = JSC::jsNontrivialString(vm, typeStr); RETURN_IF_EXCEPTION(scope, {}); } else { // If no declared type (e.g., for expressions or results of functions) typeValue = JSC::jsNull(); } array->putDirectIndex(lexicalGlobalObject, i, typeValue); } RELEASE_AND_RETURN(scope, JSC::JSValue::encode(array)); } JSC_DEFINE_CUSTOM_GETTER(jsSqlStatementGetSafeIntegers, (JSGlobalObject * lexicalGlobalObject, JSC::EncodedJSValue thisValue, PropertyName attributeName)) { auto& vm = JSC::getVM(lexicalGlobalObject); JSSQLStatement* castedThis = jsDynamicCast(JSValue::decode(thisValue)); auto scope = DECLARE_THROW_SCOPE(vm); CHECK_THIS CHECK_PREPARED RELEASE_AND_RETURN(scope, JSC::JSValue::encode(JSC::jsBoolean(castedThis->useBigInt64))); } JSC_DEFINE_CUSTOM_SETTER(jsSqlStatementSetSafeIntegers, (JSGlobalObject * lexicalGlobalObject, JSC::EncodedJSValue thisValue, JSC::EncodedJSValue encodedValue, PropertyName attributeName)) { auto& vm = JSC::getVM(lexicalGlobalObject); JSSQLStatement* castedThis = jsDynamicCast(JSValue::decode(thisValue)); auto scope = DECLARE_THROW_SCOPE(vm); CHECK_THIS CHECK_PREPARED bool value = JSValue::decode(encodedValue).toBoolean(lexicalGlobalObject); castedThis->useBigInt64 = value; return true; } JSC_DEFINE_HOST_FUNCTION(jsSQLStatementFunctionFinalize, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::CallFrame* callFrame)) { auto& vm = JSC::getVM(lexicalGlobalObject); JSSQLStatement* castedThis = jsDynamicCast(callFrame->thisValue()); auto scope = DECLARE_THROW_SCOPE(vm); CHECK_THIS if (castedThis->stmt) { sqlite3_finalize(castedThis->stmt); castedThis->stmt = nullptr; } RELEASE_AND_RETURN(scope, JSValue::encode(jsUndefined())); } const ClassInfo JSSQLStatement::s_info = { "SQLStatement"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSSQLStatement) }; /* Hash table for prototype */ void JSSQLStatement::finishCreation(VM& vm) { Base::finishCreation(vm); ASSERT(inherits(info())); vm.heap.reportExtraMemoryAllocated(this, this->extraMemorySize); } JSSQLStatement::~JSSQLStatement() { if (this->stmt) { sqlite3_finalize(this->stmt); } if (auto* columnNames = this->columnNames.get()) { columnNames->releaseData(); this->columnNames = nullptr; } if (this->version_db) { this->version_db->release(); } } 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) { auto& vm = JSC::getVM(lexicalGlobalObject); auto scope = DECLARE_THROW_SCOPE(vm); auto* stmt = this->stmt; auto val = rebindStatement(lexicalGlobalObject, values, scope, this->version_db->db, stmt, clone, this->m_bindingNames, this->useBigInt64); if (val.isNumber()) { RELEASE_AND_RETURN(scope, val); } else { return val; } } template 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); visitor.append(thisObject->userPrototype); } DEFINE_VISIT_CHILDREN(JSSQLStatement); template void JSSQLStatement::visitAdditionalChildren(Visitor& visitor) { JSSQLStatement* thisObject = this; ASSERT_GC_OBJECT_INHERITS(thisObject, info()); visitor.append(thisObject->_structure); visitor.append(thisObject->_prototype); visitor.append(thisObject->userPrototype); } template void JSSQLStatement::visitOutputConstraints(JSCell* cell, Visitor& visitor) { auto* thisObject = jsCast(cell); ASSERT_GC_OBJECT_INHERITS(thisObject, info()); Base::visitOutputConstraints(thisObject, visitor); thisObject->visitAdditionalChildren(visitor); } template void JSSQLStatement::visitOutputConstraints(JSCell*, AbstractSlotVisitor&); template void JSSQLStatement::visitOutputConstraints(JSCell*, SlotVisitor&); JSValue createJSSQLStatementConstructor(Zig::GlobalObject* globalObject) { VM& vm = globalObject->vm(); JSObject* object = JSC::constructEmptyObject(globalObject); auto* diff = InternalFieldTuple::create(vm, globalObject->internalFieldTupleStructure(), jsUndefined(), jsUndefined()); auto* constructor = JSSQLStatementConstructor::create( vm, globalObject, JSSQLStatementConstructor::createStructure(vm, globalObject, globalObject->m_functionPrototype.get())); object->putDirectIndex(globalObject, 0, constructor); object->putDirectIndex(globalObject, 1, diff); return object; } } // namespace WebCore