Skip to content
Closed
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
181 changes: 104 additions & 77 deletions natives/native-worker.js
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,97 @@ function executeStatement(db, sql, params) {
return result;
}

/**
* Execute a query (SELECT or otherwise) and return results.
*
* @param {object} db - Database instance
* @param {string} sql - SQL statement
* @param {any[]} params - Parameters
* @returns {object} Result with columns, values, rowCount
*/
function executeQuery(db, sql, params) {
console.error("[native-worker] query:", sql.substring(0, 50));

// Detect if this is a SELECT query or a modification (UPDATE/INSERT/DELETE/etc)
const trimmedSql = sql.trim().toUpperCase();
const isSelectQuery = trimmedSql.startsWith("SELECT") ||
trimmedSql.startsWith("PRAGMA") ||
trimmedSql.startsWith("EXPLAIN") ||
trimmedSql.startsWith("WITH");

console.error("[native-worker] isSelectQuery:", isSelectQuery);

let columns = [];
let values = [];
let rowCount = 0;

if (isSelectQuery) {
const stmt = db.prepare(sql);
let rows;
try {
if (typeof stmt.all === 'function') {
if (params && params.length > 0) {
rows = stmt.all(...params);
} else {
rows = stmt.all();
}
} else {
// Fallback for iterators
rows = [];
if (params && params.length > 0 && typeof stmt.bind === 'function') {
try { stmt.bind(...params); } catch(e) { console.error("bind failed", e); }
}
for (const row of stmt) {
rows.push(row);
}
}
} finally {
if (typeof stmt.finalize === 'function') stmt.finalize();
}

console.error("[native-worker] got rows:", rows?.length);

if (rows && rows.length > 0) {
columns = Object.keys(rows[0]);
values = rows.map(row => columns.map(col => row[col]));
rowCount = rows.length;
}
} else {
// Non-SELECT via query() - typically shouldn't happen for updateCell but good to support
console.error("[native-worker] executing non-SELECT via query()");
if (params && params.length > 0) {
const stmt = db.prepare(sql);
try {
if (typeof stmt.run === 'function') {
stmt.run(...params);
} else if (typeof stmt.execute === 'function') {
if (typeof stmt.bind === 'function') stmt.bind(...params);
stmt.execute();
} else {
if (typeof stmt.bind === 'function') stmt.bind(...params);
stmt.step(); // or iterate
}
} finally {
if (typeof stmt.finalize === 'function') stmt.finalize();
}
} else {
db.exec(sql);
}

// Get changes
try {
const chg = db.prepare("SELECT changes() as c").all()[0].c;
rowCount = chg;
} catch(e) { rowCount = 0; }
Comment on lines +242 to +245
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Relying on a try...catch block to handle an empty result set from db.prepare(...).all() can be considered an anti-pattern. It's more robust to explicitly check the array length before accessing an element. This makes the code's intent clearer and avoids using exceptions for control flow in a non-exceptional case.

Suggested change
try {
const chg = db.prepare("SELECT changes() as c").all()[0].c;
rowCount = chg;
} catch(e) { rowCount = 0; }
try {
const res = db.prepare("SELECT changes() as c").all();
rowCount = res.length > 0 ? res[0].c : 0;
} catch(e) { rowCount = 0; }

}

return {
columns,
values,
rowCount
};
}

