Skip to content

Commit

Permalink
fix(ext/node): throw RangeError when sqlite INTEGER is too large (#27907
Browse files Browse the repository at this point in the history
)

Signed-off-by: Divy Srivastava <[email protected]>
  • Loading branch information
littledivy authored Feb 1, 2025
1 parent 9da6a20 commit 7d19668
Show file tree
Hide file tree
Showing 3 changed files with 23 additions and 5 deletions.
3 changes: 3 additions & 0 deletions ext/node/ops/sqlite/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,7 @@ pub enum SqliteError {
#[class(generic)]
#[error("Invalid constructor")]
InvalidConstructor,
#[class(range)]
#[error("The value of column {0} is too large to be represented as a JavaScript number: {1}")]
NumberTooLarge(i32, i64),
}
15 changes: 10 additions & 5 deletions ext/node/ops/sqlite/statement.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ use serde::Serialize;

use super::SqliteError;

// ECMA-262, 15th edition, 21.1.2.6. Number.MAX_SAFE_INTEGER (2^53-1)
const MAX_SAFE_JS_INTEGER: i64 = 9007199254740991;

#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct RunStatementResult {
Expand Down Expand Up @@ -122,17 +125,19 @@ impl StatementSync {
&self,
index: i32,
scope: &mut v8::HandleScope<'a>,
) -> v8::Local<'a, v8::Value> {
) -> Result<v8::Local<'a, v8::Value>, SqliteError> {
// SAFETY: `self.inner` is a valid pointer to a sqlite3_stmt
// as it lives as long as the StatementSync instance.
unsafe {
match ffi::sqlite3_column_type(self.inner, index) {
Ok(match ffi::sqlite3_column_type(self.inner, index) {
ffi::SQLITE_INTEGER => {
let value = ffi::sqlite3_column_int64(self.inner, index);
if self.use_big_ints.get() {
v8::BigInt::new_from_i64(scope, value).into()
} else {
} else if value.abs() <= MAX_SAFE_JS_INTEGER {
v8::Integer::new(scope, value as _).into()
} else {
return Err(SqliteError::NumberTooLarge(index, value));
}
}
ffi::SQLITE_FLOAT => {
Expand Down Expand Up @@ -162,7 +167,7 @@ impl StatementSync {
}
ffi::SQLITE_NULL => v8::null(scope).into(),
_ => v8::undefined(scope).into(),
}
})
}
}

Expand All @@ -183,7 +188,7 @@ impl StatementSync {
let mut values = Vec::with_capacity(num_cols);

for (index, name) in iter {
let value = self.column_value(index, scope);
let value = self.column_value(index, scope)?;
let name =
v8::String::new_from_utf8(scope, name, v8::NewStringType::Normal)
.unwrap()
Expand Down
10 changes: 10 additions & 0 deletions tests/unit_node/sqlite_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,16 @@ Deno.test("[node/sqlite] StatementSync read bigints are supported", () => {
assertEquals(stmt.get(), { key: 1n, __proto__: null });
});

Deno.test("[node/sqlite] StatementSync integer too large", () => {
const db = new DatabaseSync(":memory:");
db.exec("CREATE TABLE data(key INTEGER PRIMARY KEY);");
db.prepare("INSERT INTO data (key) VALUES (?)").run(
Number.MAX_SAFE_INTEGER + 1,
);

assertThrows(() => db.prepare("SELECT * FROM data").get());
});

Deno.test("[node/sqlite] StatementSync blob are Uint8Array", () => {
const db = new DatabaseSync(":memory:");
const obj = db.prepare("select cast('test' as blob)").all();
Expand Down

0 comments on commit 7d19668

Please sign in to comment.