Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 20 additions & 20 deletions docs/runtime/sqlite.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -486,45 +486,45 @@ const results = query.all("hello", "goodbye");

SQLite supports signed 64-bit integers, but JavaScript only supports signed 52-bit integers or arbitrary-precision integers with `bigint`.

`bigint` input is supported everywhere, but by default `bun:sqlite` returns integers as `number` types. If you need to handle integers larger than 2^53, set the `safeIntegers` option to `true` when creating a `Database` instance. This also validates that `bigint` values passed to `bun:sqlite` do not exceed 64 bits.
`bigint` input is supported everywhere, but by default `bun:sqlite` returns integers as `number` types. If you need to handle integers larger than 2^53, set the `safeIntegers` option to `true` when creating a `Database` instance.
Comment thread
coderabbitai[bot] marked this conversation as resolved.

### `safeIntegers: true`

When `safeIntegers` is `true`, `bun:sqlite` returns integers as `bigint` types:
Regardless of the `safeIntegers` setting, `bun:sqlite` throws a `RangeError` if a `bigint` value in a bound parameter does not fit in a signed 64-bit integer:

```ts db.ts icon="/icons/typescript.svg" highlight={3}
```ts db.ts icon="/icons/typescript.svg"
import { Database } from "bun:sqlite";

const db = new Database(":memory:", { safeIntegers: true });
const query = db.query(`SELECT ${BigInt(Number.MAX_SAFE_INTEGER) + 102n} as max_int`);
const result = query.get();
const db = new Database(":memory:");
db.run("CREATE TABLE test (id INTEGER PRIMARY KEY, value INTEGER)");

console.log(result.max_int);
const query = db.query("INSERT INTO test (value) VALUES ($value)");

try {
query.run({ $value: BigInt(Number.MAX_SAFE_INTEGER) ** 2n });
} catch (e) {
console.log(e.message);
}
```

```txt
9007199254741093n
BigInt value '81129638414606663681390495662081' is out of range
```

When `safeIntegers` is `true`, `bun:sqlite` throws an error if a `bigint` value in a bound parameter exceeds 64 bits:
### `safeIntegers: true`

When `safeIntegers` is `true`, `bun:sqlite` returns integers as `bigint` types:

```ts db.ts icon="/icons/typescript.svg" highlight={3}
import { Database } from "bun:sqlite";

const db = new Database(":memory:", { safeIntegers: true });
db.run("CREATE TABLE test (id INTEGER PRIMARY KEY, value INTEGER)");

const query = db.query("INSERT INTO test (value) VALUES ($value)");
const query = db.query(`SELECT ${BigInt(Number.MAX_SAFE_INTEGER) + 102n} as max_int`);
const result = query.get();

