Skip to content
This repository was archived by the owner on Jun 24, 2025. It is now read-only.

Commit 2b4d9f8

Browse files
committed
fix(sql): prepared statements leak raw state (fixes #1705)
1 parent bbc8536 commit 2b4d9f8

File tree

1 file changed

+19
-7
lines changed

1 file changed

+19
-7
lines changed

src/services/sql.ts

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -112,12 +112,21 @@ function upsert<T extends {}>(tableName: string, primaryKey: string, rec: T) {
112112
execute(query, rec);
113113
}
114114

115-
function stmt(sql: string) {
116-
if (!(sql in statementCache)) {
117-
statementCache[sql] = dbConnection.prepare(sql);
115+
/**
116+
* For the given SQL query, returns a prepared statement. For the same query (string comparison), the same statement is returned.
117+
*
118+
* @param sql the SQL query for which to return a prepared statement.
119+
* @param isRaw indicates whether `.raw()` is going to be called on the prepared statement in order to return the raw rows (e.g. via {@link getRawRows()}). The reason is that the raw state is preserved in the saved statement and would break non-raw calls for the same query.
120+
* @returns the corresponding {@link Statement}.
121+
*/
122+
function stmt(sql: string, isRaw?: boolean) {
123+
const key = (isRaw ? "raw/" + sql : sql);
124+
125+
if (!(key in statementCache)) {
126+
statementCache[key] = dbConnection.prepare(sql);
118127
}
119128

120-
return statementCache[sql];
129+
return statementCache[key];
121130
}
122131

123132
function getRow<T>(query: string, params: Params = []): T {
@@ -172,7 +181,7 @@ function getRows<T>(query: string, params: Params = []): T[] {
172181
}
173182

174183
function getRawRows<T extends {} | unknown[]>(query: string, params: Params = []): T[] {
175-
return (wrap(query, (s) => s.raw().all(params)) as T[]) || [];
184+
return (wrap(query, (s) => s.raw().all(params), true) as T[]) || [];
176185
}
177186

178187
function iterateRows<T>(query: string, params: Params = []): IterableIterator<T> {
@@ -234,7 +243,10 @@ function executeScript(query: string): DatabaseType {
234243
return dbConnection.exec(query);
235244
}
236245

237-
function wrap(query: string, func: (statement: Statement) => unknown): unknown {
246+
/**
247+
* @param isRaw indicates whether `.raw()` is going to be called on the prepared statement in order to return the raw rows (e.g. via {@link getRawRows()}). The reason is that the raw state is preserved in the saved statement and would break non-raw calls for the same query.
248+
*/
249+
function wrap(query: string, func: (statement: Statement) => unknown, isRaw?: boolean): unknown {
238250
const startTimestamp = Date.now();
239251
let result;
240252

@@ -243,7 +255,7 @@ function wrap(query: string, func: (statement: Statement) => unknown): unknown {
243255
}
244256

245257
try {
246-
result = func(stmt(query));
258+
result = func(stmt(query, isRaw));
247259
} catch (e: any) {
248260
if (e.message.includes("The database connection is not open")) {
249261
// this often happens on killing the app which puts these alerts in front of user

0 commit comments

Comments
 (0)