fix(ext/node): implement StatementSync#iterate (#28168)

Fixes https://github.com/denoland/deno/issues/28130
This commit is contained in:
Divy Srivastava 2025-02-18 21:26:17 +05:30 committed by GitHub
parent 2f4527562c
commit 4ab380e0a7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 160 additions and 0 deletions

View file

@ -205,6 +205,7 @@ impl DatabaseSync {
inner: raw_stmt,
db: self.conn.clone(),
use_big_ints: Cell::new(false),
is_iter_finished: false,
})
}

View file

@ -22,11 +22,13 @@ pub struct RunStatementResult {
changes: u64,
}
#[derive(Debug)]
pub struct StatementSync {
pub inner: *mut ffi::sqlite3_stmt,
pub db: Rc<RefCell<Option<rusqlite::Connection>>>,
pub use_big_ints: Cell<bool>,
pub is_iter_finished: bool,
}
impl Drop for StatementSync {
@ -369,6 +371,143 @@ impl StatementSync {
Ok(arr)
}
fn iterate<'a>(
&self,
scope: &mut v8::HandleScope<'a>,
#[varargs] params: Option<&v8::FunctionCallbackArguments>,
) -> Result<v8::Local<'a, v8::Object>, SqliteError> {
macro_rules! v8_static_strings {
($($ident:ident = $str:literal),* $(,)?) => {
$(
pub static $ident: deno_core::FastStaticString = deno_core::ascii_str!($str);
)*
};
}
v8_static_strings! {
ITERATOR = "Iterator",
PROTOTYPE = "prototype",
NEXT = "next",
RETURN = "return",
DONE = "done",
VALUE = "value",
}
self.reset();
self.bind_params(scope, params)?;
let iterate_next = |scope: &mut v8::HandleScope,
args: v8::FunctionCallbackArguments,
mut rv: v8::ReturnValue| {
let context = v8::Local::<v8::External>::try_from(args.data())
.expect("Iterator#next expected external data");
// SAFETY: `context` is a valid pointer to a StatementSync instance
let statement = unsafe { &mut *(context.value() as *mut StatementSync) };
let names = &[
DONE.v8_string(scope).unwrap().into(),
VALUE.v8_string(scope).unwrap().into(),
];
if statement.is_iter_finished {
let values = &[
v8::Boolean::new(scope, true).into(),
v8::undefined(scope).into(),
];
let null = v8::null(scope).into();
let result =
v8::Object::with_prototype_and_properties(scope, null, names, values);
rv.set(result.into());
return;
}
let Ok(Some(row)) = statement.read_row(scope) else {
statement.reset();
statement.is_iter_finished = true;
let values = &[
v8::Boolean::new(scope, true).into(),
v8::undefined(scope).into(),
];
let null = v8::null(scope).into();
let result =
v8::Object::with_prototype_and_properties(scope, null, names, values);
rv.set(result.into());
return;
};
let values = &[v8::Boolean::new(scope, false).into(), row.into()];
let null = v8::null(scope).into();
let result =
v8::Object::with_prototype_and_properties(scope, null, names, values);
rv.set(result.into());
};
let iterate_return = |scope: &mut v8::HandleScope,
args: v8::FunctionCallbackArguments,
mut rv: v8::ReturnValue| {
let context = v8::Local::<v8::External>::try_from(args.data())
.expect("Iterator#return expected external data");
// SAFETY: `context` is a valid pointer to a StatementSync instance
let statement = unsafe { &mut *(context.value() as *mut StatementSync) };
statement.is_iter_finished = true;
statement.reset();
let names = &[
DONE.v8_string(scope).unwrap().into(),
VALUE.v8_string(scope).unwrap().into(),
];
let values = &[
v8::Boolean::new(scope, true).into(),
v8::undefined(scope).into(),
];
let null = v8::null(scope).into();
let result =
v8::Object::with_prototype_and_properties(scope, null, names, values);
rv.set(result.into());
};
let external = v8::External::new(scope, self as *const _ as _);
let next_func = v8::Function::builder(iterate_next)
.data(external.into())
.build(scope)
.expect("Failed to create Iterator#next function");
let return_func = v8::Function::builder(iterate_return)
.data(external.into())
.build(scope)
.expect("Failed to create Iterator#return function");
let global = scope.get_current_context().global(scope);
let iter_str = ITERATOR.v8_string(scope).unwrap();
let js_iterator: v8::Local<v8::Object> = {
global
.get(scope, iter_str.into())
.unwrap()
.try_into()
.unwrap()
};
let proto_str = PROTOTYPE.v8_string(scope).unwrap();
let js_iterator_proto = js_iterator.get(scope, proto_str.into()).unwrap();
let names = &[
NEXT.v8_string(scope).unwrap().into(),
RETURN.v8_string(scope).unwrap().into(),
];
let values = &[next_func.into(), return_func.into()];
let iterator = v8::Object::with_prototype_and_properties(
scope,
js_iterator_proto,
names,
values,
);
Ok(iterator)
}
#[fast]
fn set_read_big_ints(&self, enabled: bool) {
self.use_big_ints.set(enabled);

View file

@ -197,3 +197,23 @@ CREATE TABLE two(id int PRIMARY KEY) STRICT;`);
db.close();
});
Deno.test("[node/sqlite] StatementSync#iterate", () => {
const db = new DatabaseSync(":memory:");
const stmt = db.prepare("SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3");
// @ts-ignore: types are not up to date
const iter = stmt.iterate();
const result = [];
for (const row of iter) {
result.push(row);
}
assertEquals(result, stmt.all());
const { done, value } = iter.next();
assertEquals(done, true);
assertEquals(value, undefined);
db.close();
});