Merge 'JavaScript improvements' from Pekka Enberg

Closes #2467
This commit is contained in:
Pekka Enberg
2025-08-07 14:01:07 +03:00
committed by GitHub
8 changed files with 71 additions and 17 deletions

View File

@@ -11,6 +11,8 @@ export declare class Database {
constructor(path: string)
/** Returns whether the database is in memory-only mode. */
get memory(): boolean
/** Returns whether the database connection is open. */
get open(): boolean
/**
* Executes a batch of SQL statements.
*

View File

@@ -87,6 +87,10 @@ class Database {
* @param {string} sql - The SQL statement string to prepare.
*/
prepare(sql) {
if (!this.open) {
throw new TypeError("The database connection is not open");
}
if (!sql) {
throw new RangeError("The supplied SQL string contains no statements");
}
@@ -186,6 +190,10 @@ class Database {
* @param {string} sql - The SQL statement string to execute.
*/
exec(sql) {
if (!this.open) {
throw new TypeError("The database connection is not open");
}
try {
this.db.batch(sql);
} catch (err) {

View File

@@ -13,7 +13,11 @@
use napi::bindgen_prelude::*;
use napi::{Env, Task};
use napi_derive::napi;
use std::{cell::RefCell, num::NonZeroUsize, sync::Arc};
use std::{
cell::{Cell, RefCell},
num::NonZeroUsize,
sync::Arc,
};
/// Step result constants
const STEP_ROW: u32 = 1;
@@ -35,6 +39,7 @@ pub struct Database {
io: Arc<dyn turso_core::IO>,
conn: Arc<turso_core::Connection>,
is_memory: bool,
is_open: Cell<bool>,
}
#[napi]
@@ -76,6 +81,7 @@ impl Database {
io,
conn,
is_memory,
is_open: Cell::new(true),
})
}
@@ -85,6 +91,12 @@ impl Database {
self.is_memory
}
/// Returns whether the database connection is open.
#[napi(getter)]
pub fn open(&self) -> bool {
self.is_open.get()
}
/// Executes a batch of SQL statements.
///
/// # Arguments
@@ -114,12 +126,10 @@ impl Database {
/// A `Statement` instance.
#[napi]
pub fn prepare(&self, sql: String) -> Result<Statement> {
let stmt = self.conn.prepare(&sql).map_err(|e| {
Error::new(
Status::GenericFailure,
format!("Failed to prepare statement: {e}"),
)
})?;
let stmt = self
.conn
.prepare(&sql)
.map_err(|e| Error::new(Status::GenericFailure, format!("{e}")))?;
let column_names: Vec<std::ffi::CString> = (0..stmt.num_columns())
.map(|i| std::ffi::CString::new(stmt.get_column_name(i).to_string()).unwrap())
.collect();
@@ -167,6 +177,7 @@ impl Database {
/// `Ok(())` if the database is closed successfully.
#[napi]
pub fn close(&self) -> Result<()> {
self.is_open.set(false);
// Database close is handled automatically when dropped
Ok(())
}

View File

@@ -87,6 +87,10 @@ class Database {
* @param {string} sql - The SQL statement string to prepare.
*/
prepare(sql) {
if (!this.open) {
throw new TypeError("The database connection is not open");
}
if (!sql) {
throw new RangeError("The supplied SQL string contains no statements");
}
@@ -186,6 +190,10 @@ class Database {
* @param {string} sql - The SQL statement string to execute.
*/
exec(sql) {
if (!this.open) {
throw new TypeError("The database connection is not open");
}
try {
this.db.batch(sql);
} catch (err) {

View File

@@ -15,6 +15,7 @@ export interface Config extends SessionConfig {}
export class Connection {
private config: Config;
private session: Session;
private isOpen: boolean = true;
constructor(config: Config) {
if (!config.url) {
@@ -40,6 +41,9 @@ export class Connection {
* ```
*/
prepare(sql: string): Statement {
if (!this.isOpen) {
throw new TypeError("The database connection is not open");
}
return new Statement(this.config, sql);
}
@@ -56,6 +60,9 @@ export class Connection {
* ```
*/
async exec(sql: string): Promise<any> {
if (!this.isOpen) {
throw new TypeError("The database connection is not open");
}
return this.session.sequence(sql);
}
@@ -87,6 +94,9 @@ export class Connection {
* @returns Promise resolving to the result of the pragma
*/
async pragma(pragma: string): Promise<any> {
if (!this.isOpen) {
throw new TypeError("The database connection is not open");
}
const sql = `PRAGMA ${pragma}`;
return this.session.execute(sql);
}
@@ -97,6 +107,7 @@ export class Connection {
* This sends a close request to the server to properly clean up the stream.
*/
async close(): Promise<void> {
this.isOpen = false;
await this.session.close();
}
}

View File

@@ -112,12 +112,15 @@ export class Statement {
const result = await this.session.execute(this.sql, normalizedArgs);
if (this.presentationMode === 'raw') {
// In raw mode, return arrays of values
// Each row is already an array with column properties added
return result.rows.map((row: any) => [...row]);
}
return result.rows;
return result.rows.map((row: any) => {
const obj: any = {};
result.columns.forEach((col: string, i: number) => {
obj[col] = row[i];
});
return obj;
});
}
/**

View File

@@ -147,7 +147,7 @@ test.skip("Statement.iterate()", async (t) => {
}
});
test.skip("Statement.all()", async (t) => {
test.serial("Statement.all()", async (t) => {
const db = t.context.db;
const stmt = await db.prepare("SELECT * FROM users");
@@ -158,7 +158,7 @@ test.skip("Statement.all()", async (t) => {
t.deepEqual(await stmt.all(), expected);
});
test.skip("Statement.all() [raw]", async (t) => {
test.serial("Statement.all() [raw]", async (t) => {
const db = t.context.db;
const stmt = await db.prepare("SELECT * FROM users");
@@ -330,7 +330,7 @@ test.skip("errors", async (t) => {
t.is(noTableError.rawCode, 1)
});
test.skip("Database.prepare() after close()", async (t) => {
test.serial("Database.prepare() after close()", async (t) => {
const db = t.context.db;
await db.close();
await t.throwsAsync(async () => {
@@ -341,7 +341,18 @@ test.skip("Database.prepare() after close()", async (t) => {
});
});
test.skip("Database.exec() after close()", async (t) => {
test.serial("Database.pragma() after close()", async (t) => {
const db = t.context.db;
await db.close();
await t.throwsAsync(async () => {
await db.pragma("cache_size = 2000");
}, {
instanceOf: TypeError,
message: "The database connection is not open"
});
});
test.serial("Database.exec() after close()", async (t) => {
const db = t.context.db;
await db.close();
await t.throwsAsync(async () => {

View File

@@ -415,7 +415,7 @@ test.skip("errors", async (t) => {
}
});
test.skip("Database.prepare() after close()", async (t) => {
test.serial("Database.prepare() after close()", async (t) => {
const db = t.context.db;
db.close();
t.throws(() => {
@@ -426,7 +426,7 @@ test.skip("Database.prepare() after close()", async (t) => {
});
});
test.skip("Database.exec() after close()", async (t) => {
test.serial("Database.exec() after close()", async (t) => {
const db = t.context.db;
db.close();
t.throws(() => {