@deterministic-code/datasources-sqlite 0.0.0-stage → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/SqliteCreateTableStatementRenderer.d.ts +6 -0
- package/dist/SqliteDriver.d.ts +12 -0
- package/dist/SqliteStatementRenderer.d.ts +6 -0
- package/dist/columnTypes.d.ts +1 -0
- package/dist/command.d.ts +13 -0
- package/dist/connection-options.d.ts +6 -0
- package/dist/connection.d.ts +20 -0
- package/dist/converters/BinaryConverter.d.ts +5 -0
- package/dist/converters/BoolConverter.d.ts +5 -0
- package/dist/converters/DateTimeConverter.d.ts +5 -0
- package/dist/converters/DecimalConverter.d.ts +5 -0
- package/dist/converters/SqliteRowTypeConverters.d.ts +3 -0
- package/dist/converters/UuidConverter.d.ts +5 -0
- package/dist/i-sqlite-driver.d.ts +17 -0
- package/dist/index.d.ts +2 -0
- package/dist/sqlitePath.d.ts +5 -0
- package/dist/transaction.d.ts +9 -0
- package/package.json +24 -4
- package/src/SqliteCreateTableStatementRenderer.ts +24 -0
- package/src/SqliteDriver.ts +53 -0
- package/src/SqliteStatementRenderer.test.ts +60 -0
- package/src/SqliteStatementRenderer.ts +28 -0
- package/src/columnTypes.ts +10 -0
- package/src/command.ts +231 -0
- package/src/connection-options.ts +7 -0
- package/src/connection.ts +83 -0
- package/src/converters/BinaryConverter.test.ts +11 -0
- package/src/converters/BinaryConverter.ts +6 -0
- package/src/converters/BoolConverter.test.ts +14 -0
- package/src/converters/BoolConverter.ts +6 -0
- package/src/converters/DateTimeConverter.test.ts +13 -0
- package/src/converters/DateTimeConverter.ts +8 -0
- package/src/converters/DecimalConverter.test.ts +11 -0
- package/src/converters/DecimalConverter.ts +7 -0
- package/src/converters/SqliteRowTypeConverters.test.ts +18 -0
- package/src/converters/SqliteRowTypeConverters.ts +24 -0
- package/src/converters/UuidConverter.test.ts +11 -0
- package/src/converters/UuidConverter.ts +6 -0
- package/src/createFromDriver.test.ts +15 -0
- package/src/expected-repository-console.test.ts +95 -0
- package/src/i-sqlite-driver.ts +21 -0
- package/src/index.ts +2 -0
- package/src/sqlite.test.ts +296 -0
- package/src/sqlitePath.ts +8 -0
- package/src/transaction.ts +21 -0
- package/README.md +0 -3
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { CreateTableStatement, CreateTableStatementTextRenderer, type Column } from "@datasources/core";
|
|
2
|
+
export declare class SqliteCreateTableStatementRenderer extends CreateTableStatementTextRenderer {
|
|
3
|
+
render(statement: CreateTableStatement): string;
|
|
4
|
+
protected typeName(column: Column): string;
|
|
5
|
+
protected identity(column: Column): string;
|
|
6
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { StatementTextRenderer, type Cell, type DatasourceOptions, type ICommand, type IConnection, type IDriver, type Information, type ITransaction, type IsolationLevel, type TypeConverter } from "@datasources/core";
|
|
2
|
+
import { columnTypes } from "./columnTypes.js";
|
|
3
|
+
export { columnTypes };
|
|
4
|
+
export declare class SqliteDriver implements IDriver {
|
|
5
|
+
readonly statementRenderer: StatementTextRenderer;
|
|
6
|
+
readonly information: Information;
|
|
7
|
+
rowTypeConverters: Record<string, TypeConverter<Cell, Cell>>;
|
|
8
|
+
columnType: (name: string) => string;
|
|
9
|
+
connection: (options?: DatasourceOptions) => IConnection;
|
|
10
|
+
command: (connection: IConnection) => ICommand;
|
|
11
|
+
transaction: (connection: IConnection, isolation?: IsolationLevel) => ITransaction;
|
|
12
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { CreateTableStatement, SelectInsertedIdStatement, StatementRenderer, type StatementTextRenderer } from "@datasources/core";
|
|
2
|
+
export declare class SqliteStatementRenderer extends StatementRenderer {
|
|
3
|
+
constructor(text: StatementTextRenderer);
|
|
4
|
+
protected visitSelectInsertedId(inserted: SelectInsertedIdStatement): string;
|
|
5
|
+
protected visitCreateTable(create: CreateTableStatement): string;
|
|
6
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const columnTypes: Record<string, string>;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { CommandMiddlewareHost, Native, StatementTextRenderer, Response, type Cells, type ICommand, type IReader, type IStatement, type Parameter } from "@datasources/core";
|
|
2
|
+
import type { SqliteConnection } from "./connection.js";
|
|
3
|
+
export declare class SqliteCommand extends CommandMiddlewareHost implements ICommand {
|
|
4
|
+
readonly connection: SqliteConnection;
|
|
5
|
+
private readonly statements;
|
|
6
|
+
constructor(connection: SqliteConnection, statements?: StatementTextRenderer);
|
|
7
|
+
executeReaderNative(text: Native, parameters?: Parameter[]): Promise<IReader<Cells>>;
|
|
8
|
+
executeReaderStatement(statement: IStatement, parameters?: Parameter[]): Promise<IReader<Cells>>;
|
|
9
|
+
executeStatement(statement: IStatement, parameters?: Parameter[]): Promise<Response>;
|
|
10
|
+
private firstCell;
|
|
11
|
+
executeNative(text: Native, parameters?: Parameter[]): Promise<Response>;
|
|
12
|
+
private iterate;
|
|
13
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { type DatasourceOptions, type ICommand, type IConnection, type IsolationLevelName, type ITransaction } from "@datasources/core";
|
|
2
|
+
import type { SqliteConnectionOptions } from "./connection-options.js";
|
|
3
|
+
import type { SqliteDatabase } from "./i-sqlite-driver.js";
|
|
4
|
+
export declare class SqliteConnection implements IConnection {
|
|
5
|
+
readonly provider = "sqlite";
|
|
6
|
+
filename: string;
|
|
7
|
+
isOpen: boolean;
|
|
8
|
+
private db?;
|
|
9
|
+
private readonly Database?;
|
|
10
|
+
static from: (options?: DatasourceOptions) => SqliteConnection;
|
|
11
|
+
constructor(options?: SqliteConnectionOptions);
|
|
12
|
+
createCommand: () => ICommand;
|
|
13
|
+
open(): Promise<void>;
|
|
14
|
+
close(): Promise<void>;
|
|
15
|
+
[Symbol.asyncDispose]: () => Promise<void>;
|
|
16
|
+
startTransaction(isolation?: IsolationLevelName): Promise<ITransaction>;
|
|
17
|
+
ensureOpen(): void;
|
|
18
|
+
require(): SqliteDatabase;
|
|
19
|
+
exec(sql: string): void;
|
|
20
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Cell, Cells, Scalar } from "@datasources/core";
|
|
2
|
+
export interface SqliteStatement {
|
|
3
|
+
run(params?: Record<string, Scalar>): {
|
|
4
|
+
changes: number;
|
|
5
|
+
lastInsertRowid?: Cell;
|
|
6
|
+
};
|
|
7
|
+
all(params?: Record<string, Scalar>): Cells[];
|
|
8
|
+
iterate(params?: Record<string, Scalar>): IterableIterator<Cells>;
|
|
9
|
+
}
|
|
10
|
+
export interface SqliteDatabase {
|
|
11
|
+
prepare(sql: string): SqliteStatement;
|
|
12
|
+
exec(sql: string): void;
|
|
13
|
+
close(): void;
|
|
14
|
+
}
|
|
15
|
+
export type SqliteDatabaseCtor = new (filename: string, options?: {
|
|
16
|
+
timeout?: number;
|
|
17
|
+
}) => SqliteDatabase;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type ITransaction, type IsolationLevelName } from "@datasources/core";
|
|
2
|
+
import type { SqliteConnection } from "./connection.js";
|
|
3
|
+
export declare class SqliteTransaction implements ITransaction {
|
|
4
|
+
readonly connection: SqliteConnection;
|
|
5
|
+
readonly isolation: IsolationLevelName;
|
|
6
|
+
constructor(connection: SqliteConnection, isolation?: IsolationLevelName);
|
|
7
|
+
commit(): Promise<void>;
|
|
8
|
+
abort(): Promise<void>;
|
|
9
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,26 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deterministic-code/datasources-sqlite",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "./src/index.ts",
|
|
6
|
+
"types": "./dist/index.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"import": "./src/index.ts",
|
|
11
|
+
"default": "./src/index.ts"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist",
|
|
16
|
+
"src"
|
|
17
|
+
],
|
|
18
|
+
"publishConfig": {
|
|
19
|
+
"access": "public"
|
|
20
|
+
},
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"@types/better-sqlite3": "^7.6.13",
|
|
23
|
+
"better-sqlite3": "^12.10.0",
|
|
24
|
+
"@deterministic-code/datasources": "^0.1.1"
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { CreateTableStatement, CreateTableStatementTextRenderer, PrimaryKey, type Column } from "@datasources/core";
|
|
2
|
+
import { columnTypes } from "./columnTypes.ts";
|
|
3
|
+
|
|
4
|
+
export class SqliteCreateTableStatementRenderer extends CreateTableStatementTextRenderer {
|
|
5
|
+
override render(statement: CreateTableStatement): string {
|
|
6
|
+
const identity = new Set(statement.columns.filter((column) => column.identity).map((column) => column.name));
|
|
7
|
+
const constraints = statement.constraints.filter(
|
|
8
|
+
(constraint) => !(constraint instanceof PrimaryKey) || !constraint.columns.every((column) => identity.has(column)),
|
|
9
|
+
);
|
|
10
|
+
return super.render(
|
|
11
|
+
constraints.length === statement.constraints.length
|
|
12
|
+
? statement
|
|
13
|
+
: new CreateTableStatement(statement.table, statement.columns, constraints),
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
protected typeName(column: Column): string {
|
|
18
|
+
return columnTypes[column.type] ?? column.type;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
protected identity(column: Column): string {
|
|
22
|
+
return column.identity ? " PRIMARY KEY AUTOINCREMENT" : "";
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DatasourceError,
|
|
3
|
+
StatementTextRenderer,
|
|
4
|
+
type Cell,
|
|
5
|
+
type DatasourceOptions,
|
|
6
|
+
type ICommand,
|
|
7
|
+
type IConnection,
|
|
8
|
+
type IDriver,
|
|
9
|
+
type Information,
|
|
10
|
+
type ITransaction,
|
|
11
|
+
type IsolationLevel,
|
|
12
|
+
type TypeConverter,
|
|
13
|
+
} from "@datasources/core";
|
|
14
|
+
import { SqliteConnection } from "./connection.ts";
|
|
15
|
+
import { SqliteCommand } from "./command.ts";
|
|
16
|
+
import { SqliteTransaction } from "./transaction.ts";
|
|
17
|
+
import { SqliteRowTypeConverters } from "./converters/SqliteRowTypeConverters.ts";
|
|
18
|
+
import { SqliteStatementRenderer } from "./SqliteStatementRenderer.ts";
|
|
19
|
+
import { columnTypes } from "./columnTypes.ts";
|
|
20
|
+
|
|
21
|
+
export { columnTypes };
|
|
22
|
+
|
|
23
|
+
const statements = new StatementTextRenderer(t => new SqliteStatementRenderer(t));
|
|
24
|
+
|
|
25
|
+
export class SqliteDriver implements IDriver {
|
|
26
|
+
readonly statementRenderer = statements;
|
|
27
|
+
readonly information: Information = {
|
|
28
|
+
descriptions: [
|
|
29
|
+
"Embedded SQL database for local development and tests.",
|
|
30
|
+
"Full connection, command, transaction, and repository support.",
|
|
31
|
+
],
|
|
32
|
+
overrides: [
|
|
33
|
+
"Overrides SelectInsertedIdStatement to SELECT last_insert_rowid() AS {column}",
|
|
34
|
+
"Overrides CreateTableStatement type mapping (int→INTEGER, bool→BOOLEAN, uuid/string/datetime→TEXT, decimal→NUMERIC, float→REAL, binary→BLOB)",
|
|
35
|
+
],
|
|
36
|
+
changeLogs: [],
|
|
37
|
+
};
|
|
38
|
+
rowTypeConverters: Record<string, TypeConverter<Cell, Cell>> = SqliteRowTypeConverters;
|
|
39
|
+
columnType = (name: string): string =>
|
|
40
|
+
columnTypes[name] ??
|
|
41
|
+
(() => {
|
|
42
|
+
throw new DatasourceError(`Unknown sqlite type "${name}"`);
|
|
43
|
+
})();
|
|
44
|
+
connection = (options?: DatasourceOptions): IConnection =>
|
|
45
|
+
SqliteConnection.from(options);
|
|
46
|
+
command = (connection: IConnection): ICommand =>
|
|
47
|
+
new SqliteCommand(connection as SqliteConnection, statements);
|
|
48
|
+
transaction = (
|
|
49
|
+
connection: IConnection,
|
|
50
|
+
isolation: IsolationLevel = "readCommitted",
|
|
51
|
+
): ITransaction =>
|
|
52
|
+
new SqliteTransaction(connection as SqliteConnection, isolation);
|
|
53
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ColumnDefault,
|
|
3
|
+
CreateTableStatement,
|
|
4
|
+
DefaultStatementTextRenderer,
|
|
5
|
+
ForeignKey,
|
|
6
|
+
SelectInsertedIdStatement,
|
|
7
|
+
NotNull,
|
|
8
|
+
PrimaryKey,
|
|
9
|
+
ReferentialAction,
|
|
10
|
+
SelectStatement,
|
|
11
|
+
StatementTextRenderer,
|
|
12
|
+
} from "@datasources/core";
|
|
13
|
+
import { describe, expect, it } from "vitest";
|
|
14
|
+
import { SqliteCreateTableStatementRenderer } from "./SqliteCreateTableStatementRenderer.ts";
|
|
15
|
+
import { SqliteStatementRenderer } from "./SqliteStatementRenderer.ts";
|
|
16
|
+
|
|
17
|
+
describe("SqliteStatementRenderer", () => {
|
|
18
|
+
const renderer = new StatementTextRenderer(t => new SqliteStatementRenderer(t));
|
|
19
|
+
|
|
20
|
+
it("renders inserted id and create table", () => {
|
|
21
|
+
expect(renderer.render(new SelectInsertedIdStatement("users", "id"))).toBe(
|
|
22
|
+
"SELECT last_insert_rowid() AS id",
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
const create = new CreateTableStatement(
|
|
26
|
+
"orders",
|
|
27
|
+
[
|
|
28
|
+
{ name: "id", type: "INTEGER", default: new ColumnDefault("0", "df_id"), notNull: new NotNull("nn_id") },
|
|
29
|
+
{ name: "user_id", type: "INTEGER" },
|
|
30
|
+
],
|
|
31
|
+
[
|
|
32
|
+
new PrimaryKey("pk_orders", ["id"]),
|
|
33
|
+
new ForeignKey("fk_orders_user", ["user_id"], "users", ["id"], ReferentialAction.Cascade),
|
|
34
|
+
],
|
|
35
|
+
);
|
|
36
|
+
expect(renderer.render(create)).toBe(new SqliteCreateTableStatementRenderer().render(create));
|
|
37
|
+
expect(renderer.render(new SelectStatement({ table: "users", fields: ["id"] }))).toBe(
|
|
38
|
+
"SELECT id FROM users",
|
|
39
|
+
);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("renders canonical types", () => {
|
|
43
|
+
const create = new CreateTableStatement("users", [
|
|
44
|
+
{ name: "id", type: "int", default: new ColumnDefault("0"), notNull: new NotNull(), identity: true },
|
|
45
|
+
]);
|
|
46
|
+
expect(renderer.render(create)).toBe(
|
|
47
|
+
"CREATE TABLE users (id INTEGER PRIMARY KEY AUTOINCREMENT DEFAULT 0 NOT NULL)",
|
|
48
|
+
);
|
|
49
|
+
expect(DefaultStatementTextRenderer.instance.render(create)).toBe(
|
|
50
|
+
"CREATE TABLE users (id int DEFAULT 0 NOT NULL)",
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
const keyed = new CreateTableStatement(
|
|
54
|
+
"users",
|
|
55
|
+
[{ name: "id", type: "int", identity: true }],
|
|
56
|
+
[new PrimaryKey("pk_users", ["id"])],
|
|
57
|
+
);
|
|
58
|
+
expect(renderer.render(keyed)).toBe("CREATE TABLE users (id INTEGER PRIMARY KEY AUTOINCREMENT)");
|
|
59
|
+
});
|
|
60
|
+
});
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CreateTableStatement,
|
|
3
|
+
SelectInsertedIdStatement,
|
|
4
|
+
SelectInsertedIdStatementTextRenderer,
|
|
5
|
+
StatementRenderer,
|
|
6
|
+
type StatementTextRenderer,
|
|
7
|
+
} from "@datasources/core";
|
|
8
|
+
import { SqliteCreateTableStatementRenderer } from "./SqliteCreateTableStatementRenderer.ts";
|
|
9
|
+
|
|
10
|
+
class SqliteSelectInsertedIdStatementTextRenderer extends SelectInsertedIdStatementTextRenderer {
|
|
11
|
+
override render(statement: SelectInsertedIdStatement): string {
|
|
12
|
+
return `SELECT last_insert_rowid() AS ${statement.column}`;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export class SqliteStatementRenderer extends StatementRenderer {
|
|
17
|
+
constructor(text: StatementTextRenderer) {
|
|
18
|
+
super(text);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
protected override visitSelectInsertedId(inserted: SelectInsertedIdStatement): string {
|
|
22
|
+
return new SqliteSelectInsertedIdStatementTextRenderer().render(inserted);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
protected override visitCreateTable(create: CreateTableStatement): string {
|
|
26
|
+
return new SqliteCreateTableStatementRenderer().render(create);
|
|
27
|
+
}
|
|
28
|
+
}
|
package/src/command.ts
ADDED
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ArrayReader,
|
|
3
|
+
CommandMiddlewareHost,
|
|
4
|
+
FirstResultReader,
|
|
5
|
+
DatasourceError,
|
|
6
|
+
Native,
|
|
7
|
+
ReturnStatement,
|
|
8
|
+
SelectStatement,
|
|
9
|
+
StatementTextRenderer,
|
|
10
|
+
parametersToRecord,
|
|
11
|
+
Response,
|
|
12
|
+
splitStatements,
|
|
13
|
+
statementParameters,
|
|
14
|
+
StreamingReader,
|
|
15
|
+
type Cell,
|
|
16
|
+
type Cells,
|
|
17
|
+
type Scalar,
|
|
18
|
+
type ICommand,
|
|
19
|
+
type IReader,
|
|
20
|
+
type IStatement,
|
|
21
|
+
type Parameter,
|
|
22
|
+
} from "@datasources/core";
|
|
23
|
+
import type { SqliteConnection } from "./connection.ts";
|
|
24
|
+
import type { SqliteStatement } from "./i-sqlite-driver.ts";
|
|
25
|
+
import { reverse, SqliteRowTypeConverters } from "./converters/SqliteRowTypeConverters.ts";
|
|
26
|
+
import { SqliteStatementRenderer } from "./SqliteStatementRenderer.ts";
|
|
27
|
+
|
|
28
|
+
const bound = (parameters?: Parameter[]): Record<string, Scalar> => {
|
|
29
|
+
const record = parametersToRecord(parameters);
|
|
30
|
+
return Object.fromEntries(
|
|
31
|
+
Object.entries(record).map(([name, value]) => [name, reverse(value)]),
|
|
32
|
+
) as Record<string, Scalar>;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const toSqlite = (sql: string, params: Record<string, Scalar>): { text: string; values: Scalar[] } => {
|
|
36
|
+
const values: Scalar[] = [];
|
|
37
|
+
const text = sql.replace(/\$([A-Za-z_][A-Za-z0-9_]*)/g, (_match, name: string) => {
|
|
38
|
+
values.push(params[name] ?? null);
|
|
39
|
+
return "?";
|
|
40
|
+
});
|
|
41
|
+
return { text, values };
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export class SqliteCommand extends CommandMiddlewareHost implements ICommand {
|
|
45
|
+
private readonly statements: StatementTextRenderer;
|
|
46
|
+
|
|
47
|
+
constructor(readonly connection: SqliteConnection, statements?: StatementTextRenderer) {
|
|
48
|
+
super();
|
|
49
|
+
this.statements = statements ?? new StatementTextRenderer(t => new SqliteStatementRenderer(t));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async executeReaderNative(
|
|
53
|
+
text: Native,
|
|
54
|
+
parameters?: Parameter[],
|
|
55
|
+
): Promise<IReader<Cells>> {
|
|
56
|
+
return this.run(text.text, parameters, async (query, params) => {
|
|
57
|
+
if (!query.trim()) return { value: new ArrayReader([]), rows: [] };
|
|
58
|
+
this.connection.ensureOpen();
|
|
59
|
+
const statements = splitStatements(query);
|
|
60
|
+
if (statements.length === 0) return { value: new ArrayReader([]), rows: [] };
|
|
61
|
+
const paramsBound = bound(params);
|
|
62
|
+
let index = 0;
|
|
63
|
+
let iter = this.iterate(statements[0] as string, paramsBound);
|
|
64
|
+
return FirstResultReader.capture(
|
|
65
|
+
new StreamingReader(
|
|
66
|
+
async () => {
|
|
67
|
+
const step = iter.next();
|
|
68
|
+
return step.done ? undefined : step.value;
|
|
69
|
+
},
|
|
70
|
+
async () => {
|
|
71
|
+
iter.return();
|
|
72
|
+
index += 1;
|
|
73
|
+
const sql = statements[index];
|
|
74
|
+
if (!sql) return false;
|
|
75
|
+
iter = this.iterate(sql, paramsBound);
|
|
76
|
+
return true;
|
|
77
|
+
},
|
|
78
|
+
async () => {
|
|
79
|
+
iter.return();
|
|
80
|
+
},
|
|
81
|
+
),
|
|
82
|
+
);
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async executeReaderStatement(
|
|
87
|
+
statement: IStatement,
|
|
88
|
+
parameters?: Parameter[],
|
|
89
|
+
): Promise<IReader<Cells>> {
|
|
90
|
+
return this.executeReaderNative(new Native(this.statements.render(statement)), parameters);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async executeStatement(statement: IStatement, parameters?: Parameter[]): Promise<Response> {
|
|
94
|
+
return statement instanceof SelectStatement || statement instanceof ReturnStatement
|
|
95
|
+
? this.firstCell(statement, parameters)
|
|
96
|
+
: this.executeNative(new Native(this.statements.render(statement)), parameters);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
private async firstCell(statement: IStatement, parameters?: Parameter[]): Promise<Response> {
|
|
100
|
+
const reader = await this.executeReaderStatement(statement, parameters);
|
|
101
|
+
try {
|
|
102
|
+
if (!(await reader.next())) return new Response(null, 0, parameters);
|
|
103
|
+
const row = reader.current;
|
|
104
|
+
/* v8 ignore next */
|
|
105
|
+
if (!row) throw new DatasourceError("Reader current must be a row");
|
|
106
|
+
const values = Object.values(row);
|
|
107
|
+
return new Response(values.length === 0 ? null : values[0], 1, parameters);
|
|
108
|
+
} finally {
|
|
109
|
+
await reader.dispose();
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async executeNative(text: Native, parameters?: Parameter[]): Promise<Response> {
|
|
114
|
+
return this.run(text.text, parameters, async (query, params) => {
|
|
115
|
+
if (!query.trim()) return { value: new Response(undefined, 0, params), rows: [] };
|
|
116
|
+
this.connection.ensureOpen();
|
|
117
|
+
const db = this.connection.require();
|
|
118
|
+
const named = bound(params);
|
|
119
|
+
let changes = 0;
|
|
120
|
+
for (const sql of splitStatements(query)) {
|
|
121
|
+
const names = new Set(statementParameters(sql));
|
|
122
|
+
const used = Object.fromEntries(
|
|
123
|
+
Object.entries(named).filter(([name]) => names.has(name.replace(/^[$:@]/, ""))),
|
|
124
|
+
);
|
|
125
|
+
const { text, values } = toSqlite(sql, used);
|
|
126
|
+
changes += db.prepare(text).run(...values).changes;
|
|
127
|
+
}
|
|
128
|
+
return { value: new Response(null, changes, params), rows: [] };
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
private iterate(sql: string, params: Record<string, Scalar>): IterableIterator<Cells> & {
|
|
133
|
+
return(): IteratorResult<Cells>;
|
|
134
|
+
} {
|
|
135
|
+
const statement = this.connection.require().prepare(sql) as ColumnStatement;
|
|
136
|
+
const columns = statement.columns?.() ?? [];
|
|
137
|
+
const names = qualified(columns);
|
|
138
|
+
const declared = columns.map((column) => column.type ?? null);
|
|
139
|
+
const raw = names.some((name, index) => name !== columns[index]?.name)
|
|
140
|
+
? statement.raw?.(true).iterate(params)
|
|
141
|
+
: undefined;
|
|
142
|
+
const rows = raw ? namedRows(raw, names, declared) : closable(statement.iterate(params), columns);
|
|
143
|
+
return rows[Symbol.iterator]();
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
type SqliteColumn = { name: string; column: string | null; table: string | null; type?: string | null };
|
|
148
|
+
|
|
149
|
+
const booleanDeclared = (declared?: string | null): boolean =>
|
|
150
|
+
declared?.toUpperCase() === "BOOLEAN";
|
|
151
|
+
|
|
152
|
+
const cell = (value: Cell | null | undefined, declared?: string | null): Cell | null =>
|
|
153
|
+
value == null ? null : booleanDeclared(declared) ? SqliteRowTypeConverters.bool.convert(value) : value;
|
|
154
|
+
|
|
155
|
+
type ColumnStatement = SqliteStatement & {
|
|
156
|
+
columns?: () => SqliteColumn[];
|
|
157
|
+
raw?: (enabled: boolean) => {
|
|
158
|
+
iterate(params?: Record<string, Scalar>): IterableIterator<Cell[]>;
|
|
159
|
+
};
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
const closable = (
|
|
163
|
+
iter: IterableIterator<Cells>,
|
|
164
|
+
columns: SqliteColumn[],
|
|
165
|
+
): IterableIterator<Cells> & { return(): IteratorResult<Cells> } => {
|
|
166
|
+
const stop = iter.return ? iter.return.bind(iter) : undefined;
|
|
167
|
+
const rows: IterableIterator<Cells> & { return(): IteratorResult<Cells> } = {
|
|
168
|
+
next: () => {
|
|
169
|
+
const step = iter.next();
|
|
170
|
+
if (step.done || !step.value) return step;
|
|
171
|
+
const value = Object.fromEntries(
|
|
172
|
+
Object.entries(step.value).map(([name, item]) => [
|
|
173
|
+
name,
|
|
174
|
+
cell(item, columns.find((column) => column.name === name)?.type),
|
|
175
|
+
]),
|
|
176
|
+
) as Cells;
|
|
177
|
+
return { done: false, value };
|
|
178
|
+
},
|
|
179
|
+
return: () => {
|
|
180
|
+
if (stop) stop();
|
|
181
|
+
return { done: true, value: undefined };
|
|
182
|
+
},
|
|
183
|
+
[Symbol.iterator]() {
|
|
184
|
+
return rows;
|
|
185
|
+
},
|
|
186
|
+
};
|
|
187
|
+
return rows;
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
const namedRows = (
|
|
191
|
+
iter: IterableIterator<Cell[]>,
|
|
192
|
+
names: string[],
|
|
193
|
+
declared: (string | null)[],
|
|
194
|
+
): IterableIterator<Cells> & { return(): IteratorResult<Cells> } => {
|
|
195
|
+
/* v8 ignore next */
|
|
196
|
+
const stop = iter.return ? iter.return.bind(iter) : undefined;
|
|
197
|
+
const rows: IterableIterator<Cells> & { return(): IteratorResult<Cells> } = {
|
|
198
|
+
next: () => {
|
|
199
|
+
const step = iter.next();
|
|
200
|
+
return step.done
|
|
201
|
+
? { done: true, value: undefined }
|
|
202
|
+
: {
|
|
203
|
+
done: false,
|
|
204
|
+
value: Object.fromEntries(
|
|
205
|
+
names.map((name, index) => [name, cell(step.value[index], declared[index])]),
|
|
206
|
+
),
|
|
207
|
+
};
|
|
208
|
+
},
|
|
209
|
+
return: () => {
|
|
210
|
+
/* v8 ignore next */
|
|
211
|
+
if (stop) stop();
|
|
212
|
+
return { done: true, value: undefined };
|
|
213
|
+
},
|
|
214
|
+
[Symbol.iterator]() {
|
|
215
|
+
return rows;
|
|
216
|
+
},
|
|
217
|
+
};
|
|
218
|
+
return rows;
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
const qualified = (columns: SqliteColumn[]): string[] => {
|
|
222
|
+
const names = columns.map((column) => column.name);
|
|
223
|
+
const duplicated = new Set(names.filter((name, index) => names.indexOf(name) !== index));
|
|
224
|
+
return duplicated.size === 0
|
|
225
|
+
? names
|
|
226
|
+
: columns.map((column, index) =>
|
|
227
|
+
duplicated.has(names[index]!) && column.table && column.column
|
|
228
|
+
? `${column.table}.${column.column}`
|
|
229
|
+
: names[index]!
|
|
230
|
+
);
|
|
231
|
+
};
|