use std::cell::Cell;
use std::cell::RefCell;
use std::rc::Rc;
use deno_core::GarbageCollected;
use deno_core::ToV8;
use deno_core::op2;
use deno_core::v8;
use deno_core::v8::GetPropertyNamesArgs;
use deno_core::v8_static_strings;
use rusqlite::ffi;
use super::SqliteError;
use super::validators;
const MAX_SAFE_JS_INTEGER: i64 = 9007199254740991;
pub struct RunStatementResult {
last_insert_rowid: i64,
changes: u64,
use_big_ints: bool,
}
impl<'a> ToV8<'a> for RunStatementResult {
type Error = SqliteError;
fn to_v8(
self,
scope: &mut v8::PinScope<'a, '_>,
) -> Result<v8::Local<'a, v8::Value>, SqliteError> {
v8_static_strings! {
LAST_INSERT_ROW_ID = "lastInsertRowid",
CHANGES = "changes",
}
let obj = v8::Object::new(scope);
let last_insert_row_id_str = LAST_INSERT_ROW_ID.v8_string(scope).unwrap();
let last_insert_row_id = if self.use_big_ints {
v8::BigInt::new_from_i64(scope, self.last_insert_rowid).into()
} else {
v8::Number::new(scope, self.last_insert_rowid as f64).into()
};
obj
.set(scope, last_insert_row_id_str.into(), last_insert_row_id)
.unwrap();
let changes_str = CHANGES.v8_string(scope).unwrap();
let changes = if self.use_big_ints {
v8::BigInt::new_from_u64(scope, self.changes).into()
} else {
v8::Number::new(scope, self.changes as f64).into()
};
obj.set(scope, changes_str.into(), changes).unwrap();
Ok(obj.into())
}
}
pub type InnerStatementPtr = Rc<Cell<Option<*mut ffi::sqlite3_stmt>>>;
pub trait StatementExecution {
fn stmt_ptr(&self) -> Result<*mut ffi::sqlite3_stmt, SqliteError>;
fn step(&self) -> Result<bool, SqliteError>;
fn return_arrays(&self) -> bool;
fn use_big_ints(&self) -> bool;
fn check_bind_result(&self, r: i32) -> Result<(), SqliteError>;
fn column_count(&self) -> Result<i32, SqliteError> {
let raw = self.stmt_ptr()?;
let count = unsafe { ffi::sqlite3_column_count(raw) };
Ok(count)
}
fn column_name(&self, index: i32) -> Result<&[u8], SqliteError> {
let raw = self.stmt_ptr()?;
unsafe {
let name = ffi::sqlite3_column_name(raw, index);
Ok(std::ffi::CStr::from_ptr(name as _).to_bytes())
}
}
fn column_value<'a>(
&self,
index: i32,
scope: &mut v8::PinScope<'a, '_>,
) -> Result<v8::Local<'a, v8::Value>, SqliteError> {
let raw = self.stmt_ptr()?;
let use_big_ints = self.use_big_ints();
unsafe {
Ok(match ffi::sqlite3_column_type(raw, index) {
ffi::SQLITE_INTEGER => {
let value = ffi::sqlite3_column_int64(raw, index);
if use_big_ints {
v8::BigInt::new_from_i64(scope, value).into()
} else if value.abs() <= MAX_SAFE_JS_INTEGER {
v8::Number::new(scope, value as f64).into()
} else {
return Err(SqliteError::NumberTooLarge(value));
}
}
ffi::SQLITE_FLOAT => {
let value = ffi::sqlite3_column_double(raw, index);
v8::Number::new(scope, value).into()
}
ffi::SQLITE_TEXT => {
let value = ffi::sqlite3_column_text(raw, index);
let value = std::ffi::CStr::from_ptr(value as _);
v8::String::new_from_utf8(
scope,
value.to_bytes(),
v8::NewStringType::Normal,
)
.unwrap()
.into()
}
ffi::SQLITE_BLOB => {
let value = ffi::sqlite3_column_blob(raw, index);
let size = ffi::sqlite3_column_bytes(raw, index);
let ab = if size == 0 {
v8::ArrayBuffer::new(scope, 0)
} else {
let value =
std::slice::from_raw_parts(value as *const u8, size as usize);
let bs =
v8::ArrayBuffer::new_backing_store_from_vec(value.to_vec())
.make_shared();
v8::ArrayBuffer::with_backing_store(scope, &bs)
};
v8::Uint8Array::new(scope, ab, 0, size as _).unwrap().into()
}
ffi::SQLITE_NULL => v8::null(scope).into(),
_ => v8::undefined(scope).into(),
})
}
}
fn bind_value(
&self,
scope: &mut v8::PinScope<'_, '_>,
value: v8::Local<v8::Value>,
index: i32,
) -> Result<(), SqliteError> {
let raw = self.stmt_ptr()?;
let r = if value.is_number() {
let value = value.number_value(scope).unwrap();
unsafe { ffi::sqlite3_bind_double(raw, index, value) }
} else if value.is_string() {
let value = value.to_rust_string_lossy(scope);
unsafe {
ffi::sqlite3_bind_text(
raw,
index,
value.as_ptr() as *const _,
value.len() as i32,
ffi::SQLITE_TRANSIENT(),
)
}
} else if value.is_null() {
unsafe { ffi::sqlite3_bind_null(raw, index) }
} else if value.is_array_buffer_view() {
let value: v8::Local<v8::ArrayBufferView> = value.try_into().unwrap();
let mut data = value.data();
let mut size = value.byte_length();
if data.is_null() {
static EMPTY: [u8; 0] = [];
data = EMPTY.as_ptr() as *mut _;
size = 0;
}
unsafe {
ffi::sqlite3_bind_blob(
raw,
index,
data,
size as i32,
ffi::SQLITE_TRANSIENT(),
)
}
} else if value.is_big_int() {
let value: v8::Local<v8::BigInt> = value.try_into().unwrap();
let (as_int, lossless) = value.i64_value();
if !lossless {
return Err(SqliteError::InvalidBindValue(
"BigInt value is too large to bind",
));
}
unsafe { ffi::sqlite3_bind_int64(raw, index, as_int) }
} else {
return Err(SqliteError::InvalidBindType(index));
};
self.check_bind_result(r)
}
fn read_row<'a>(
&self,
scope: &mut v8::PinScope<'a, '_>,
) -> Result<Option<v8::Local<'a, v8::Value>>, SqliteError> {
if self.step()? {
return Ok(None);
}
let num_cols = self.column_count()?;
let mut names = Vec::with_capacity(num_cols as usize);
let mut values = Vec::with_capacity(num_cols as usize);
for i in 0..num_cols {
let name = self.column_name(i)?;
let value = self.column_value(i, scope)?;
let name =
v8::String::new_from_utf8(scope, name, v8::NewStringType::Normal)
.unwrap()
.into();
names.push(name);
values.push(value);
}
if self.return_arrays() {
let result = v8::Array::new_with_elements(scope, &values);
Ok(Some(result.into()))
} else {
let null = v8::null(scope).into();
let result =
v8::Object::with_prototype_and_properties(scope, null, &names, &values);
Ok(Some(result.into()))
}
}
}
#[derive(Debug)]
pub struct StatementSync {
pub inner: InnerStatementPtr,
pub db: Rc<RefCell<Option<rusqlite::Connection>>>,
pub statements: Rc<RefCell<Vec<InnerStatementPtr>>>,
pub ignore_next_sqlite_error: Rc<Cell<bool>>,
pub return_arrays: Cell<bool>,
pub use_big_ints: Cell<bool>,
pub allow_bare_named_params: Cell<bool>,
pub allow_unknown_named_params: Cell<bool>,
pub is_iter_finished: Cell<bool>,
}
impl Drop for StatementSync {
fn drop(&mut self) {
let mut statements = self.statements.borrow_mut();
let mut finalized_stmt = None;
if let Some(pos) = statements
.iter()
.position(|stmt| Rc::ptr_eq(stmt, &self.inner))
{
let stmt = statements.remove(pos);
finalized_stmt = stmt.get();
stmt.set(None);
}
if let Some(ptr) = finalized_stmt {
unsafe {
ffi::sqlite3_finalize(ptr);
}
}
}
}
pub(crate) fn check_error_code(
r: i32,
db: *mut ffi::sqlite3,
) -> Result<(), SqliteError> {
if r != ffi::SQLITE_OK {
let err_message = unsafe { ffi::sqlite3_errmsg(db) };
if !err_message.is_null() {
let err_message = unsafe { std::ffi::CStr::from_ptr(err_message) }
.to_string_lossy()
.into_owned();
let err_str = unsafe { std::ffi::CStr::from_ptr(ffi::sqlite3_errstr(r)) }
.to_string_lossy()
.into_owned();
return Err(SqliteError::SqliteSysError {
message: err_message,
errcode: r as _,
errstr: err_str,
});
}
}
Ok(())
}
pub(crate) fn check_error_code2(r: i32) -> Result<(), SqliteError> {
let err_str = unsafe { std::ffi::CStr::from_ptr(ffi::sqlite3_errstr(r)) }
.to_string_lossy()
.into_owned();
Err(SqliteError::SqliteSysError {
message: err_str.clone(),
errcode: r as _,
errstr: err_str,
})
}
unsafe impl GarbageCollected for StatementSync {
fn trace(&self, _visitor: &mut deno_core::v8::cppgc::Visitor) {}
fn get_name(&self) -> &'static std::ffi::CStr {
c"StatementSync"
}
}
impl StatementExecution for StatementSync {
fn stmt_ptr(&self) -> Result<*mut ffi::sqlite3_stmt, SqliteError> {
let ptr = self.inner.get();
match ptr {
Some(p) => Ok(p),
None => Err(SqliteError::StatementFinalized),
}
}
fn step(&self) -> Result<bool, SqliteError> {
let raw = self.stmt_ptr()?;
unsafe {
let r = ffi::sqlite3_step(raw);
if r == ffi::SQLITE_DONE {
return Ok(true);
}
if r != ffi::SQLITE_ROW {
self.check_error_code_impl(r)?;
}
}
Ok(false)
}
fn return_arrays(&self) -> bool {
self.return_arrays.get()
}
fn use_big_ints(&self) -> bool {
self.use_big_ints.get()
}
fn check_bind_result(&self, r: i32) -> Result<(), SqliteError> {
self.check_error_code_impl(r)
}
}
impl StatementSync {
fn assert_statement_finalized(&self) -> Result<(), SqliteError> {
if self.inner.get().is_none() {
return Err(SqliteError::StatementFinalized);
}
Ok(())
}
fn reset(&self) -> Result<(), SqliteError> {
let raw = self.stmt_ptr()?;
let r = unsafe { ffi::sqlite3_reset(raw) };
self.check_error_code_impl(r)
}
fn check_error_code_impl(&self, r: i32) -> Result<(), SqliteError> {
if r != ffi::SQLITE_OK {
if self.ignore_next_sqlite_error.get() {
self.ignore_next_sqlite_error.set(false);
return Ok(());
}
let db = self.db.borrow();
let db = db.as_ref().ok_or(SqliteError::AlreadyClosed)?;
unsafe {
check_error_code(r, db.handle())?;
}
}
Ok(())
}
fn bind_params(
&self,
scope: &mut v8::PinScope<'_, '_>,
params: Option<&v8::FunctionCallbackArguments>,
) -> Result<(), SqliteError> {
let raw = self.stmt_ptr()?;
unsafe {
let r = ffi::sqlite3_clear_bindings(raw);
self.check_error_code_impl(r)?;
}
let mut anon_start = 0;
if let Some(params) = params {
let param0 = params.get(0);
if param0.is_object() && !param0.is_array_buffer_view() {
let obj = v8::Local::<v8::Object>::try_from(param0).unwrap();
let keys = obj
.get_property_names(scope, GetPropertyNamesArgs::default())
.unwrap();
let mut bare_named_params = std::collections::HashMap::new();
if self.allow_bare_named_params.get() {
let param_count = unsafe { ffi::sqlite3_bind_parameter_count(raw) };
for i in 1..=param_count {
let full_name = unsafe {
let name = ffi::sqlite3_bind_parameter_name(raw, i);
if name.is_null() {
continue;
}
std::ffi::CStr::from_ptr(name).to_bytes()
};
let bare_name = &full_name[1..];
let e = bare_named_params.insert(bare_name, i);
if let Some(existing_index) = e {
let bare_name_str = std::str::from_utf8(bare_name)?;
let full_name_str = std::str::from_utf8(full_name)?;
unsafe {
let existing_full_name =
ffi::sqlite3_bind_parameter_name(raw, existing_index);
let existing_full_name_str =
std::ffi::CStr::from_ptr(existing_full_name).to_str()?;
return Err(SqliteError::DuplicateNamedParameter(
bare_name_str.to_string(),
existing_full_name_str.to_string(),
full_name_str.to_string(),
));
}
}
}
}
let len = keys.length();
for j in 0..len {
let key = keys.get_index(scope, j).unwrap();
let key_str = key.to_rust_string_lossy(scope);
let key_c = std::ffi::CString::new(key_str).unwrap();
let mut r = unsafe {
ffi::sqlite3_bind_parameter_index(raw, key_c.as_ptr() as *const _)
};
if r == 0 {
let lookup = bare_named_params.get(key_c.as_bytes());
if let Some(index) = lookup {
r = *index;
}
if r == 0 {
if self.allow_unknown_named_params.get() {
continue;
}
return Err(SqliteError::UnknownNamedParameter(
key_c.into_string().unwrap(),
));
}
}
let value = obj.get(scope, key).unwrap();
self.bind_value(scope, value, r)?;
}
anon_start += 1;
}
let sql_param_count = unsafe { ffi::sqlite3_bind_parameter_count(raw) };
let mut positional_idx = 1;
for i in anon_start..params.length() {
while positional_idx <= sql_param_count {
let name_ptr =
unsafe { ffi::sqlite3_bind_parameter_name(raw, positional_idx) };
if name_ptr.is_null()
|| unsafe { *name_ptr as u8 == b'?' }
{
break;
}
positional_idx += 1;
}
let value = params.get(i);
self.bind_value(scope, value, positional_idx)?;
positional_idx += 1;
}
}
Ok(())
}
}
struct ResetGuard<'a>(&'a StatementSync);
impl Drop for ResetGuard<'_> {
fn drop(&mut self) {
let _ = self.0.reset();
}
}
#[op2]
impl StatementSync {
#[constructor]
#[cppgc]
fn new(_: bool) -> Result<StatementSync, SqliteError> {
Err(SqliteError::InvalidConstructor)
}
#[reentrant]
fn get<'a>(
&self,
scope: &mut v8::PinScope<'a, '_>,
#[varargs] params: Option<&v8::FunctionCallbackArguments>,
) -> Result<v8::Local<'a, v8::Value>, SqliteError> {
self.reset()?;
self.bind_params(scope, params)?;
let _reset = ResetGuard(self);
let entry = self.read_row(scope)?;
let result = entry.unwrap_or_else(|| v8::undefined(scope).into());
Ok(result)
}
fn run(
&self,
scope: &mut v8::PinScope<'_, '_>,
#[varargs] params: Option<&v8::FunctionCallbackArguments>,
) -> Result<RunStatementResult, SqliteError> {
let db = self.db.borrow();
let db = db.as_ref().ok_or(SqliteError::AlreadyClosed)?;
self.bind_params(scope, params)?;
let reset = ResetGuard(self);
self.step()?;
drop(reset);
Ok(RunStatementResult {
last_insert_rowid: db.last_insert_rowid(),
changes: db.changes(),
use_big_ints: self.use_big_ints.get(),
})
}
fn all<'a>(
&self,
scope: &mut v8::PinScope<'a, '_>,
#[varargs] params: Option<&v8::FunctionCallbackArguments>,
) -> Result<v8::Local<'a, v8::Array>, SqliteError> {
let mut arr = vec![];
self.bind_params(scope, params)?;
let _reset = ResetGuard(self);
while let Some(result) = self.read_row(scope)? {
arr.push(result);
}
let arr = v8::Array::new_with_elements(scope, &arr);
Ok(arr)
}
fn iterate<'a>(
&self,
scope: &mut v8::PinScope<'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",
__STATEMENT_REF = "__statement_ref",
}
self.reset()?;
self.bind_params(scope, params)?;
let iterate_next = |scope: &mut v8::PinScope<'_, '_>,
args: v8::FunctionCallbackArguments,
mut rv: v8::ReturnValue| {
let context = v8::Local::<v8::External>::try_from(args.data())
.expect("Iterator#next expected external data");
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.get() {
let values =
&[v8::Boolean::new(scope, true).into(), v8::null(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 {
let _ = statement.reset();
statement.is_iter_finished.set(true);
let values =
&[v8::Boolean::new(scope, true).into(), v8::null(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];
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::PinScope<'_, '_>,
args: v8::FunctionCallbackArguments,
mut rv: v8::ReturnValue| {
let context = v8::Local::<v8::External>::try_from(args.data())
.expect("Iterator#return expected external data");
let statement = unsafe { &mut *(context.value() as *mut StatementSync) };
statement.is_iter_finished.set(true);
let _ = 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::null(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(),
__STATEMENT_REF.v8_string(scope).unwrap().into(),
];
let statement_ref = if let Some(args) = params {
args.this().into()
} else {
v8::undefined(scope).into()
};
let values = &[next_func.into(), return_func.into(), statement_ref];
let iterator = v8::Object::with_prototype_and_properties(
scope,
js_iterator_proto,
names,
values,
);
self.is_iter_finished.set(false);
Ok(iterator)
}
#[fast]
#[undefined]
fn set_allow_bare_named_parameters(
&self,
#[validate(validators::allow_bare_named_params_bool)] enabled: bool,
) -> Result<(), SqliteError> {
self.assert_statement_finalized()?;
self.allow_bare_named_params.set(enabled);
Ok(())
}
#[fast]
#[undefined]
fn set_allow_unknown_named_parameters(
&self,
#[validate(validators::allow_unknown_named_params_bool)] enabled: bool,
) -> Result<(), SqliteError> {
self.assert_statement_finalized()?;
self.allow_unknown_named_params.set(enabled);
Ok(())
}
#[fast]
#[undefined]
fn set_read_big_ints(
&self,
#[validate(validators::read_big_ints_bool)] enabled: bool,
) -> Result<(), SqliteError> {
self.assert_statement_finalized()?;
self.use_big_ints.set(enabled);
Ok(())
}
#[fast]
#[undefined]
fn set_return_arrays(
&self,
#[validate(validators::return_arrays_bool)] enabled: bool,
) -> Result<(), SqliteError> {
self.assert_statement_finalized()?;
self.return_arrays.set(enabled);
Ok(())
}
#[getter]
#[rename("sourceSQL")]
#[string]
fn source_sql(&self) -> Result<String, SqliteError> {
let inner = self.stmt_ptr()?;
let source_sql = unsafe {
let raw = ffi::sqlite3_sql(inner);
std::ffi::CStr::from_ptr(raw as _)
.to_string_lossy()
.into_owned()
};
Ok(source_sql)
}
#[getter]
#[rename("expandedSQL")]
#[string]
fn expanded_sql(&self) -> Result<String, SqliteError> {
let inner = self.stmt_ptr()?;
unsafe {
let raw = ffi::sqlite3_expanded_sql(inner);
if raw.is_null() {
return Err(SqliteError::InvalidExpandedSql);
}
let sql = std::ffi::CStr::from_ptr(raw as _)
.to_string_lossy()
.into_owned();
ffi::sqlite3_free(raw as _);
Ok(sql)
}
}
fn columns<'a>(
&self,
scope: &mut v8::PinScope<'a, '_>,
) -> Result<v8::Local<'a, v8::Array>, SqliteError> {
v8_static_strings! {
NAME = "name",
COLUMN = "column",
TABLE = "table",
DATABASE = "database",
TYPE = "type",
}
let column_count = self.column_count()?;
let mut columns = Vec::with_capacity(column_count as usize);
let name_key = NAME.v8_string(scope).unwrap().into();
let column_key = COLUMN.v8_string(scope).unwrap().into();
let table_key = TABLE.v8_string(scope).unwrap().into();
let database_key = DATABASE.v8_string(scope).unwrap().into();
let type_key = TYPE.v8_string(scope).unwrap().into();
let keys = &[name_key, column_key, table_key, database_key, type_key];
let raw = self.stmt_ptr()?;
for i in 0..column_count {
let name = unsafe {
let name_ptr = ffi::sqlite3_column_name(raw, i);
if !name_ptr.is_null() {
let name_cstr = std::ffi::CStr::from_ptr(name_ptr as _);
v8::String::new_from_utf8(
scope,
name_cstr.to_bytes(),
v8::NewStringType::Normal,
)
.unwrap()
.into()
} else {
v8::null(scope).into()
}
};
let column = unsafe {
let column_ptr = ffi::sqlite3_column_origin_name(raw, i);
if !column_ptr.is_null() {
let column_cstr = std::ffi::CStr::from_ptr(column_ptr as _);
v8::String::new_from_utf8(
scope,
column_cstr.to_bytes(),
v8::NewStringType::Normal,
)
.unwrap()
.into()
} else {
v8::null(scope).into()
}
};
let table = unsafe {
let table_ptr = ffi::sqlite3_column_table_name(raw, i);
if !table_ptr.is_null() {
let table_cstr = std::ffi::CStr::from_ptr(table_ptr as _);
v8::String::new_from_utf8(
scope,
table_cstr.to_bytes(),
v8::NewStringType::Normal,
)
.unwrap()
.into()
} else {
v8::null(scope).into()
}
};
let database = unsafe {
let database_ptr = ffi::sqlite3_column_database_name(raw, i);
if !database_ptr.is_null() {
let database_cstr = std::ffi::CStr::from_ptr(database_ptr as _);
v8::String::new_from_utf8(
scope,
database_cstr.to_bytes(),
v8::NewStringType::Normal,
)
.unwrap()
.into()
} else {
v8::null(scope).into()
}
};
let col_type = unsafe {
let type_ptr = ffi::sqlite3_column_decltype(raw, i);
if !type_ptr.is_null() {
let type_cstr = std::ffi::CStr::from_ptr(type_ptr as _);
v8::String::new_from_utf8(
scope,
type_cstr.to_bytes(),
v8::NewStringType::Normal,
)
.unwrap()
.into()
} else {
v8::null(scope).into()
}
};
let values = &[name, column, table, database, col_type];
let null = v8::null(scope).into();
let obj =
v8::Object::with_prototype_and_properties(scope, null, keys, values);
columns.push(obj.into());
}
Ok(v8::Array::new_with_elements(scope, &columns))
}
}