diff options
-rw-r--r-- | src/bun.js/bindings/sqlite/JSSQLStatement.cpp | 135 | ||||
-rw-r--r-- | test/bun.js/sqlite-cross-process.js | 45 | ||||
-rw-r--r-- | test/bun.js/sqlite.test.js | 26 |
3 files changed, 142 insertions, 64 deletions
diff --git a/src/bun.js/bindings/sqlite/JSSQLStatement.cpp b/src/bun.js/bindings/sqlite/JSSQLStatement.cpp index efcb12355..39f1f42ed 100644 --- a/src/bun.js/bindings/sqlite/JSSQLStatement.cpp +++ b/src/bun.js/bindings/sqlite/JSSQLStatement.cpp @@ -727,9 +727,11 @@ JSC_DEFINE_HOST_FUNCTION(jsSQLStatementExecuteFunction, (JSC::JSGlobalObject * l thisObject->databases[handle]->version++; } - // 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) { + while (rc == SQLITE_ROW) { + rc = sqlite3_step(statement); + } + + if (rc != SQLITE_DONE) { 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 @@ -1206,44 +1208,46 @@ JSC_DEFINE_HOST_FUNCTION(jsSQLStatementExecuteStatementFunctionAll, (JSC::JSGlob } size_t columnCount = castedThis->columnNames->size(); - int counter = 0; + JSValue result = jsUndefined(); 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->version_db->db)))); - } - - JSC::JSArray* resultArray = JSC::constructEmptyArray(lexicalGlobalObject, nullptr, 0); - { - JSC::ObjectInitializationScope initializationScope(vm); - JSC::GCDeferralContext deferralContext(vm); + result = jsNumber(sqlite3_changes(castedThis->version_db->db)); while (status == SQLITE_ROW) { - JSC::JSValue result = constructResultObject(lexicalGlobalObject, castedThis); - resultArray->push(lexicalGlobalObject, result); status = sqlite3_step(stmt); } - } + } else { - if (UNLIKELY(status != SQLITE_DONE)) { - throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, WTF::String::fromUTF8(sqlite3_errstr(status)))); - sqlite3_reset(stmt); - return JSValue::encode(jsUndefined()); - } + JSC::JSArray* resultArray = JSC::constructEmptyArray(lexicalGlobalObject, nullptr, 0); + { + JSC::ObjectInitializationScope initializationScope(vm); + JSC::GCDeferralContext deferralContext(vm); - RELEASE_AND_RETURN(scope, JSC::JSValue::encode(resultArray)); + while (status == SQLITE_ROW) { + JSC::JSValue result = constructResultObject(lexicalGlobalObject, castedThis); + resultArray->push(lexicalGlobalObject, result); + status = sqlite3_step(stmt); + } + } + result = resultArray; + } } else if (status == SQLITE_DONE) { if (columnCount == 0) { - RELEASE_AND_RETURN(scope, JSValue::encode(jsNumber(0))); + result = jsNumber(sqlite3_changes(castedThis->version_db->db)); + } else { + result = JSC::constructEmptyArray(lexicalGlobalObject, nullptr, 0); } + } - RELEASE_AND_RETURN(scope, JSValue::encode(JSC::constructEmptyArray(lexicalGlobalObject, nullptr, 0))); - } else { + 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(result)); } JSC_DEFINE_HOST_FUNCTION(jsSQLStatementExecuteStatementFunctionGet, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::CallFrame* callFrame)) @@ -1278,13 +1282,16 @@ JSC_DEFINE_HOST_FUNCTION(jsSQLStatementExecuteStatementFunctionGet, (JSC::JSGlob initializeColumnNames(lexicalGlobalObject, castedThis); } - size_t columnCount = castedThis->columnNames->size(); - int counter = 0; - + JSValue result = jsNull(); 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())); + result = constructResultObject(lexicalGlobalObject, castedThis); + while (status == SQLITE_ROW) { + status = sqlite3_step(stmt); + } + } + + if (status == SQLITE_DONE) { + RELEASE_AND_RETURN(scope, JSValue::encode(result)); } else { throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, WTF::String::fromUTF8(sqlite3_errstr(status)))); sqlite3_reset(stmt); @@ -1298,8 +1305,11 @@ static JSC_DECLARE_JIT_OPERATION_WITHOUT_WTF_INTERNAL(jsSQLStatementExecuteState JSC_DEFINE_JIT_OPERATION(jsSQLStatementExecuteStatementFunctionGetWithoutTypeChecking, EncodedJSValue, (JSC::JSGlobalObject * lexicalGlobalObject, JSSQLStatement* castedThis)) { - - JSC::VM& vm = lexicalGlobalObject->vm(); + VM& vm = JSC::getVM(lexicalGlobalObject); + IGNORE_WARNINGS_BEGIN("frame-address") + CallFrame* callFrame = DECLARE_CALL_FRAME(vm); + IGNORE_WARNINGS_END + JSC::JITOperationPrologueCallFrameTracer tracer(vm, callFrame); auto scope = DECLARE_THROW_SCOPE(vm); auto* stmt = castedThis->stmt; @@ -1320,13 +1330,16 @@ JSC_DEFINE_JIT_OPERATION(jsSQLStatementExecuteStatementFunctionGetWithoutTypeChe initializeColumnNames(lexicalGlobalObject, castedThis); } - size_t columnCount = castedThis->columnNames->size(); - int counter = 0; - + JSValue result = jsNull(); 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())); + result = constructResultObject(lexicalGlobalObject, castedThis); + while (status == SQLITE_ROW) { + status = sqlite3_step(stmt); + } + } + + if (status == SQLITE_DONE) { + RELEASE_AND_RETURN(scope, JSValue::encode(result)); } else { throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, WTF::String::fromUTF8(sqlite3_errstr(status)))); sqlite3_reset(stmt); @@ -1378,46 +1391,42 @@ JSC_DEFINE_HOST_FUNCTION(jsSQLStatementExecuteStatementFunctionRows, (JSC::JSGlo } size_t columnCount = castedThis->columnNames->size(); - int counter = 0; - + JSValue result = jsNull(); 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->version_db->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()); - } + result = jsNumber(sqlite3_column_count(stmt)); - // 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))); + } else { + JSC::ObjectInitializationScope initializationScope(vm); + JSC::GCDeferralContext deferralContext(vm); + + JSC::JSArray* resultArray = JSC::constructEmptyArray(lexicalGlobalObject, nullptr, 0); + { + + while (status == SQLITE_ROW) { + JSC::JSValue row = constructResultRow(lexicalGlobalObject, castedThis, initializationScope, &deferralContext); + resultArray->push(lexicalGlobalObject, row); + status = sqlite3_step(stmt); + } + } + + result = resultArray; } + } - RELEASE_AND_RETURN(scope, JSValue::encode(JSC::constructEmptyArray(lexicalGlobalObject, nullptr, 0))); - } else { + 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(result)); } JSC_DEFINE_HOST_FUNCTION(jsSQLStatementExecuteStatementFunctionRun, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::CallFrame* callFrame)) diff --git a/test/bun.js/sqlite-cross-process.js b/test/bun.js/sqlite-cross-process.js new file mode 100644 index 000000000..d5b9b87b5 --- /dev/null +++ b/test/bun.js/sqlite-cross-process.js @@ -0,0 +1,45 @@ +// https://github.com/oven-sh/bun/issues/1366 +import { Database } from "bun:sqlite"; +import { rmSync } from "fs"; + +const dir = process.env.SQLITE_DIR; + +rmSync(dir + "get-persist.sqlite", { force: true }); + +var db = Database.open(dir + "get-persist.sqlite", { create: true }); + +// Note, I've played with various values and it doesn't seem to change +// the behavior. The "beter-sqlite3" npm package does not exhibit this +// bug, so it doesn't seem to be a general SQLite thing. +db.run(`PRAGMA journal_mode = WAL`); +db.run(`PRAGMA synchrounous = NORMAL`); + +db.run( + `CREATE TABLE IF NOT EXISTS examples ( + id TEXT PRIMARY KEY + )`, +); + +// This persists, but if you place this call +db.run( + ` + INSERT INTO examples + VALUES ('hello') + ON CONFLICT (id) DO + UPDATE SET id='hello' + RETURNING id + `, +); + +db.query(`SELECT id FROM examples WHERE id='hello'`).get().id; +db.query( + ` +INSERT INTO examples +VALUES ('world') +ON CONFLICT (id) DO + UPDATE SET id='world' +RETURNING id +`, +).get(); + +process.exit(0); diff --git a/test/bun.js/sqlite.test.js b/test/bun.js/sqlite.test.js index e5f83fe4d..f78d3b481 100644 --- a/test/bun.js/sqlite.test.js +++ b/test/bun.js/sqlite.test.js @@ -1,6 +1,9 @@ import { expect, it, describe } from "bun:test"; import { Database, constants } from "bun:sqlite"; -import { existsSync, fstat, writeFileSync } from "fs"; +import { existsSync, fstat, realpathSync, rmSync, writeFileSync } from "fs"; +import { spawnSync } from "bun"; +import { bunExe } from "bunExe"; +import { tmpdir } from "os"; var encode = (text) => new TextEncoder().encode(text); it("Database.open", () => { @@ -55,6 +58,27 @@ it("Database.open", () => { new Database().close(); }); +it("upsert cross-process, see #1366", () => { + const dir = realpathSync(tmpdir()) + "/"; + const { exitCode } = spawnSync( + [bunExe(), import.meta.dir + "/sqlite-cross-process.js"], + { + env: { + SQLITE_DIR: dir, + }, + stderr: "inherit", + }, + ); + expect(exitCode).toBe(0); + + const db2 = Database.open(dir + "get-persist.sqlite"); + + expect(db2.query(`SELECT id FROM examples`).all()).toEqual([ + { id: "hello" }, + { id: "world" }, + ]); +}); + it("creates", () => { const db = Database.open(":memory:"); db.exec( |