try {
query.run({ $value: BigInt(Number.MAX_SAFE_INTEGER) ** 2n });
} catch (e) {
console.log(e.message);
}
console.log(result.max_int);
```

```txt
BigInt value '81129638414606663681390495662081' is out of range
9007199254741093n
```

### `safeIntegers: false` (default)
Expand Down
114 changes: 53 additions & 61 deletions src/jsc/bindings/sqlite/JSSQLStatement.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -506,6 +506,7 @@ class JSSQLStatement : public JSC::JSDestructibleObject {

bool need_update() { return version_db->version.load() != version; }
void update_version() { version = version_db->version.load(); }
void updateColumnNamesIfNeeded(JSC::JSGlobalObject*);

~JSSQLStatement();

Expand Down Expand Up @@ -851,13 +852,22 @@ static void initializeColumnNames(JSC::JSGlobalObject* lexicalGlobalObject, JSSQ
castedThis->_prototype.set(vm, castedThis, object);
}

void JSSQLStatement::updateColumnNamesIfNeeded(JSC::JSGlobalObject* lexicalGlobalObject)
{
// sqlite3_step() may transparently re-prepare under a changed result shape.
const bool reprepared = sqlite3_stmt_status(stmt, SQLITE_STMTSTATUS_REPREPARE, 1) > 0;
if (!hasExecuted || reprepared || need_update()) {
initializeColumnNames(lexicalGlobalObject, this);
}
}
Comment thread
robobun marked this conversation as resolved.

void JSSQLStatement::destroy(JSC::JSCell* cell)
{
JSSQLStatement* thisObject = static_cast<JSSQLStatement*>(cell);
thisObject->~JSSQLStatement();
}

static inline bool rebindValue(JSC::JSGlobalObject* lexicalGlobalObject, sqlite3* db, sqlite3_stmt* stmt, int i, JSC::JSValue value, JSC::ThrowScope& scope, bool isSafeInteger)
static inline bool rebindValue(JSC::JSGlobalObject* lexicalGlobalObject, sqlite3* db, sqlite3_stmt* stmt, int i, JSC::JSValue value, JSC::ThrowScope& scope)
{
auto throwSQLiteError = [&]() -> void {
throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, WTF::String::fromUTF8(sqlite3_errmsg(db))));
Expand Down Expand Up @@ -902,30 +912,24 @@ static inline bool rebindValue(JSC::JSGlobalObject* lexicalGlobalObject, sqlite3
}

if (roped->is8Bit() && roped->containsOnlyASCII()) {
CHECK_BIND(sqlite3_bind_text(stmt, i, reinterpret_cast<const char*>(roped->span8().data()), roped->length(), SQLITE_TRANSIENT));
} else if (!roped->is8Bit()) {
CHECK_BIND(sqlite3_bind_text16(stmt, i, roped->span16().data(), roped->length() * 2, SQLITE_TRANSIENT));
CHECK_BIND(sqlite3_bind_text64(stmt, i, reinterpret_cast<const char*>(roped->span8().data()), roped->length(), SQLITE_TRANSIENT, SQLITE_UTF8));
} else {
// Not sqlite3_bind_text16: SQLite's UTF-16 decoder stores lone surrogates as ill-formed UTF-8.
auto utf8 = roped->utf8();
CHECK_BIND(sqlite3_bind_text(stmt, i, utf8.data(), utf8.length(), SQLITE_TRANSIENT));
CHECK_BIND(sqlite3_bind_text64(stmt, i, utf8.data(), utf8.length(), SQLITE_TRANSIENT, SQLITE_UTF8));
}

} else if (value.isHeapBigInt()) [[unlikely]] {
if (!isSafeInteger) {
JSBigInt* bigInt = value.asHeapBigInt();
const auto min = JSBigInt::compare(bigInt, std::numeric_limits<int64_t>::min());
const auto max = JSBigInt::compare(bigInt, std::numeric_limits<int64_t>::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 {
JSBigInt* bigInt = value.asHeapBigInt();
const auto min = JSBigInt::compare(bigInt, std::numeric_limits<int64_t>::min());
const auto max = JSBigInt::compare(bigInt, std::numeric_limits<int64_t>::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;
}
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 = dynamicDowncast<JSC::JSArrayBufferView>(value)) {
CHECK_BIND(sqlite3_bind_blob(stmt, i, buffer->vector(), buffer->byteLength(), SQLITE_TRANSIENT));
} else {
Expand All @@ -937,7 +941,7 @@ static inline bool rebindValue(JSC::JSGlobalObject* lexicalGlobalObject, sqlite3
#undef CHECK_BIND
}

static JSC::JSValue rebindObject(JSC::JSGlobalObject* globalObject, SQLiteBindingsMap& bindings, JSC::JSObject* target, JSC::ThrowScope& scope, sqlite3* db, sqlite3_stmt* stmt, bool safeIntegers, JSSQLStatement* statement)
static JSC::JSValue rebindObject(JSC::JSGlobalObject* globalObject, SQLiteBindingsMap& bindings, JSC::JSObject* target, JSC::ThrowScope& scope, sqlite3* db, sqlite3_stmt* stmt, JSSQLStatement* statement)
{
int count = 0;

Expand Down Expand Up @@ -1015,7 +1019,7 @@ static JSC::JSValue rebindObject(JSC::JSGlobalObject* globalObject, SQLiteBindin
}
RETURN_IF_EXCEPTION(scope, {});

if (!rebindValue(globalObject, db, stmt, i + 1, value, scope, safeIntegers)) {
if (!rebindValue(globalObject, db, stmt, i + 1, value, scope)) {
return {};
}

Expand All @@ -1042,7 +1046,7 @@ static JSC::JSValue rebindObject(JSC::JSGlobalObject* globalObject, SQLiteBindin

RETURN_IF_EXCEPTION(scope, {});

if (!rebindValue(globalObject, db, stmt, i + 1, value, scope, safeIntegers)) {
if (!rebindValue(globalObject, db, stmt, i + 1, value, scope)) {
return {};
}

Expand Down Expand Up @@ -1070,7 +1074,7 @@ static JSC::JSValue rebindObject(JSC::JSGlobalObject* globalObject, SQLiteBindin

RETURN_IF_EXCEPTION(scope, {});

if (!rebindValue(globalObject, db, stmt, i + 1, value, scope, safeIntegers)) {
if (!rebindValue(globalObject, db, stmt, i + 1, value, scope)) {
return {};
}

Expand Down Expand Up @@ -1105,7 +1109,7 @@ static JSC::JSValue rebindObject(JSC::JSGlobalObject* globalObject, SQLiteBindin
if (!statementStillAlive())
return {};

if (!rebindValue(globalObject, db, stmt, i + 1, value, scope, safeIntegers)) {
if (!rebindValue(globalObject, db, stmt, i + 1, value, scope)) {
return {};
}

Expand All @@ -1117,15 +1121,15 @@ static JSC::JSValue rebindObject(JSC::JSGlobalObject* globalObject, SQLiteBindin
return jsNumber(count);
}

static JSC::JSValue rebindStatement(JSC::JSGlobalObject* lexicalGlobalObject, JSC::JSValue values, JSC::ThrowScope& scope, sqlite3* db, sqlite3_stmt* stmt, SQLiteBindingsMap& bindings, bool safeIntegers, JSSQLStatement* statement)
static JSC::JSValue rebindStatement(JSC::JSGlobalObject* lexicalGlobalObject, JSC::JSValue values, JSC::ThrowScope& scope, sqlite3* db, sqlite3_stmt* stmt, SQLiteBindingsMap& bindings, JSSQLStatement* statement)
{
sqlite3_clear_bindings(stmt);
JSC::JSArray* array = dynamicDowncast<JSC::JSArray>(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, safeIntegers, statement);
auto res = rebindObject(lexicalGlobalObject, bindings, object, scope, db, stmt, statement);
RETURN_IF_EXCEPTION(scope, {});
return res;
}
Expand Down Expand Up @@ -1161,7 +1165,7 @@ static JSC::JSValue rebindStatement(JSC::JSGlobalObject* lexicalGlobalObject, JS
if (!value)
value = JSC::jsUndefined();
}
if (!rebindValue(lexicalGlobalObject, db, stmt, i + 1, value, scope, safeIntegers)) {
if (!rebindValue(lexicalGlobalObject, db, stmt, i + 1, value, scope)) {
return {};
}
RETURN_IF_EXCEPTION(scope, {});
Expand Down Expand Up @@ -1534,7 +1538,7 @@ JSC_DEFINE_HOST_FUNCTION(jsSQLStatementExecuteFunction, (JSC::JSGlobalObject * l
int count = sqlite3_bind_parameter_count(sql.stmt);

SQLiteBindingsMap bindings { static_cast<uint16_t>(count > -1 ? count : 0), strict };
JSC::JSValue reb = rebindStatement(lexicalGlobalObject, bindingsAliveScope.value(), scope, db, sql.stmt, bindings, safeIntegers, nullptr);
JSC::JSValue reb = rebindStatement(lexicalGlobalObject, bindingsAliveScope.value(), scope, db, sql.stmt, bindings, nullptr);
RETURN_IF_EXCEPTION(scope, {});

if (versionDB->db != db) [[unlikely]] {
Expand Down Expand Up @@ -2173,10 +2177,8 @@ JSC_DEFINE_HOST_FUNCTION(jsSQLStatementExecuteStatementFunctionIterate, (JSC::JS
castedThis->version_db->version++;
}

if (!castedThis->hasExecuted || castedThis->need_update()) {
initializeColumnNames(lexicalGlobalObject, castedThis);
RETURN_IF_EXCEPTION(scope, {});
}
castedThis->updateColumnNamesIfNeeded(lexicalGlobalObject);
RETURN_IF_EXCEPTION(scope, {});

JSValue result = jsNull();
if (status == SQLITE_ROW) {
Expand Down Expand Up @@ -2225,10 +2227,8 @@ JSC_DEFINE_HOST_FUNCTION(jsSQLStatementExecuteStatementFunctionAll, (JSC::JSGlob
castedThis->version_db->version++;
}

if (!castedThis->hasExecuted || castedThis->need_update()) {
initializeColumnNames(lexicalGlobalObject, castedThis);
RETURN_IF_EXCEPTION(scope, {});
}
castedThis->updateColumnNamesIfNeeded(lexicalGlobalObject);
RETURN_IF_EXCEPTION(scope, {});

int columnCount = sqlite3_column_count(stmt);
JSValue result = jsUndefined();
Expand Down Expand Up @@ -2318,10 +2318,8 @@ JSC_DEFINE_HOST_FUNCTION(jsSQLStatementExecuteStatementFunctionGet, (JSC::JSGlob
castedThis->version_db->version++;
}

if (!castedThis->hasExecuted || castedThis->need_update()) {
initializeColumnNames(lexicalGlobalObject, castedThis);
RETURN_IF_EXCEPTION(scope, {});
}
castedThis->updateColumnNamesIfNeeded(lexicalGlobalObject);
RETURN_IF_EXCEPTION(scope, {});

JSValue result = jsNull();
if (status == SQLITE_ROW) {
Expand Down Expand Up @@ -2374,14 +2372,11 @@ JSC_DEFINE_HOST_FUNCTION(jsSQLStatementExecuteStatementFunctionRows, (JSC::JSGlo
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, {});
}
castedThis->updateColumnNamesIfNeeded(lexicalGlobalObject);
if (scope.exception()) [[unlikely]] {
// Don't forget to reset before releasing the exception.
sqlite3_reset(stmt);
RELEASE_AND_RETURN(scope, {});
}

size_t columnCount = sqlite3_column_count(stmt);
Expand Down Expand Up @@ -2463,12 +2458,10 @@ JSC_DEFINE_HOST_FUNCTION(jsSQLStatementExecuteStatementFunctionRawRows, (JSC::JS
castedThis->version_db->version++;
}

if (!castedThis->hasExecuted || castedThis->need_update()) {
initializeColumnNames(lexicalGlobalObject, castedThis);
if (scope.exception()) [[unlikely]] {
sqlite3_reset(stmt);
RELEASE_AND_RETURN(scope, {});
}
castedThis->updateColumnNamesIfNeeded(lexicalGlobalObject);
if (scope.exception()) [[unlikely]] {
sqlite3_reset(stmt);
RELEASE_AND_RETURN(scope, {});
}

size_t columnCount = sqlite3_column_count(stmt);
Expand Down Expand Up @@ -2564,12 +2557,10 @@ JSC_DEFINE_HOST_FUNCTION(jsSQLStatementExecuteStatementFunctionRun, (JSC::JSGlob
castedThis->version_db->version++;
}

if (!castedThis->hasExecuted || castedThis->need_update()) {
initializeColumnNames(lexicalGlobalObject, castedThis);
if (scope.exception()) [[unlikely]] {
sqlite3_reset(stmt);
RELEASE_AND_RETURN(scope, {});
}
castedThis->updateColumnNamesIfNeeded(lexicalGlobalObject);
if (scope.exception()) [[unlikely]] {
sqlite3_reset(stmt);
RELEASE_AND_RETURN(scope, {});
}

while (status == SQLITE_ROW) {
Expand Down Expand Up @@ -2671,8 +2662,6 @@ JSC_DEFINE_CUSTOM_GETTER(jsSqlStatementGetColumnTypes, (JSGlobalObject * lexical
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;
Expand All @@ -2695,6 +2684,9 @@ JSC_DEFINE_CUSTOM_GETTER(jsSqlStatementGetColumnTypes, (JSGlobalObject * lexical
// Step once to get to the first row (safe for read-only statements)
int stepStatus = sqlite3_step(castedThis->stmt);

// After the step: sqlite3_step() can re-prepare under a changed column count.
int count = sqlite3_column_count(castedThis->stmt);

// If we got a row, get types from it
if (stepStatus == SQLITE_ROW) {
for (int i = 0; i < count; i++) {
Expand Down Expand Up @@ -2873,7 +2865,7 @@ JSC::JSValue JSSQLStatement::rebind(JSC::JSGlobalObject* lexicalGlobalObject, JS
auto scope = DECLARE_THROW_SCOPE(vm);
auto* stmt = this->stmt;

auto val = rebindStatement(lexicalGlobalObject, values, scope, this->version_db->db, stmt, this->m_bindingNames, this->useBigInt64, this);
auto val = rebindStatement(lexicalGlobalObject, values, scope, this->version_db->db, stmt, this->m_bindingNames, this);
RETURN_IF_EXCEPTION(scope, {});

// A getter invoked while binding can finalize this statement; the callers
Expand Down
8 changes: 0 additions & 8 deletions src/jsc/bindings/sqlite/lazy_sqlite3.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,6 @@ typedef int (*lazy_sqlite3_bind_double_type)(sqlite3_stmt*, int, double);
typedef int (*lazy_sqlite3_bind_int_type)(sqlite3_stmt*, int, int);
typedef int (*lazy_sqlite3_bind_int64_type)(sqlite3_stmt*, int, sqlite3_int64);
typedef int (*lazy_sqlite3_bind_null_type)(sqlite3_stmt*, int);
typedef int (*lazy_sqlite3_bind_text_type)(sqlite3_stmt*, int, const char*, int, void (*)(void*));
typedef int (*lazy_sqlite3_bind_text16_type)(sqlite3_stmt*, int, const void*, int, void (*)(void*));
typedef int (*lazy_sqlite3_bind_text64_type)(sqlite3_stmt*, int, const char*, sqlite3_uint64, void (*)(void*), unsigned char encoding);
typedef int (*lazy_sqlite3_bind_parameter_count_type)(sqlite3_stmt*);
typedef int (*lazy_sqlite3_bind_parameter_index_type)(sqlite3_stmt*, const char* zName);
Expand Down Expand Up @@ -135,8 +133,6 @@ inline lazy_sqlite3_bind_int64_type lazy_sqlite3_bind_int64;
inline lazy_sqlite3_bind_null_type lazy_sqlite3_bind_null;
inline lazy_sqlite3_bind_parameter_count_type lazy_sqlite3_bind_parameter_count;
inline lazy_sqlite3_bind_parameter_index_type lazy_sqlite3_bind_parameter_index;
inline lazy_sqlite3_bind_text_type lazy_sqlite3_bind_text;
inline lazy_sqlite3_bind_text16_type lazy_sqlite3_bind_text16;
inline lazy_sqlite3_bind_text64_type lazy_sqlite3_bind_text64;
inline lazy_sqlite3_changes_type lazy_sqlite3_changes;
inline lazy_sqlite3_changes64_type lazy_sqlite3_changes64;
Expand Down Expand Up @@ -235,8 +231,6 @@ inline lazy_sqlite3changeset_apply_type lazy_sqlite3changeset_apply;
#define sqlite3_bind_null lazy_sqlite3_bind_null
#define sqlite3_bind_parameter_count lazy_sqlite3_bind_parameter_count
#define sqlite3_bind_parameter_index lazy_sqlite3_bind_parameter_index
#define sqlite3_bind_text lazy_sqlite3_bind_text
#define sqlite3_bind_text16 lazy_sqlite3_bind_text16
#define sqlite3_bind_text64 lazy_sqlite3_bind_text64
#define sqlite3_changes lazy_sqlite3_changes
#define sqlite3_changes64 lazy_sqlite3_changes64
Expand Down Expand Up @@ -375,8 +369,6 @@ inline int lazyLoadSQLite()
lazy_sqlite3_bind_null = (lazy_sqlite3_bind_null_type)dlsym(sqlite3_handle, "sqlite3_bind_null");
lazy_sqlite3_bind_parameter_count = (lazy_sqlite3_bind_parameter_count_type)dlsym(sqlite3_handle, "sqlite3_bind_parameter_count");
lazy_sqlite3_bind_parameter_index = (lazy_sqlite3_bind_parameter_index_type)dlsym(sqlite3_handle, "sqlite3_bind_parameter_index");
lazy_sqlite3_bind_text = (lazy_sqlite3_bind_text_type)dlsym(sqlite3_handle, "sqlite3_bind_text");
lazy_sqlite3_bind_text16 = (lazy_sqlite3_bind_text16_type)dlsym(sqlite3_handle, "sqlite3_bind_text16");
lazy_sqlite3_bind_text64 = (lazy_sqlite3_bind_text64_type)dlsym(sqlite3_handle, "sqlite3_bind_text64");
lazy_sqlite3_changes = (lazy_sqlite3_changes_type)dlsym(sqlite3_handle, "sqlite3_changes");
lazy_sqlite3_changes64 = (lazy_sqlite3_changes64_type)dlsym(sqlite3_handle, "sqlite3_changes64");
Expand Down
Loading
Loading