Skip to content

Commit

Permalink
fix segmentation fault (#5)
Browse files Browse the repository at this point in the history
  • Loading branch information
DjDeveloperr authored Dec 24, 2021
1 parent 1cd96f3 commit d99722d
Show file tree
Hide file tree
Showing 9 changed files with 148 additions and 97 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,6 @@ sqlite_test/
*.pdb
*.dll
*.so
async-sqlite3
*.db-shm
*.db-wal
1 change: 1 addition & 0 deletions bench/backend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export interface Prepare {

export interface Backend {
name: string;
query(sql: string): unknown[];
execute(sql: string, params: unknown[]): void;
prepare(sql: string): Prepare;
close(): void;
Expand Down
46 changes: 43 additions & 3 deletions bench/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,20 @@ import native from "./native.ts";
import wasm from "./wasm.ts";
import { Backend } from "./backend.ts";

let level = 0;

function log(type: string, msg: string) {
console.log(`%c${type} %c${msg}`, "color: #0DBC79", "");
console.log(`${" ".repeat(level)}%c${type} %c${msg}`, "color: #0DBC79", "");
}

const ROWS = 100;
const ROWS = 10;
const ITERS = 10;

const backends: Backend[] = [native, wasm];

for (const backend of backends) {
log("Bench", backend.name);
level++;

backend.execute("pragma journal_mode = WAL", []);
backend.execute("pragma synchronous = normal", []);
Expand All @@ -23,6 +26,9 @@ for (const backend of backends) {
[],
);

log("Insert", "Bench start ->");
level++;

let total = 0;
let min!: number, max!: number;
for (let iter = 0; iter < ITERS; iter++) {
Expand All @@ -43,7 +49,6 @@ for (const backend of backends) {

total += took;
}
backend.close();

log(
"Result",
Expand All @@ -53,4 +58,39 @@ for (const backend of backends) {
Math.abs(max - min).toFixed(2)
}ms`,
);
level--;

log("Query", "Bench start ->");
level++;

total = 0, min = undefined as any, max = undefined as any;
for (let iter = 0; iter < ITERS; iter++) {
const now = performance.now();
backend.query("select * from test");
const took = performance.now() - now;

if (min === undefined || max === undefined) {
min = max = took;
} else {
min = Math.min(min, took);
max = Math.max(max, took);
}

total += took;
}

log(
"Result",
`${ITERS} iter ${ROWS} rows took ${total.toFixed(2)}ms ${
(ITERS / (total / 1000)).toFixed(2)
} iter/sec min ${min.toFixed(2)}ms max ${max.toFixed(2)}ms ±${
Math.abs(max - min).toFixed(2)
}ms`,
);

level--;

level--;

backend.close();
}
1 change: 1 addition & 0 deletions bench/native.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,6 @@ export default <Backend> {
finalize: () => prep.finalize(),
};
},
query: (sql) => db.queryArray(sql),
close: () => db.close(),
};
1 change: 1 addition & 0 deletions bench/wasm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,6 @@ export default <Backend> {
finalize: () => prep.finalize(),
};
},
query: (sql) => db.query(sql),
close: () => db.close(),
};
75 changes: 56 additions & 19 deletions src/database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import {
sqlite3_bind_double,
sqlite3_bind_int,
sqlite3_bind_int64,
sqlite3_bind_null,
sqlite3_bind_parameter_count,
sqlite3_bind_parameter_index,
sqlite3_bind_parameter_name,
Expand Down Expand Up @@ -76,7 +75,7 @@ export class Database {
#path: string;
#handle: sqlite3;

/** Path of the */
/** Path of the database file. */
get path(): string {
return this.#path;
}
Expand Down Expand Up @@ -266,7 +265,7 @@ export class Row {

/** Returns the names of the columns in the row. */
get columns(): string[] {
const columnCount = this.columnCount;
const columnCount = this.#stmt.columnCount;
const cols = new Array(columnCount);
for (let i = 0; i < columnCount; i++) {
cols[i] = this.#stmt.columnName(i);
Expand All @@ -276,7 +275,7 @@ export class Row {

/** Returns the row as array containing columns' values. */
asArray<T extends unknown[] = any[]>() {
const columnCount = this.columnCount;
const columnCount = this.#stmt.columnCount;
const array = new Array(columnCount);
for (let i = 0; i < columnCount; i++) {
array[i] = this.#stmt.column(i);
Expand All @@ -286,7 +285,7 @@ export class Row {

/** Returns the row as object with column names mapping to values. */
asObject<T extends Record<string, unknown> = Record<string, any>>(): T {
const columnCount = this.columnCount;
const columnCount = this.#stmt.columnCount;
const obj: Record<string, unknown> = {};
for (let i = 0; i < columnCount; i++) {
const name = this.#stmt.columnName(i);
Expand Down Expand Up @@ -339,6 +338,15 @@ export class PreparedStatement {
return sqlite3_bind_parameter_index(this.#handle, name);
}

#cstrCache = new Map<string, Uint8Array>();

#cstr(str: string) {
if (this.#cstrCache.has(str)) return this.#cstrCache.get(str)!;
const val = cstr(str);
this.#cstrCache.set(str, val);
return val;
}

/**
* We need to store references to any type that involves passing pointers
* to avoid V8's GC deallocating them before the statement is finalized.
Expand Down Expand Up @@ -386,7 +394,9 @@ export class PreparedStatement {

case "object":
if (value === null) {
sqlite3_bind_null(this.db.unsafeRawHandle, this.#handle, index);
// By default, SQLite sets non-binded values to null.
// so this call is not needed.
// sqlite3_bind_null(this.db.unsafeRawHandle, this.#handle, index);
} else if (value instanceof Uint8Array) {
this.#bufferRefs.add(value);
sqlite3_bind_blob(
Expand All @@ -412,7 +422,7 @@ export class PreparedStatement {
break;

case "string": {
const buffer = cstr(value);
const buffer = this.#cstr(value);
this.#bufferRefs.add(buffer);
sqlite3_bind_text(
this.db.unsafeRawHandle,
Expand Down Expand Up @@ -454,19 +464,32 @@ export class PreparedStatement {
}
}

#cachedColCount?: number;

/** Column count in current row. */
get columnCount() {
return sqlite3_column_count(this.#handle);
if (this.#cachedColCount !== undefined) return this.#cachedColCount;
return (this.#cachedColCount = sqlite3_column_count(this.#handle));
}

#colTypeCache = new Map<number, SqliteType>();

/** Return the data type of the column at given index in current row. */
columnType(index: number): SqliteType {
return sqlite3_column_type(this.#handle, index);
if (this.#colTypeCache.has(index)) return this.#colTypeCache.get(index)!;
const type = sqlite3_column_type(this.#handle, index);
this.#colTypeCache.set(index, type);
return type;
}

#colNameCache = new Map<number, string>();

/** Return the name of the column at given index in current row. */
columnName(index: number) {
return sqlite3_column_name(this.#handle, index);
if (this.#colNameCache.has(index)) return this.#colNameCache.get(index)!;
const name = sqlite3_column_name(this.#handle, index);
this.#colNameCache.set(index, name);
return name;
}

/** Return value of a column at given index in current row. */
Expand All @@ -486,14 +509,15 @@ export class PreparedStatement {

case SqliteType.BLOB: {
const blob = sqlite3_column_blob(this.#handle, index);
if (blob.value === 0n) return null;
const length = sqlite3_column_bytes(this.#handle, index);
const data = new Uint8Array(length);
new Deno.UnsafePointerView(blob).copyInto(data);
return data;
}

default:
throw new Error(`Unsupported column type: ${this.columnType(index)}`);
return null;
}
}

Expand All @@ -514,22 +538,35 @@ export class PreparedStatement {
sqlite3_reset(this.#db.unsafeRawHandle, this.#handle);
}

/** Finalize and run the prepared statement. */
/** Adds another step to prepared statement to be executed. Don't forget to call `finalize`. */
execute(...args: unknown[]) {
this.bindAll(...args);
this.step();
this.reset();
}

/**
* Finalize and run the prepared statement.
*
* This also frees up any resources related to the statement.
* And clears all references to the buffers as they're no longer
* needed, allowing V8 to GC them.
*/
finalize() {
try {
sqlite3_finalize(this.#db.unsafeRawHandle, this.#handle);
} finally {
this.#bufferRefs.clear();
this.#colTypeCache.clear();
this.#colNameCache.clear();
this.#cstrCache.clear();
this.#cachedColCount = undefined;
}
}

/** Adds another step to prepared statement to be executed. Don't forget to call `finalize`. */
execute(...args: unknown[]) {
this.bindAll(...args);
this.step();
this.reset();
}

/**
* Returns an iterator for rows.
*/
*[Symbol.iterator]() {
let row;
while ((row = this.step())) {
Expand Down
Loading

0 comments on commit d99722d

Please sign in to comment.