Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[POC] @stdext/sql implementation #4

Draft
wants to merge 5 commits into
base: main
Choose a base branch
from
Draft
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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
node_modules/
test-db/
*.db
*.db-wal
*.db-shm
lib/
tsconfig.tsbuildinfo
benchmarks/db
5 changes: 5 additions & 0 deletions packages/stdext-sql-adapter/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# stdext-sql-adapter

POC implementation of @stdext/sql on top of the @sqlite-js/driver API.

Loosly based on the implementation in https://github.com/denodrivers/sqlite3/tree/b1d0b0731323179648663761e134e1dc25b05324
27 changes: 27 additions & 0 deletions packages/stdext-sql-adapter/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"name": "stdext-sql-adapter",
"version": "0.0.1",
"description": "",
"type": "module",
"scripts": {
"build": "tsc -b",
"clean": "tsc -b --clean && rm -rf lib",
"test": "vitest"
},
"exports": {
".": "./lib/mod.js"
},
"keywords": [],
"author": "",
"license": "MIT",
"dependencies": {
"@sqlite-js/better-sqlite3-driver": "workspace:^",
"@sqlite-js/driver": "workspace:^",
"@stdext/sql": "workspace:^",
"@types/node": "^22.5.1"
},
"devDependencies": {
"typescript": "^5.5.4",
"vitest": "^2.0.5"
}
}
Empty file.
23 changes: 23 additions & 0 deletions packages/stdext-sql-adapter/src/better-sqlite-client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { BetterSqliteDriver } from '@sqlite-js/better-sqlite3-driver';
import { SqliteClientPool, SqliteClientOptions, SqliteClient } from './core.js';
import { fileURLToPath } from 'node:url';

export class BetterSqliteClient extends SqliteClient {
constructor(connectionUrl: string | URL, options: SqliteClientOptions = {}) {
if (typeof connectionUrl != 'string') {
connectionUrl = fileURLToPath(connectionUrl);
}
const pool = BetterSqliteDriver.openInProcess(connectionUrl);
super(connectionUrl, pool, options);
}
}

export class BetterSqliteClientPool extends SqliteClientPool {
constructor(connectionUrl: string | URL, options: SqliteClientOptions = {}) {
if (typeof connectionUrl != 'string') {
connectionUrl = fileURLToPath(connectionUrl);
}
const pool = BetterSqliteDriver.open(connectionUrl);
super(connectionUrl, pool, options);
}
}
167 changes: 167 additions & 0 deletions packages/stdext-sql-adapter/src/connection.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
// deno-lint-ignore-file require-await
import {
ReservedConnection,
SqliteDriverConnection,
SqliteDriverConnectionPool
} from '@sqlite-js/driver';
import type {
ArrayRow,
Row,
SqlConnectable,
SqlConnection,
SqlConnectionOptions
} from '@stdext/sql';
import type { SqliteParameterType, SqliteQueryOptions } from './core.js';
import { type DatabaseOpenOptions } from './database.js';

/** Various options that can be configured when opening Database connection. */
export interface SqliteConnectionOptions
extends SqlConnectionOptions,
DatabaseOpenOptions {}

export class SqliteConnection implements SqlConnection {
public driver: SqliteDriverConnection | undefined;

public readonly connectionUrl: string;

get connected(): boolean {
return this.driver != null;
}

public readonly options: SqliteConnectionOptions;

constructor(
connectionUrl: string,
driver: SqliteDriverConnection | undefined,
options?: SqliteConnectionOptions
) {
this.connectionUrl = connectionUrl;
this.driver = driver;
this.options = options ?? {};
}

async connect(): Promise<void> {
// No-op
}

async close(): Promise<void> {
await this.driver?.close();
}

async execute(
sql: string,
params?: SqliteParameterType[],
_options?: SqliteQueryOptions
): Promise<number | undefined> {
using statement = this.driver!.prepare(sql);
if (params != null) {
statement.bind(params);
}
const results = await statement.run();
return results.changes;
}

async *queryMany<T extends Row<any> = Row<any>>(
sql: string,
params?: SqliteParameterType[],
options?: SqliteQueryOptions
): AsyncGenerator<T, any, unknown> {
using statement = this.driver!.prepare(sql, {
bigint: this.options.int64 ?? false
});
if (params != null) {
statement.bind(params);
}
const chunkSize = 100;

while (true) {
const { rows, done } = await statement.step(chunkSize);
if (rows != null) {
const castRows = rows as T[];
for (let row of castRows) {
yield row;
}
}
if (done) {
break;
}
}
}

async *queryManyArray<T extends ArrayRow<any> = ArrayRow<any>>(
sql: string,
params?: SqliteParameterType[],
options?: SqliteQueryOptions
): AsyncGenerator<T, any, unknown> {
using statement = this.driver!.prepare(sql, {
bigint: this.options.int64 ?? false,
rawResults: true
});
if (params != null) {
statement.bind(params);
}
const chunkSize = 100;

while (true) {
const { rows, done } = await statement.step(chunkSize);
if (rows != null) {
const castRows = rows as T[];
for (let row of castRows) {
yield row;
}
}
if (done) {
break;
}
}
}

async [Symbol.asyncDispose](): Promise<void> {
await this.close();
}
}

export class SqliteReservedConnection extends SqliteConnection {
#connect: () => Promise<ReservedConnection>;
#reserved: ReservedConnection | undefined;

constructor(
connectionUrl: string,
connect: () => Promise<ReservedConnection>,
options?: SqliteConnectionOptions
) {
super(connectionUrl, undefined, options);
this.#connect = connect;
}

async connect(): Promise<void> {
this.#reserved = await this.#connect();
this.driver = this.#reserved.connection;
}

async close(): Promise<void> {
this.driver = undefined;
await this.#reserved?.release();
}
}

export class SqliteConnectable
implements SqlConnectable<SqliteConnectionOptions, SqliteConnection>
{
readonly connection: SqliteConnection;
readonly options: SqliteConnectionOptions;
get connected(): boolean {
return this.connection.connected;
}

constructor(
connection: SqliteConnectable['connection'],
options: SqliteConnectable['options'] = {}
) {
this.connection = connection;
this.options = options;
}
[Symbol.asyncDispose](): Promise<void> {
return this.connection.close();
}
}
Loading