diff options
Diffstat (limited to 'src/javascript/jsc/bindings/sqlite/JSSQLStatement.cpp')
-rw-r--r-- | src/javascript/jsc/bindings/sqlite/JSSQLStatement.cpp | 1409 |
1 files changed, 0 insertions, 1409 deletions
diff --git a/src/javascript/jsc/bindings/sqlite/JSSQLStatement.cpp b/src/javascript/jsc/bindings/sqlite/JSSQLStatement.cpp deleted file mode 100644 index 7f740ae66..000000000 --- a/src/javascript/jsc/bindings/sqlite/JSSQLStatement.cpp +++ /dev/null @@ -1,1409 +0,0 @@ -#include "root.h" - -#include "JSSQLStatement.h" -#include "JavaScriptCore/JSObjectInlines.h" -#include "wtf/text/ExternalStringImpl.h" - -#include "JavaScriptCore/FunctionPrototype.h" -#include "JavaScriptCore/HeapAnalyzer.h" - -#include "JavaScriptCore/JSDestructibleObjectHeapCellType.h" -#include "JavaScriptCore/SlotVisitorMacros.h" -#include "JavaScriptCore/ObjectConstructor.h" -#include "JavaScriptCore/SubspaceInlines.h" -#include "wtf/GetPtr.h" -#include "wtf/PointerPreparations.h" -#include "wtf/URL.h" -#include "JavaScriptCore/TypedArrayInlines.h" -#include "JavaScriptCore/PropertyNameArray.h" -#include "Buffer.h" -#include "GCDefferalContext.h" -#include "Buffer.h" - -/* ******************************************************************************** */ -// 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 -#ifdef LAZY_LOAD_SQLITE -#include "lazy_sqlite3.h" -#else -static inline int lazyLoadSQLite() -{ - return 0; -} - -#endif -/* ******************************************************************************** */ - -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* ctx, void* buf) -{ - sqlite3_free((void*)buf); -} - -/** This is like a 3x perf improvement for allocating objects **/ -#define SQL_USE_PROTOTYPE 1 - -static int DEFAULT_SQLITE_FLAGS - = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE; -static unsigned int DEFAULT_SQLITE_PREPARE_FLAGS = SQLITE_PREPARE_PERSISTENT; -static int MAX_SQLITE_PREPARE_FLAG = SQLITE_PREPARE_PERSISTENT | SQLITE_PREPARE_NORMALIZE | SQLITE_PREPARE_NO_VTAB; - -static JSC_DECLARE_HOST_FUNCTION(jsSQLStatementPrepareStatementFunction); -static JSC_DECLARE_HOST_FUNCTION(jsSQLStatementExecuteFunction); -static JSC_DECLARE_HOST_FUNCTION(jsSQLStatementOpenStatementFunction); -static JSC_DECLARE_HOST_FUNCTION(jsSQLStatementIsInTransactionFunction); - -static JSC_DECLARE_HOST_FUNCTION(jsSQLStatementLoadExtensionFunction); - -static JSC_DECLARE_HOST_FUNCTION(jsSQLStatementExecuteStatementFunction); -static JSC_DECLARE_HOST_FUNCTION(jsSQLStatementExecuteStatementFunctionRun); -static JSC_DECLARE_HOST_FUNCTION(jsSQLStatementExecuteStatementFunctionGet); -static JSC_DECLARE_HOST_FUNCTION(jsSQLStatementExecuteStatementFunctionAll); -static JSC_DECLARE_HOST_FUNCTION(jsSQLStatementExecuteStatementFunctionRows); - -static JSC_DECLARE_CUSTOM_GETTER(jsSqlStatementGetColumnNames); -static JSC_DECLARE_CUSTOM_GETTER(jsSqlStatementGetColumnCount); - -static JSC_DECLARE_HOST_FUNCTION(jsSQLStatementSerialize); -static JSC_DECLARE_HOST_FUNCTION(jsSQLStatementDeserialize); - -#define CHECK_THIS \ - if (UNLIKELY(!castedThis)) { \ - throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, "Expected SQLStatement"_s)); \ - return JSValue::encode(jsUndefined()); \ - } - -#define DO_REBIND(param) \ - if (param.isObject()) { \ - JSC::JSValue reb = castedThis->rebind(lexicalGlobalObject, param, true); \ - if (UNLIKELY(!reb.isNumber())) { \ - return JSValue::encode(reb); /* this means an error */ \ - } \ - } else { \ - throwException(lexicalGlobalObject, scope, createTypeError(lexicalGlobalObject, "Expected object or array"_s)); \ - return JSValue::encode(jsUndefined()); \ - } - -#define CHECK_PREPARED \ - if (UNLIKELY(castedThis->stmt == nullptr || castedThis->db == nullptr)) { \ - throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, "Statement has finalized"_s)); \ - return JSValue::encode(jsUndefined()); \ - } - -namespace WebCore { -using namespace JSC; - -class JSSQLStatement : public JSC::JSNonFinalObject { -public: - using Base = JSC::JSNonFinalObject; - static JSSQLStatement* create(JSC::Structure* structure, JSDOMGlobalObject* globalObject, sqlite3_stmt* stmt, sqlite3* db) - { - JSSQLStatement* ptr = new (NotNull, JSC::allocateCell<JSSQLStatement>(globalObject->vm())) JSSQLStatement(structure, *globalObject, stmt, db); - ptr->finishCreation(globalObject->vm()); - return ptr; - } - static void destroy(JSC::JSCell*); - template<typename, SubspaceAccess mode> static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) - { - return WebCore::subspaceForImpl<JSSQLStatement, UseCustomHeapCellType::No>( - vm, - [](auto& spaces) { return spaces.m_clientSubspaceForJSSQLStatement.get(); }, - [](auto& spaces, auto&& space) { spaces.m_clientSubspaceForJSSQLStatement = WTFMove(space); }, - [](auto& spaces) { return spaces.m_subspaceForJSSQLStatement.get(); }, - [](auto& spaces, auto&& space) { spaces.m_subspaceForJSSQLStatement = WTFMove(space); }); - } - DECLARE_VISIT_CHILDREN; - DECLARE_EXPORT_INFO; - - // static void analyzeHeap(JSCell*, JSC::HeapAnalyzer&); - - JSC::JSValue rebind(JSGlobalObject* globalObject, JSC::JSValue values, bool clone); - 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()); - } - - ~JSSQLStatement(); - - sqlite3_stmt* stmt; - sqlite3* db; - bool hasExecuted = false; - PropertyNameArray columnNames; - mutable WriteBarrier<JSC::JSArray> _columnNames; - mutable WriteBarrier<JSC::JSObject> _prototype; - -protected: - JSSQLStatement(JSC::Structure* structure, JSDOMGlobalObject& globalObject, sqlite3_stmt* stmt, sqlite3* db) - : Base(globalObject.vm(), structure) - , columnNames(globalObject.vm(), PropertyNameMode::Strings, PrivateSymbolMode::Exclude) - , _columnNames(globalObject.vm(), this, nullptr) - , _prototype(globalObject.vm(), this, nullptr) - - { - this->stmt = stmt; - this->db = db; - } - - void finishCreation(JSC::VM&); -}; - -void JSSQLStatement::destroy(JSC::JSCell* cell) -{ - JSSQLStatement* thisObject = static_cast<JSSQLStatement*>(cell); - sqlite3_finalize(thisObject->stmt); - thisObject->stmt = nullptr; -} - -void JSSQLStatementConstructor::destroy(JSC::JSCell* cell) -{ -} - -static inline bool rebindValue(JSC::JSGlobalObject* lexicalGlobalObject, sqlite3_stmt* stmt, int i, JSC::JSValue value, JSC::ThrowScope& scope, bool clone) -{ -#define CHECK_BIND(param) \ - int result = param; \ - if (UNLIKELY(result != SQLITE_OK)) { \ - throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, WTF::String::fromUTF8(sqlite3_errstr(result)))); \ - return false; \ - } - // 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 (UNLIKELY(!str)) { - throwException(lexicalGlobalObject, scope, createTypeError(lexicalGlobalObject, "Expected string"_s)); - return false; - } - - auto roped = str->tryGetValue(lexicalGlobalObject); - if (UNLIKELY(!roped)) { - throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, "Out of memory :("_s)); - return false; - } - - if (roped.is8Bit()) { - CHECK_BIND(sqlite3_bind_text(stmt, i, reinterpret_cast<const char*>(roped.characters8()), roped.length(), transientOrStatic)); - } else { - CHECK_BIND(sqlite3_bind_text16(stmt, i, roped.characters16(), roped.length() * 2, transientOrStatic)); - } - - } else if (UNLIKELY(value.isHeapBigInt())) { - CHECK_BIND(sqlite3_bind_int64(stmt, i, JSBigInt::toBigInt64(value))); - } else if (JSC::JSArrayBufferView* buffer = JSC::jsDynamicCast<JSC::JSArrayBufferView*>(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 -} - -// this function does the equivalent of -// Object.entries(obj) -// except without the intermediate array of arrays -static JSC::JSValue rebindObject(JSC::JSGlobalObject* globalObject, JSC::JSValue targetValue, JSC::ThrowScope& scope, sqlite3_stmt* stmt, bool clone) -{ - JSObject* target = targetValue.toObject(globalObject); - RETURN_IF_EXCEPTION(scope, {}); - JSC::VM& vm = globalObject->vm(); - PropertyNameArray properties(vm, PropertyNameMode::Strings, PrivateSymbolMode::Exclude); - target->methodTable()->getOwnPropertyNames(target, globalObject, properties, DontEnumPropertiesMode::Include); - RETURN_IF_EXCEPTION(scope, {}); - int count = 0; - - for (const auto& propertyName : properties) { - PropertySlot slot(target, PropertySlot::InternalMethodType::GetOwnProperty); - bool hasProperty = target->methodTable()->getOwnPropertySlot(target, globalObject, propertyName, slot); - RETURN_IF_EXCEPTION(scope, JSValue()); - if (!hasProperty) - continue; - if (slot.attributes() & PropertyAttribute::DontEnum) - continue; - - JSValue value; - if (LIKELY(!slot.isTaintedByOpaqueObject())) - value = slot.getValue(globalObject, propertyName); - else - value = target->get(globalObject, propertyName); - - // Ensure this gets freed on scope clear - auto utf8 = WTF::String(propertyName.string()).utf8(); - - int index = sqlite3_bind_parameter_index(stmt, utf8.data()); - if (index == 0) { - throwException(globalObject, scope, createError(globalObject, "Unknown parameter \"" + propertyName.string() + "\""_s)); - return JSValue(); - } - - if (!rebindValue(globalObject, stmt, index, value, scope, clone)) - return JSValue(); - RETURN_IF_EXCEPTION(scope, {}); - count++; - } - - return jsNumber(count); -} - -static JSC::JSValue rebindStatement(JSC::JSGlobalObject* lexicalGlobalObject, JSC::JSValue values, JSC::ThrowScope& scope, sqlite3_stmt* stmt, bool clone) -{ - sqlite3_clear_bindings(stmt); - JSC::JSArray* array = jsDynamicCast<JSC::JSArray*>(values); - int max = sqlite3_bind_parameter_count(stmt); - - if (!array) { - if (JSC::JSObject* object = values.getObject()) { - auto res = rebindObject(lexicalGlobalObject, object, scope, stmt, clone); - RETURN_IF_EXCEPTION(scope, {}); - return res; - } - - throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, "Expected array"_s)); - return jsUndefined(); - } - - int count = array->length(); - - if (count == 0) { - return jsNumber(0); - } - - if (count != max) { - throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, "Expected " + String::number(max) + " values, got " + String::number(count))); - return jsUndefined(); - } - - int i = 0; - for (; i < count; i++) { - JSC::JSValue value = array->getIndexQuickly(i); - rebindValue(lexicalGlobalObject, stmt, i + 1, value, scope, clone); - RETURN_IF_EXCEPTION(scope, {}); - } - - return jsNumber(i); -} - -JSC_DEFINE_HOST_FUNCTION(jsSQLStatementSetCustomSQLite, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::CallFrame* callFrame)) -{ - JSC::VM& vm = lexicalGlobalObject->vm(); - auto scope = DECLARE_THROW_SCOPE(vm); - - JSValue thisValue = callFrame->thisValue(); - JSSQLStatementConstructor* thisObject = jsDynamicCast<JSSQLStatementConstructor*>(thisValue.getObject()); - if (UNLIKELY(!thisObject)) { - throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, "Expected SQL"_s)); - return JSValue::encode(JSC::jsUndefined()); - } - - if (callFrame->argumentCount() < 1) { - throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, "Expected 1 argument"_s)); - return JSValue::encode(JSC::jsUndefined()); - } - - JSC::JSValue sqliteStrValue = callFrame->argument(0); - if (UNLIKELY(!sqliteStrValue.isString())) { - throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, "Expected SQLite path"_s)); - return JSValue::encode(JSC::jsUndefined()); - } - -#ifdef 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 JSValue::encode(JSC::jsUndefined()); - } - - sqlite3_lib_path = sqliteStrValue.toWTFString(lexicalGlobalObject).utf8().data(); - if (lazyLoadSQLite() == -1) { - sqlite3_handle = nullptr; - WTF::String msg = WTF::String::fromUTF8(dlerror()); - throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, msg)); - return JSValue::encode(JSC::jsUndefined()); - } -#endif - - RELEASE_AND_RETURN(scope, JSValue::encode(JSC::jsBoolean(true))); -} - -JSC_DEFINE_HOST_FUNCTION(jsSQLStatementDeserialize, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::CallFrame* callFrame)) -{ - JSC::VM& vm = lexicalGlobalObject->vm(); - auto scope = DECLARE_THROW_SCOPE(vm); - - JSValue thisValue = callFrame->thisValue(); - JSSQLStatementConstructor* thisObject = jsDynamicCast<JSSQLStatementConstructor*>(thisValue.getObject()); - if (UNLIKELY(!thisObject)) { - throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, "Expected SQL"_s)); - return JSValue::encode(JSC::jsUndefined()); - } - - if (callFrame->argumentCount() < 1) { - throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, "Expected 1 argument"_s)); - return JSValue::encode(JSC::jsUndefined()); - } - - JSC::JSArrayBufferView* array = jsDynamicCast<JSC::JSArrayBufferView*>(callFrame->argument(0)); - if (UNLIKELY(!array)) { - throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, "Expected Uint8Array or Buffer"_s)); - return JSValue::encode(JSC::jsUndefined()); - } - - if (UNLIKELY(array->isDetached())) { - throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, "TypedArray is detached"_s)); - return JSValue::encode(JSC::jsUndefined()); - } - - size_t byteLength = array->byteLength(); - void* ptr = array->vector(); - if (UNLIKELY(ptr == nullptr || byteLength == 0)) { - throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, "ArrayBuffer must not be empty"_s)); - return JSValue::encode(JSC::jsUndefined()); - } - void* data = sqlite3_malloc64(byteLength); - if (UNLIKELY(data == nullptr)) { - throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, "Failed to allocate memory"_s)); - return JSValue::encode(JSC::jsUndefined()); - } - if (byteLength) { - memcpy(data, ptr, byteLength); - } - - unsigned int flags = SQLITE_DESERIALIZE_FREEONCLOSE | SQLITE_DESERIALIZE_RESIZEABLE; - - if (callFrame->argumentCount() > 1 and callFrame->argument(1).toBoolean(lexicalGlobalObject)) { - flags |= SQLITE_DESERIALIZE_READONLY; - } - - sqlite3* db = nullptr; - if (sqlite3_open_v2(":memory:", &db, DEFAULT_SQLITE_FLAGS, nullptr) != SQLITE_OK) { - throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, "Failed to open SQLite"_s)); - return JSValue::encode(JSC::jsUndefined()); - } - - int status = sqlite3_db_config(db, SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION, 1, NULL); - assert(status == SQLITE_OK); - status = sqlite3_db_config(db, SQLITE_DBCONFIG_DEFENSIVE, 1, NULL); - assert(status == SQLITE_OK); - - status = sqlite3_deserialize(db, "main", reinterpret_cast<unsigned char*>(data), byteLength, byteLength, flags); - if (status == SQLITE_BUSY) { - sqlite3_free(data); - throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, "SQLITE_BUSY"_s)); - return JSValue::encode(JSC::jsUndefined()); - } - - 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 JSValue::encode(JSC::jsUndefined()); - } - - auto count = thisObject->databases.size(); - thisObject->databases.append(db); - RELEASE_AND_RETURN(scope, JSValue::encode(jsNumber(count))); -} - -JSC_DEFINE_HOST_FUNCTION(jsSQLStatementSerialize, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::CallFrame* callFrame)) -{ - JSC::VM& vm = lexicalGlobalObject->vm(); - auto scope = DECLARE_THROW_SCOPE(vm); - - JSValue thisValue = callFrame->thisValue(); - JSSQLStatementConstructor* thisObject = jsDynamicCast<JSSQLStatementConstructor*>(thisValue.getObject()); - if (UNLIKELY(!thisObject)) { - throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, "Expected SQL"_s)); - return JSValue::encode(JSC::jsUndefined()); - } - - int32_t dbIndex = callFrame->argument(0).toInt32(lexicalGlobalObject); - if (UNLIKELY(dbIndex < 0 || dbIndex >= thisObject->databases.size())) { - throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, "Invalid database handle"_s)); - return JSValue::encode(JSC::jsUndefined()); - } - - sqlite3* db = thisObject->databases[dbIndex]; - if (UNLIKELY(!db)) { - throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, "Can't do this on a closed database"_s)); - return JSValue::encode(JSC::jsUndefined()); - } - - WTF::String attachedName = callFrame->argument(1).toWTFString(lexicalGlobalObject); - RETURN_IF_EXCEPTION(scope, JSC::JSValue::encode(JSC::jsUndefined())); - - if (attachedName.isEmpty()) { - throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, "Expected attached database name"_s)); - return JSValue::encode(JSC::jsUndefined()); - } - sqlite3_int64 length = -1; - unsigned char* data = sqlite3_serialize(db, attachedName.utf8().data(), &length, 0); - if (UNLIKELY(data == nullptr && length)) { - throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, "Out of memory"_s)); - return JSValue::encode(JSC::jsUndefined()); - } - - RELEASE_AND_RETURN(scope, JSBuffer__bufferFromPointerAndLengthAndDeinit(lexicalGlobalObject, reinterpret_cast<char*>(data), static_cast<unsigned int>(length), data, sqlite_free_typed_array)); -} - -JSC_DEFINE_HOST_FUNCTION(jsSQLStatementLoadExtensionFunction, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::CallFrame* callFrame)) -{ - JSC::VM& vm = lexicalGlobalObject->vm(); - auto scope = DECLARE_THROW_SCOPE(vm); - - JSValue thisValue = callFrame->thisValue(); - JSSQLStatementConstructor* thisObject = jsDynamicCast<JSSQLStatementConstructor*>(thisValue.getObject()); - if (UNLIKELY(!thisObject)) { - throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, "Expected SQL"_s)); - return JSValue::encode(JSC::jsUndefined()); - } - - int32_t dbIndex = callFrame->argument(0).toInt32(lexicalGlobalObject); - if (UNLIKELY(dbIndex < 0 || dbIndex >= thisObject->databases.size())) { - throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, "Invalid database handle"_s)); - return JSValue::encode(JSC::jsUndefined()); - } - - JSC::JSValue extension = callFrame->argument(1); - if (UNLIKELY(!extension.isString())) { - throwException(lexicalGlobalObject, scope, createTypeError(lexicalGlobalObject, "Expected string"_s)); - return JSValue::encode(JSC::jsUndefined()); - } - - auto extensionString = extension.toWTFString(lexicalGlobalObject); - RETURN_IF_EXCEPTION(scope, {}); - - sqlite3* db = thisObject->databases[dbIndex]; - if (UNLIKELY(!db)) { - throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, "Can't do this on a closed database"_s)); - return JSValue::encode(JSC::jsUndefined()); - } - - auto entryPointStr = callFrame->argumentCount() > 2 && callFrame->argument(2).isString() ? callFrame->argument(2).toWTFString(lexicalGlobalObject) : String(); - const char* entryPoint = entryPointStr.length() == 0 ? NULL : entryPointStr.utf8().data(); - char* error; - int rc = sqlite3_load_extension(db, extensionString.utf8().data(), entryPoint, &error); - - // TODO: can we disable loading extensions after this? - if (rc != SQLITE_OK) { - throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, sqliteString(error != nullptr ? error : sqlite3_errmsg(db)))); - return JSValue::encode(JSC::jsUndefined()); - } - - RELEASE_AND_RETURN(scope, JSValue::encode(JSC::jsUndefined())); -} - -// 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)) -{ - JSC::VM& vm = lexicalGlobalObject->vm(); - auto scope = DECLARE_THROW_SCOPE(vm); - - JSValue thisValue = callFrame->thisValue(); - JSSQLStatementConstructor* thisObject = jsDynamicCast<JSSQLStatementConstructor*>(thisValue.getObject()); - if (UNLIKELY(!thisObject)) { - throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, "Expected SQL"_s)); - return JSValue::encode(JSC::jsUndefined()); - } - - if (callFrame->argumentCount() < 2) { - throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, "Expected at least 2 arguments"_s)); - return JSValue::encode(JSC::jsUndefined()); - } - - int handle = callFrame->argument(0).toInt32(lexicalGlobalObject); - if (thisObject->databases.size() < handle) { - throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, "Invalid database handle"_s)); - return JSValue::encode(JSC::jsUndefined()); - } - sqlite3* db = thisObject->databases[handle]; - - if (UNLIKELY(!db)) { - throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, "Database has closed"_s)); - return JSValue::encode(JSC::jsUndefined()); - } - - JSC::JSValue sqlValue = callFrame->argument(1); - if (UNLIKELY(!sqlValue.isString())) { - throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, "Expected SQL string"_s)); - return JSValue::encode(JSC::jsUndefined()); - } - - EnsureStillAliveScope bindingsAliveScope = callFrame->argumentCount() > 2 ? callFrame->argument(2) : jsUndefined(); - - auto sqlString = sqlValue.toWTFString(lexicalGlobalObject); - if (UNLIKELY(sqlString.length() == 0)) { - throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, "SQL string mustn't be blank"_s)); - return JSValue::encode(JSC::jsUndefined()); - } - - // TODO: trim whitespace & newlines before sending - // we don't because webkit doesn't expose a function that makes this super - // easy without using unicode whitespace definition the - // StringPrototype.trim() implementation GC allocates a new JSString* and - // potentially re-allocates the string (not 100% sure if reallocates) so we - // can't use that here - sqlite3_stmt* statement = nullptr; - - int rc = SQLITE_OK; - if (sqlString.is8Bit()) { - rc = sqlite3_prepare_v3(db, reinterpret_cast<const char*>(sqlString.characters8()), sqlString.length(), 0, &statement, nullptr); - } else { - rc = sqlite3_prepare16_v3(db, sqlString.characters16(), sqlString.length() * 2, 0, &statement, nullptr); - } - - if (rc != SQLITE_OK || statement == nullptr) { - throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, WTF::String::fromUTF8(sqlite3_errmsg(db)))); - // sqlite3 handles when the pointer is null - sqlite3_finalize(statement); - return JSValue::encode(JSC::jsUndefined()); - } - - if (!bindingsAliveScope.value().isUndefinedOrNull()) { - if (bindingsAliveScope.value().isObject()) { - JSC::JSValue reb = rebindStatement(lexicalGlobalObject, bindingsAliveScope.value(), scope, statement, false); - if (UNLIKELY(!reb.isNumber())) { - sqlite3_finalize(statement); - return JSValue::encode(reb); /* this means an error */ - } - } else { - throwException(lexicalGlobalObject, scope, createTypeError(lexicalGlobalObject, "Expected bindings to be an object or array"_s)); - sqlite3_finalize(statement); - return JSValue::encode(jsUndefined()); - } - } - - rc = sqlite3_step(statement); - - // we don't care about the results, therefore the row-by-row output doesn't matter - // that's why we don't bother to loop through the results - if (rc != SQLITE_DONE and rc != SQLITE_ROW) { - throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, WTF::String::fromUTF8(sqlite3_errstr(rc)))); - // we finalize after just incase something about error messages in - // sqlite depends on the existence of the most recent statement i don't - // think that's actually how this works - just being cautious - sqlite3_finalize(statement); - return JSValue::encode(JSC::jsUndefined()); - } - - sqlite3_finalize(statement); - return JSValue::encode(jsUndefined()); -} - -JSC_DEFINE_HOST_FUNCTION(jsSQLStatementIsInTransactionFunction, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::CallFrame* callFrame)) -{ - JSC::VM& vm = lexicalGlobalObject->vm(); - auto scope = DECLARE_THROW_SCOPE(vm); - - JSValue thisValue = callFrame->thisValue(); - JSSQLStatementConstructor* thisObject = jsDynamicCast<JSSQLStatementConstructor*>(thisValue.getObject()); - if (UNLIKELY(!thisObject)) { - throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, "Expected SQLStatement"_s)); - return JSValue::encode(JSC::jsUndefined()); - } - - JSC::JSValue dbNumber = callFrame->argument(0); - - if (!dbNumber.isNumber()) { - throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, "Invalid database handle"_s)); - return JSValue::encode(JSC::jsUndefined()); - } - - int handle = dbNumber.toInt32(lexicalGlobalObject); - - if (handle < 0 || handle > thisObject->databases.size()) { - throwException(lexicalGlobalObject, scope, createRangeError(lexicalGlobalObject, "Invalid database handle"_s)); - return JSValue::encode(JSC::jsUndefined()); - } - - sqlite3* db = thisObject->databases[handle]; - - if (UNLIKELY(!db)) { - throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, "Database has closed"_s)); - return JSValue::encode(JSC::jsUndefined()); - } - - RELEASE_AND_RETURN(scope, JSValue::encode(jsBoolean(!sqlite3_get_autocommit(db)))); -} - -JSC_DEFINE_HOST_FUNCTION(jsSQLStatementPrepareStatementFunction, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::CallFrame* callFrame)) -{ - JSC::VM& vm = lexicalGlobalObject->vm(); - auto scope = DECLARE_THROW_SCOPE(vm); - - JSValue thisValue = callFrame->thisValue(); - JSSQLStatementConstructor* thisObject = jsDynamicCast<JSSQLStatementConstructor*>(thisValue.getObject()); - if (UNLIKELY(!thisObject)) { - throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, "Expected SQLStatement"_s)); - return JSValue::encode(JSC::jsUndefined()); - } - - JSC::JSValue dbNumber = callFrame->argument(0); - JSC::JSValue sqlValue = callFrame->argument(1); - JSC::JSValue bindings = callFrame->argument(2); - JSC::JSValue prepareFlagsValue = callFrame->argument(3); - - if (!dbNumber.isNumber() || !sqlValue.isString()) { - throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, "SQLStatement requires a number and a string"_s)); - return JSValue::encode(JSC::jsUndefined()); - } - - int handle = dbNumber.toInt32(lexicalGlobalObject); - if (handle < 0 || handle > thisObject->databases.size()) { - throwException(lexicalGlobalObject, scope, createRangeError(lexicalGlobalObject, "Invalid database handle"_s)); - return JSValue::encode(JSC::jsUndefined()); - } - - sqlite3* db = thisObject->databases[handle]; - if (!db) { - throwException(lexicalGlobalObject, scope, createRangeError(lexicalGlobalObject, "Cannot use a closed database"_s)); - return JSValue::encode(JSC::jsUndefined()); - } - - auto sqlString = sqlValue.toWTFString(lexicalGlobalObject); - if (!sqlString.length()) { - throwException(lexicalGlobalObject, scope, createRangeError(lexicalGlobalObject, "Invalid SQL statement"_s)); - return JSValue::encode(JSC::jsUndefined()); - } - - 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 JSValue::encode(JSC::jsUndefined()); - } - flags = static_cast<unsigned int>(prepareFlags); - } - - sqlite3_stmt* statement = nullptr; - - int rc = SQLITE_OK; - if (sqlString.is8Bit()) { - rc = sqlite3_prepare_v3(db, reinterpret_cast<const char*>(sqlString.characters8()), sqlString.length(), flags, &statement, nullptr); - } else { - rc = sqlite3_prepare16_v3(db, sqlString.characters16(), sqlString.length() * 2, flags, &statement, nullptr); - } - - if (rc != SQLITE_OK) { - throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, WTF::String::fromUTF8(sqlite3_errmsg(db)))); - return JSValue::encode(JSC::jsUndefined()); - } - - auto* structure = JSSQLStatement::createStructure(vm, lexicalGlobalObject, lexicalGlobalObject->objectPrototype()); - // auto* structure = JSSQLStatement::createStructure(vm, globalObject(), thisObject->getDirect(vm, vm.propertyNames->prototype)); - JSSQLStatement* sqlStatement = JSSQLStatement::create(structure, reinterpret_cast<Zig::GlobalObject*>(lexicalGlobalObject), statement, db); - sqlStatement->db = db; - 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, callHostFunctionAsConstructor, String("SQLStatement"_s)); - JSSQLStatementConstructor* ptr = new (NotNull, JSC::allocateCell<JSSQLStatementConstructor>(vm)) JSSQLStatementConstructor(vm, executable, globalObject, structure); - ptr->finishCreation(vm); - - return ptr; -} - -JSC_DEFINE_HOST_FUNCTION(jsSQLStatementOpenStatementFunction, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::CallFrame* callFrame)) -{ - JSC::VM& vm = lexicalGlobalObject->vm(); - auto scope = DECLARE_THROW_SCOPE(vm); - - JSValue thisValue = callFrame->thisValue(); - JSSQLStatementConstructor* constructor = jsDynamicCast<JSSQLStatementConstructor*>(thisValue.getObject()); - if (!constructor) { - throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, "Expected SQLStatement"_s)); - return JSValue::encode(jsUndefined()); - } - - if (callFrame->argumentCount() < 1) { - throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, "Expected 1 argument"_s)); - return JSValue::encode(jsUndefined()); - } - - JSValue pathValue = callFrame->argument(0); - if (!pathValue.isString()) { - throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, "Expected string"_s)); - return JSValue::encode(jsUndefined()); - } - -#if LAZY_LOAD_SQLITE - if (UNLIKELY(lazyLoadSQLite() < 0)) { - WTF::String msg = WTF::String::fromUTF8(dlerror()); - throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, msg)); - return JSValue::encode(JSC::jsUndefined()); - } -#endif - - auto catchScope = DECLARE_CATCH_SCOPE(vm); - String path = pathValue.toWTFString(lexicalGlobalObject); - RETURN_IF_EXCEPTION(catchScope, JSValue::encode(jsUndefined())); - catchScope.clearException(); - 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 JSValue::encode(jsUndefined()); - } - - openFlags = flags.toInt32(lexicalGlobalObject); - } - - sqlite3* db = nullptr; - int statusCode = sqlite3_open_v2(path.utf8().data(), &db, openFlags, nullptr); - if (statusCode != SQLITE_OK) { - throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, WTF::String::fromUTF8(sqlite3_errmsg(db)))); - - return JSValue::encode(jsUndefined()); - } - - int status = sqlite3_db_config(db, SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION, 1, NULL); - assert(status == SQLITE_OK); - status = sqlite3_db_config(db, SQLITE_DBCONFIG_DEFENSIVE, 1, NULL); - assert(status == SQLITE_OK); - - auto count = constructor->databases.size(); - constructor->databases.append(db); - RELEASE_AND_RETURN(scope, JSValue::encode(jsNumber(count))); -} - -JSC_DEFINE_HOST_FUNCTION(jsSQLStatementCloseStatementFunction, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::CallFrame* callFrame)) -{ - - JSC::VM& vm = lexicalGlobalObject->vm(); - auto scope = DECLARE_THROW_SCOPE(vm); - - JSValue thisValue = callFrame->thisValue(); - JSSQLStatementConstructor* constructor = jsDynamicCast<JSSQLStatementConstructor*>(thisValue.getObject()); - - if (!constructor) { - throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, "Expected SQLStatement"_s)); - return JSValue::encode(jsUndefined()); - } - - if (callFrame->argumentCount() < 1) { - throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, "Expected 1 argument"_s)); - return JSValue::encode(jsUndefined()); - } - - JSValue dbNumber = callFrame->argument(0); - if (!dbNumber.isNumber()) { - throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, "Expected number"_s)); - return JSValue::encode(jsUndefined()); - } - - int dbIndex = dbNumber.toInt32(lexicalGlobalObject); - - if (dbIndex < 0 || dbIndex >= constructor->databases.size()) { - throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, "Invalid database handle"_s)); - return JSValue::encode(jsUndefined()); - } - - sqlite3* db = constructor->databases[dbIndex]; - // no-op if already closed - if (!db) { - return JSValue::encode(jsUndefined()); - } - - int statusCode = sqlite3_close_v2(db); - if (statusCode != SQLITE_OK) { - throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, WTF::String::fromUTF8(sqlite3_errmsg(db)))); - return JSValue::encode(jsUndefined()); - } - - constructor->databases[dbIndex] = nullptr; - return JSValue::encode(jsUndefined()); -} - -/* Hash table for constructor */ -static const HashTableValue JSSQLStatementConstructorTableValues[] = { - { "open"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { (intptr_t) static_cast<RawNativeFunction>(jsSQLStatementOpenStatementFunction), (intptr_t)(2) } }, - { "close"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { (intptr_t) static_cast<RawNativeFunction>(jsSQLStatementCloseStatementFunction), (intptr_t)(1) } }, - { "prepare"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { (intptr_t) static_cast<RawNativeFunction>(jsSQLStatementPrepareStatementFunction), (intptr_t)(2) } }, - { "run"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { (intptr_t) static_cast<RawNativeFunction>(jsSQLStatementExecuteFunction), (intptr_t)(3) } }, - { "isInTransaction"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { (intptr_t) static_cast<RawNativeFunction>(jsSQLStatementIsInTransactionFunction), (intptr_t)(1) } }, - { "loadExtension"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { (intptr_t) static_cast<RawNativeFunction>(jsSQLStatementLoadExtensionFunction), (intptr_t)(2) } }, - { "setCustomSQLite"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { (intptr_t) static_cast<RawNativeFunction>(jsSQLStatementSetCustomSQLite), (intptr_t)(1) } }, - { "serialize"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { (intptr_t) static_cast<RawNativeFunction>(jsSQLStatementSerialize), (intptr_t)(1) } }, - { "deserialize"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { (intptr_t) static_cast<RawNativeFunction>(jsSQLStatementDeserialize), (intptr_t)(2) } }, -}; - -const ClassInfo JSSQLStatementConstructor::s_info = { "SQLStatement"_s, nullptr, nullptr, nullptr, CREATE_METHOD_TABLE(JSSQLStatementConstructor) }; - -void JSSQLStatementConstructor::finishCreation(VM& vm) -{ - Base::finishCreation(vm); - auto* structure = JSSQLStatement::createStructure(vm, globalObject(), globalObject()->objectPrototype()); - auto* proto = JSSQLStatement::create(structure, reinterpret_cast<Zig::GlobalObject*>(globalObject()), nullptr, nullptr); - 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(); -} - -static inline JSC::JSValue constructResultObject(JSC::JSGlobalObject* lexicalGlobalObject, JSSQLStatement* castedThis); -static inline JSC::JSValue constructResultObject(JSC::JSGlobalObject* lexicalGlobalObject, JSSQLStatement* castedThis) -{ - auto& columnNames = castedThis->columnNames.data()->propertyNameVector(); - int count = columnNames.size(); - auto& vm = lexicalGlobalObject->vm(); - -#if SQL_USE_PROTOTYPE == 1 - JSC::JSObject* result = JSC::JSFinalObject::create(vm, castedThis->_prototype.get()->structure()); -#else - JSC::JSObject* result = JSC::JSFinalObject::create(vm, JSC::JSFinalObject::createStructure(vm, lexicalGlobalObject, lexicalGlobalObject->objectPrototype(), count)); -#endif - auto* stmt = castedThis->stmt; - - for (int i = 0; i < count; i++) { - auto name = columnNames[i]; - - switch (sqlite3_column_type(stmt, i)) { - case SQLITE_INTEGER: { - result->putDirect(vm, name, jsNumber(sqlite3_column_int(stmt, i)), 0); - break; - } - case SQLITE_FLOAT: { - result->putDirect(vm, name, jsNumber(sqlite3_column_double(stmt, i)), 0); - break; - } - case SQLITE_TEXT: { - size_t len = sqlite3_column_bytes(stmt, i); - const unsigned char* text = len > 0 ? sqlite3_column_text(stmt, i) : nullptr; - - if (len > 64) { - result->putDirect(vm, name, JSC::JSValue::decode(Bun__encoding__toStringUTF8(text, len, lexicalGlobalObject)), 0); - continue; - } - - result->putDirect(vm, name, jsString(vm, WTF::String::fromUTF8(text, len)), 0); - break; - } - case SQLITE_BLOB: { - size_t len = sqlite3_column_bytes(stmt, i); - const void* blob = len > 0 ? sqlite3_column_blob(stmt, i) : nullptr; - JSC::JSUint8Array* array = JSC::JSUint8Array::createUninitialized(lexicalGlobalObject, lexicalGlobalObject->m_typedArrayUint8.get(lexicalGlobalObject), len); - memcpy(array->vector(), blob, len); - result->putDirect(vm, name, array, 0); - break; - } - default: { - result->putDirect(vm, name, jsNull(), 0); - break; - } - } - } - - return JSValue(result); -} - -static inline JSC::JSArray* constructResultRow(JSC::JSGlobalObject* lexicalGlobalObject, JSSQLStatement* castedThis, ObjectInitializationScope& scope, JSC::GCDeferralContext* deferralContext); -static inline JSC::JSArray* constructResultRow(JSC::JSGlobalObject* lexicalGlobalObject, JSSQLStatement* castedThis, ObjectInitializationScope& scope, JSC::GCDeferralContext* deferralContext) -{ - int count = castedThis->columnNames.size(); - auto& vm = lexicalGlobalObject->vm(); - - JSC::JSArray* result = JSArray::create(vm, lexicalGlobalObject->arrayStructureForIndexingTypeDuringAllocation(ArrayWithContiguous), count); - auto* stmt = castedThis->stmt; - - for (int i = 0; i < count; i++) { - - switch (sqlite3_column_type(stmt, i)) { - case SQLITE_INTEGER: { - result->initializeIndex(scope, i, jsNumber(sqlite3_column_int(stmt, i))); - break; - } - case SQLITE_FLOAT: { - result->initializeIndex(scope, i, jsNumber(sqlite3_column_double(stmt, i))); - break; - } - case SQLITE_TEXT: { - size_t len = sqlite3_column_bytes(stmt, i); - const unsigned char* text = len > 0 ? sqlite3_column_text(stmt, i) : nullptr; - if (UNLIKELY(text == nullptr || len == 0)) { - result->initializeIndex(scope, i, jsEmptyString(vm)); - continue; - } - result->initializeIndex(scope, i, len < 64 ? jsString(vm, WTF::String::fromUTF8(text, len)) : JSC::JSValue::decode(Bun__encoding__toStringUTF8(text, len, lexicalGlobalObject))); - break; - } - case SQLITE_BLOB: { - size_t len = sqlite3_column_bytes(stmt, i); - const void* blob = len > 0 ? sqlite3_column_blob(stmt, i) : nullptr; - JSC::JSUint8Array* array = JSC::JSUint8Array::createUninitialized(lexicalGlobalObject, lexicalGlobalObject->m_typedArrayUint8.get(lexicalGlobalObject), len); - memcpy(array->vector(), blob, len); - result->initializeIndex(scope, i, array); - break; - } - default: { - result->initializeIndex(scope, i, jsNull()); - break; - } - } - } - - return result; -} - -static inline JSC::JSArray* constructResultRow(JSC::JSGlobalObject* lexicalGlobalObject, JSSQLStatement* castedThis, ObjectInitializationScope& scope) -{ - return constructResultRow(lexicalGlobalObject, castedThis, scope, nullptr); -} - -static void initializeColumnNames(JSC::JSGlobalObject* lexicalGlobalObject, JSSQLStatement* castedThis) -{ - castedThis->hasExecuted = true; - JSC::VM& vm = lexicalGlobalObject->vm(); - auto& names = castedThis->columnNames; - - auto* stmt = castedThis->stmt; - - int count = sqlite3_column_count(stmt); - if (count == 0) - return; - JSC::ObjectInitializationScope initializationScope(vm); - JSC::JSObject* object = JSC::constructEmptyObject(lexicalGlobalObject, lexicalGlobalObject->objectPrototype(), count); - - for (int i = 0; i < count; i++) { - const char* name = sqlite3_column_name(stmt, i); - - if (name == nullptr) - break; - - size_t len = strlen(name); - if (len == 0) - break; - - auto wtfString = WTF::String::fromUTF8(name, len); - auto str = JSValue(jsString(vm, wtfString)); - auto key = str.toPropertyKey(lexicalGlobalObject); - 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; - } - } - } - - object->putDirect(vm, key, primitive, 0); - names.add(key); - } - castedThis->_prototype.set(vm, castedThis, object); -} - -JSC_DEFINE_HOST_FUNCTION(jsSQLStatementExecuteStatementFunctionAll, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::CallFrame* callFrame)) -{ - - JSC::VM& vm = lexicalGlobalObject->vm(); - auto scope = DECLARE_THROW_SCOPE(vm); - auto castedThis = jsDynamicCast<JSSQLStatement*>(callFrame->thisValue()); - - CHECK_THIS - - auto* stmt = castedThis->stmt; - CHECK_PREPARED - int statusCode = sqlite3_reset(stmt); - - if (UNLIKELY(statusCode != SQLITE_OK)) { - throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, WTF::String::fromUTF8(sqlite3_errstr(statusCode)))); - return JSValue::encode(jsUndefined()); - } - - if (callFrame->argumentCount() > 0) { - auto arg0 = callFrame->argument(0); - DO_REBIND(arg0); - } - - if (!castedThis->hasExecuted) { - initializeColumnNames(lexicalGlobalObject, castedThis); - } - - auto& columnNames = castedThis->columnNames; - - int status = sqlite3_step(stmt); - - size_t columnCount = columnNames.size(); - int counter = 0; - - if (status == SQLITE_ROW) { - // this is a count from UPDATE or another query like that - if (columnCount == 0) { - RELEASE_AND_RETURN(scope, JSC::JSValue::encode(jsNumber(sqlite3_changes(castedThis->db)))); - } - - JSC::JSArray* resultArray = JSC::constructEmptyArray(lexicalGlobalObject, nullptr, 0); - { - JSC::ObjectInitializationScope initializationScope(vm); - JSC::GCDeferralContext deferralContext(vm); - - while (status == SQLITE_ROW) { - JSC::JSValue result = constructResultObject(lexicalGlobalObject, castedThis); - resultArray->push(lexicalGlobalObject, result); - status = sqlite3_step(stmt); - } - } - - if (UNLIKELY(status != SQLITE_DONE)) { - throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, WTF::String::fromUTF8(sqlite3_errstr(status)))); - sqlite3_reset(stmt); - return JSValue::encode(jsUndefined()); - } - - RELEASE_AND_RETURN(scope, JSC::JSValue::encode(resultArray)); - } else if (status == SQLITE_DONE) { - if (columnCount == 0) { - RELEASE_AND_RETURN(scope, JSValue::encode(jsNumber(0))); - } - - RELEASE_AND_RETURN(scope, JSValue::encode(JSC::constructEmptyArray(lexicalGlobalObject, nullptr, 0))); - } else { - throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, WTF::String::fromUTF8(sqlite3_errstr(status)))); - sqlite3_reset(stmt); - return JSValue::encode(jsUndefined()); - } -} - -JSC_DEFINE_HOST_FUNCTION(jsSQLStatementExecuteStatementFunctionGet, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::CallFrame* callFrame)) -{ - - JSC::VM& vm = lexicalGlobalObject->vm(); - auto scope = DECLARE_THROW_SCOPE(vm); - auto castedThis = jsDynamicCast<JSSQLStatement*>(callFrame->thisValue()); - - CHECK_THIS - - auto* stmt = castedThis->stmt; - CHECK_PREPARED - - int statusCode = sqlite3_reset(stmt); - if (UNLIKELY(statusCode != SQLITE_OK)) { - throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, WTF::String::fromUTF8(sqlite3_errstr(statusCode)))); - return JSValue::encode(jsUndefined()); - } - - if (callFrame->argumentCount() > 0) { - auto arg0 = callFrame->argument(0); - DO_REBIND(arg0); - } - - if (!castedThis->hasExecuted) { - initializeColumnNames(lexicalGlobalObject, castedThis); - } - - auto& columnNames = castedThis->columnNames; - // { - // JSC::ObjectInitializationScope initializationScope(vm); - // array = - // } - int status = sqlite3_step(stmt); - - size_t columnCount = columnNames.size(); - int counter = 0; - - if (status == SQLITE_ROW) { - RELEASE_AND_RETURN(scope, JSC::JSValue::encode(constructResultObject(lexicalGlobalObject, castedThis))); - } else if (status == SQLITE_DONE) { - RELEASE_AND_RETURN(scope, JSValue::encode(JSC::jsNull())); - } else { - throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, WTF::String::fromUTF8(sqlite3_errstr(status)))); - sqlite3_reset(stmt); - return JSValue::encode(jsUndefined()); - } -} - -JSC_DEFINE_HOST_FUNCTION(jsSQLStatementExecuteStatementFunctionRows, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::CallFrame* callFrame)) -{ - - JSC::VM& vm = lexicalGlobalObject->vm(); - auto scope = DECLARE_THROW_SCOPE(vm); - auto castedThis = jsDynamicCast<JSSQLStatement*>(callFrame->thisValue()); - - CHECK_THIS; - - auto* stmt = castedThis->stmt; - CHECK_PREPARED - - int statusCode = sqlite3_reset(stmt); - if (UNLIKELY(statusCode != SQLITE_OK)) { - throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, WTF::String::fromUTF8(sqlite3_errstr(statusCode)))); - sqlite3_reset(stmt); - return JSValue::encode(jsUndefined()); - } - - int count = callFrame->argumentCount(); - if (count > 0) { - auto arg0 = callFrame->argument(0); - DO_REBIND(arg0); - } - - if (!castedThis->hasExecuted) { - initializeColumnNames(lexicalGlobalObject, castedThis); - } - - auto& columnNames = castedThis->columnNames; - int status = sqlite3_step(stmt); - - size_t columnCount = columnNames.size(); - int counter = 0; - - if (status == SQLITE_ROW) { - // this is a count from UPDATE or another query like that - if (columnCount == 0) { - RELEASE_AND_RETURN(scope, JSC::JSValue::encode(jsNumber(sqlite3_changes(castedThis->db)))); - } - - JSC::ObjectInitializationScope initializationScope(vm); - JSC::GCDeferralContext deferralContext(vm); - - JSC::JSArray* resultArray = JSC::constructEmptyArray(lexicalGlobalObject, nullptr, 0); - { - - while (status == SQLITE_ROW) { - JSC::JSValue result = constructResultRow(lexicalGlobalObject, castedThis, initializationScope, &deferralContext); - resultArray->push(lexicalGlobalObject, result); - status = sqlite3_step(stmt); - } - } - - if (UNLIKELY(status != SQLITE_DONE)) { - throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, WTF::String::fromUTF8(sqlite3_errstr(status)))); - sqlite3_reset(stmt); - return JSValue::encode(jsUndefined()); - } - - // sqlite3_reset(stmt); - RELEASE_AND_RETURN(scope, JSC::JSValue::encode(resultArray)); - } else if (status == SQLITE_DONE) { - if (columnCount == 0) { - RELEASE_AND_RETURN(scope, JSValue::encode(jsNumber(0))); - } - - RELEASE_AND_RETURN(scope, JSValue::encode(JSC::constructEmptyArray(lexicalGlobalObject, nullptr, 0))); - } else { - throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, WTF::String::fromUTF8(sqlite3_errstr(status)))); - sqlite3_reset(stmt); - return JSValue::encode(jsUndefined()); - } -} - -JSC_DEFINE_HOST_FUNCTION(jsSQLStatementExecuteStatementFunctionRun, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::CallFrame* callFrame)) -{ - - JSC::VM& vm = lexicalGlobalObject->vm(); - auto scope = DECLARE_THROW_SCOPE(vm); - auto castedThis = jsDynamicCast<JSSQLStatement*>(callFrame->thisValue()); - - CHECK_THIS - - auto* stmt = castedThis->stmt; - CHECK_PREPARED - - int statusCode = sqlite3_reset(stmt); - if (UNLIKELY(statusCode != SQLITE_OK)) { - throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, WTF::String::fromUTF8(sqlite3_errstr(statusCode)))); - return JSValue::encode(jsUndefined()); - } - - if (callFrame->argumentCount() > 0) { - auto arg0 = callFrame->argument(0); - DO_REBIND(arg0); - } - - if (!castedThis->hasExecuted) { - initializeColumnNames(lexicalGlobalObject, castedThis); - } - - int status = sqlite3_step(stmt); - - if (status == SQLITE_ROW || status == SQLITE_DONE) { - // sqlite3_reset(stmt); - RELEASE_AND_RETURN(scope, JSC::JSValue::encode(jsUndefined())); - } else { - sqlite3_reset(stmt); - throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, WTF::String::fromUTF8(sqlite3_errstr(status)))); - return JSValue::encode(jsUndefined()); - } -} - -JSC_DEFINE_HOST_FUNCTION(jsSQLStatementToStringFunction, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::CallFrame* callFrame)) -{ - JSC::VM& vm = lexicalGlobalObject->vm(); - JSSQLStatement* castedThis = jsDynamicCast<JSSQLStatement*>(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); - JSString* 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, EncodedJSValue thisValue, PropertyName attributeName)) -{ - JSC::VM& vm = lexicalGlobalObject->vm(); - JSSQLStatement* castedThis = jsDynamicCast<JSSQLStatement*>(JSValue::decode(thisValue)); - auto scope = DECLARE_THROW_SCOPE(vm); - CHECK_THIS - - if (!castedThis->hasExecuted) { - initializeColumnNames(lexicalGlobalObject, castedThis); - } - - auto* array = castedThis->_columnNames.get(); - if (array == nullptr) { - if (castedThis->columnNames.size() > 0) { - array = ownPropertyKeys(lexicalGlobalObject, castedThis->_prototype.get(), PropertyNameMode::Strings, DontEnumPropertiesMode::Exclude, CachedPropertyNamesKind::Keys); - } else { - array = JSC::constructEmptyArray(lexicalGlobalObject, nullptr, 0); - } - - castedThis->_columnNames.set(vm, castedThis, array); - } - - return JSC::JSValue::encode(array); -} - -JSC_DEFINE_CUSTOM_GETTER(jsSqlStatementGetColumnCount, (JSGlobalObject * lexicalGlobalObject, EncodedJSValue thisValue, PropertyName attributeName)) -{ - JSC::VM& vm = lexicalGlobalObject->vm(); - JSSQLStatement* castedThis = jsDynamicCast<JSSQLStatement*>(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, EncodedJSValue thisValue, PropertyName attributeName)) -{ - JSC::VM& vm = lexicalGlobalObject->vm(); - JSSQLStatement* castedThis = jsDynamicCast<JSSQLStatement*>(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_HOST_FUNCTION(jsSQLStatementFunctionFinalize, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::CallFrame* callFrame)) -{ - JSC::VM& vm = lexicalGlobalObject->vm(); - JSSQLStatement* castedThis = jsDynamicCast<JSSQLStatement*>(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, nullptr, nullptr, nullptr, CREATE_METHOD_TABLE(JSSQLStatement) }; - -/* Hash table for prototype */ -static const HashTableValue JSSQLStatementTableValues[] = { - { "run"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { (intptr_t) static_cast<RawNativeFunction>(jsSQLStatementExecuteStatementFunctionRun), (intptr_t)(1) } }, - { "get"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { (intptr_t) static_cast<RawNativeFunction>(jsSQLStatementExecuteStatementFunctionGet), (intptr_t)(1) } }, - { "all"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { (intptr_t) static_cast<RawNativeFunction>(jsSQLStatementExecuteStatementFunctionAll), (intptr_t)(1) } }, - { "values"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { (intptr_t) static_cast<RawNativeFunction>(jsSQLStatementExecuteStatementFunctionRows), (intptr_t)(1) } }, - { "finalize"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { (intptr_t) static_cast<RawNativeFunction>(jsSQLStatementFunctionFinalize), (intptr_t)(0) } }, - { "toString"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { (intptr_t) static_cast<RawNativeFunction>(jsSQLStatementToStringFunction), (intptr_t)(0) } }, - { "columns"_s, static_cast<unsigned>(JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::CustomAccessor), NoIntrinsic, { (intptr_t) static_cast<PropertySlot::GetValueFunc>(jsSqlStatementGetColumnNames), (intptr_t) static_cast<PutPropertySlot::PutValueFunc>(0) } }, - { "columnsCount"_s, static_cast<unsigned>(JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::CustomAccessor), NoIntrinsic, { (intptr_t) static_cast<PropertySlot::GetValueFunc>(jsSqlStatementGetColumnCount), (intptr_t) static_cast<PutPropertySlot::PutValueFunc>(0) } }, - { "paramsCount"_s, static_cast<unsigned>(JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::CustomAccessor), NoIntrinsic, { (intptr_t) static_cast<PropertySlot::GetValueFunc>(jsSqlStatementGetParamCount), (intptr_t) static_cast<PutPropertySlot::PutValueFunc>(0) } }, -}; - -void JSSQLStatement::finishCreation(VM& vm) -{ - Base::finishCreation(vm); - reifyStaticProperties(vm, JSSQLStatement::info(), JSSQLStatementTableValues, *this); -} - -JSSQLStatement::~JSSQLStatement() -{ - if (this->stmt) { - sqlite3_finalize(this->stmt); - } -} - -JSC::JSValue JSSQLStatement::rebind(JSC::JSGlobalObject* lexicalGlobalObject, JSC::JSValue values, bool clone) -{ - JSC::VM& vm = lexicalGlobalObject->vm(); - auto scope = DECLARE_THROW_SCOPE(vm); - auto* stmt = this->stmt; - auto val = rebindStatement(lexicalGlobalObject, values, scope, stmt, clone); - if (val.isNumber()) { - RELEASE_AND_RETURN(scope, val); - } else { - return val; - } -} - -template<typename Visitor> -void JSSQLStatement::visitChildrenImpl(JSCell* cell, Visitor& visitor) -{ - JSSQLStatement* thisObject = jsCast<JSSQLStatement*>(cell); - ASSERT_GC_OBJECT_INHERITS(thisObject, info()); - Base::visitChildren(thisObject, visitor); - visitor.append(thisObject->_columnNames); - visitor.append(thisObject->_prototype); -} - -DEFINE_VISIT_CHILDREN(JSSQLStatement); -} |