/**
* Handle incoming RPC request.
*
Expand Down Expand Up @@ -238,88 +329,24 @@ async function handleRequest(request) {
// Execute SQL and return all results
// args: [sql: string, params?: any[]]
const [sql, params] = args;
console.error("[native-worker] query:", sql.substring(0, 50));
if (!db) throw new Error("Database not open");

// Detect if this is a SELECT query or a modification (UPDATE/INSERT/DELETE/etc)
const trimmedSql = sql.trim().toUpperCase();
const isSelectQuery = trimmedSql.startsWith("SELECT") ||
trimmedSql.startsWith("PRAGMA") ||
trimmedSql.startsWith("EXPLAIN") ||
trimmedSql.startsWith("WITH");

console.error("[native-worker] isSelectQuery:", isSelectQuery);

let columns = [];
let values = [];
let rowCount = 0;

if (isSelectQuery) {
const stmt = db.prepare(sql);
let rows;
try {
if (typeof stmt.all === 'function') {
if (params && params.length > 0) {
rows = stmt.all(...params);
} else {
rows = stmt.all();
}
} else {
// Fallback for iterators
rows = [];
if (params && params.length > 0 && typeof stmt.bind === 'function') {
try { stmt.bind(...params); } catch(e) { console.error("bind failed", e); }
}
for (const row of stmt) {
rows.push(row);
}
}
} finally {
if (typeof stmt.finalize === 'function') stmt.finalize();
}

console.error("[native-worker] got rows:", rows?.length);
result = executeQuery(db, sql, params);
console.error("[native-worker] query complete");
break;
}

if (rows && rows.length > 0) {
columns = Object.keys(rows[0]);
values = rows.map(row => columns.map(col => row[col]));
rowCount = rows.length;
}
} else {
// Non-SELECT via query() - typically shouldn't happen for updateCell but good to support
console.error("[native-worker] executing non-SELECT via query()");
if (params && params.length > 0) {
const stmt = db.prepare(sql);
try {
if (typeof stmt.run === 'function') {
stmt.run(...params);
} else if (typeof stmt.execute === 'function') {
if (typeof stmt.bind === 'function') stmt.bind(...params);
stmt.execute();
} else {
if (typeof stmt.bind === 'function') stmt.bind(...params);
stmt.step(); // or iterate
}
} finally {
if (typeof stmt.finalize === 'function') stmt.finalize();
}
} else {
db.exec(sql);
}
case "queryBatch": {
// Execute a batch of queries and return results for each
// args: [queries: { sql: string, params?: any[] }[]]
const [queries] = args;
if (!db) throw new Error("Database not open");

// Get changes
try {
const chg = db.prepare("SELECT changes() as c").all()[0].c;
rowCount = chg;
} catch(e) { rowCount = 0; }
const results = [];
for (const query of queries) {
results.push(executeQuery(db, query.sql, query.params));
}

result = {
columns,
values,
rowCount
};
console.error("[native-worker] query complete");
result = { results };
break;
}

Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 9 additions & 5 deletions src/nativeWorker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -897,13 +897,17 @@ export async function createNativeDatabaseConnection(
'auto_vacuum'
];

const queries = pragmasToFetch.map(pragma => ({ sql: `PRAGMA ${pragma}` }));
const res = await worker.call<any>('queryBatch', [queries]);

const result: Record<string, CellValue> = {};

for (const pragma of pragmasToFetch) {
const res = await worker.call<any>('query', [`PRAGMA ${pragma}`]);
if (res && res.values && res.values.length > 0) {
result[pragma] = res.values[0][0];
}
if (res && res.results && Array.isArray(res.results)) {
res.results.forEach((r: any, i: number) => {
if (r && r.values && r.values.length > 0) {
result[pragmasToFetch[i]] = r.values[0][0];
}
});
}
Comment on lines +901 to 911
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

To improve type safety and code clarity, it's best to avoid using any. You can define an interface for the queryBatch response and use it to type the result from worker.call. This will provide better autocompletion and compile-time checks.

          interface QueryBatchResult {
            results: {
              values: CellValue[][];
            }[];
          }
          const res = await worker.call<QueryBatchResult>('queryBatch', [queries]);

          const result: Record<string, CellValue> = {};

          if (res?.results) {
            res.results.forEach((r, i) => {
              if (r?.values?.length > 0 && r.values[0]?.length > 0) {
                result[pragmasToFetch[i]] = r.values[0][0];
              }
            });
          }


return result;
Expand Down