@deterministic-code/datasources-postgres 0.1.0 → 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/PostgresBulkUpdateStatementTextRenderer.d.ts +4 -0
- package/dist/PostgresCreateTableStatementRenderer.d.ts +6 -0
- package/dist/PostgresDriver.d.ts +12 -0
- package/dist/PostgresStatementRenderer.d.ts +7 -0
- package/dist/bind.d.ts +7 -0
- package/dist/columnTypes.d.ts +1 -0
- package/dist/command.d.ts +14 -0
- package/dist/connection-options.d.ts +6 -0
- package/dist/connection.d.ts +21 -0
- package/dist/converters/BinaryConverter.d.ts +6 -0
- package/dist/converters/BoolConverter.d.ts +5 -0
- package/dist/converters/DateTimeConverter.d.ts +6 -0
- package/dist/converters/DecimalConverter.d.ts +5 -0
- package/dist/converters/PostgresRowTypeConverters.d.ts +3 -0
- package/dist/converters/UuidConverter.d.ts +5 -0
- package/dist/i-postgres-driver.d.ts +38 -0
- package/dist/index.d.ts +1 -0
- package/dist/scalar.d.ts +3 -0
- package/dist/transaction.d.ts +9 -0
- package/package.json +10 -6
- package/src/PostgresBulkUpdateStatementTextRenderer.ts +1 -1
- package/src/PostgresCreateTableStatementRenderer.ts +1 -1
- package/src/PostgresDriver.ts +1 -1
- package/src/PostgresStatementRenderer.test.ts +105 -0
- package/src/PostgresStatementRenderer.ts +1 -1
- package/src/bind.ts +1 -1
- package/src/command.ts +1 -1
- package/src/connection.ts +1 -1
- package/src/converters/BinaryConverter.test.ts +14 -0
- package/src/converters/BinaryConverter.ts +1 -1
- package/src/converters/BoolConverter.test.ts +14 -0
- package/src/converters/BoolConverter.ts +1 -1
- package/src/converters/DateTimeConverter.test.ts +13 -0
- package/src/converters/DateTimeConverter.ts +1 -1
- package/src/converters/DecimalConverter.test.ts +11 -0
- package/src/converters/DecimalConverter.ts +1 -1
- package/src/converters/PostgresRowTypeConverters.test.ts +18 -0
- package/src/converters/PostgresRowTypeConverters.ts +1 -1
- package/src/converters/UuidConverter.test.ts +11 -0
- package/src/converters/UuidConverter.ts +1 -1
- package/src/i-postgres-driver.ts +1 -1
- package/src/postgres.test.ts +345 -0
- package/src/transaction.ts +1 -1
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { CreateTableStatementTextRenderer, type Column } from "@datasources/core";
|
|
2
|
+
export declare class PostgresCreateTableStatementRenderer extends CreateTableStatementTextRenderer {
|
|
3
|
+
protected typeName(column: Column): string;
|
|
4
|
+
protected columnDefault(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 PostgresDriver 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,7 @@
|
|
|
1
|
+
import { BulkUpdateStatement, CreateTableStatement, SelectInsertedIdStatement, StatementRenderer, type StatementTextRenderer } from "@datasources/core";
|
|
2
|
+
export declare class PostgresStatementRenderer extends StatementRenderer {
|
|
3
|
+
constructor(text: StatementTextRenderer);
|
|
4
|
+
protected visitSelectInsertedId(inserted: SelectInsertedIdStatement): string;
|
|
5
|
+
protected visitCreateTable(create: CreateTableStatement): string;
|
|
6
|
+
protected visitBulkUpdate(update: BulkUpdateStatement): string;
|
|
7
|
+
}
|
package/dist/bind.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type Parameter } from "@datasources/core";
|
|
2
|
+
import type { Scalar, Wire } from "./scalar.js";
|
|
3
|
+
export declare const toPostgres: (sql: string, params?: Record<string, Scalar>) => {
|
|
4
|
+
text: string;
|
|
5
|
+
values: Wire[];
|
|
6
|
+
};
|
|
7
|
+
export declare const bindParameters: (parameters?: Parameter[] | null) => Record<string, Scalar>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const columnTypes: Record<string, string>;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { CommandMiddlewareHost, Native, StatementTextRenderer, Response, type Cells, type ICommand, type IReader, type IStatement, type Parameter } from "@datasources/core";
|
|
2
|
+
import type { PostgresConnection } from "./connection.js";
|
|
3
|
+
export declare class PostgresCommand extends CommandMiddlewareHost implements ICommand {
|
|
4
|
+
readonly connection: PostgresConnection;
|
|
5
|
+
private readonly statements;
|
|
6
|
+
constructor(connection: PostgresConnection, 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 runSql;
|
|
13
|
+
private query;
|
|
14
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type DatasourceOptions, type ICommand, type IConnection, type IsolationLevelName, type ITransaction } from "@datasources/core";
|
|
2
|
+
import type { PostgresConnectionOptions } from "./connection-options.js";
|
|
3
|
+
import { type IPgClient } from "./i-postgres-driver.js";
|
|
4
|
+
export declare class PostgresConnection implements IConnection {
|
|
5
|
+
readonly provider = "postgres";
|
|
6
|
+
url: string;
|
|
7
|
+
schema?: string;
|
|
8
|
+
isOpen: boolean;
|
|
9
|
+
private client?;
|
|
10
|
+
private readonly Client?;
|
|
11
|
+
static from: (options?: DatasourceOptions) => PostgresConnection;
|
|
12
|
+
constructor(options?: PostgresConnectionOptions);
|
|
13
|
+
createCommand: () => ICommand;
|
|
14
|
+
open(): Promise<void>;
|
|
15
|
+
close(): Promise<void>;
|
|
16
|
+
[Symbol.asyncDispose]: () => Promise<void>;
|
|
17
|
+
startTransaction(isolation?: IsolationLevelName): Promise<ITransaction>;
|
|
18
|
+
ensureOpen(): void;
|
|
19
|
+
require(): IPgClient;
|
|
20
|
+
exec(sql: string): Promise<void>;
|
|
21
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { TypeConverter, type Cell } from "@datasources/core";
|
|
2
|
+
export declare const isBase64: (value: string) => boolean;
|
|
3
|
+
export declare class BinaryConverter extends TypeConverter<Cell, Cell> {
|
|
4
|
+
convert: (from: Cell) => Cell;
|
|
5
|
+
reverseConvert: (to: Cell) => Cell;
|
|
6
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { TypeConverter, type Cell } from "@datasources/core";
|
|
2
|
+
export declare const isIsoDate: (value: string) => boolean;
|
|
3
|
+
export declare class DateTimeConverter extends TypeConverter<Cell, Cell> {
|
|
4
|
+
convert: (from: Cell) => Cell;
|
|
5
|
+
reverseConvert: (to: Cell) => Cell;
|
|
6
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { Parameter } from "@datasources/core";
|
|
2
|
+
import QueryStream from "pg-query-stream";
|
|
3
|
+
import type { Cells, Scalar, Wire } from "./scalar.js";
|
|
4
|
+
export interface PostgresExecuteResult {
|
|
5
|
+
rowsAffected: number;
|
|
6
|
+
result?: Scalar;
|
|
7
|
+
parameters?: Parameter[];
|
|
8
|
+
}
|
|
9
|
+
export interface PgQueryResult {
|
|
10
|
+
rowCount: number | null;
|
|
11
|
+
rows: Cells[];
|
|
12
|
+
}
|
|
13
|
+
export interface IPgClient {
|
|
14
|
+
connect(): Promise<void>;
|
|
15
|
+
query(text: string, values?: Wire[]): Promise<PgQueryResult>;
|
|
16
|
+
rows(text: string, values?: Wire[]): AsyncIterable<Cells>;
|
|
17
|
+
end(): Promise<void>;
|
|
18
|
+
}
|
|
19
|
+
interface PgNativeClient {
|
|
20
|
+
connect(): Promise<void>;
|
|
21
|
+
query(text: string, values?: Wire[]): Promise<PgQueryResult>;
|
|
22
|
+
query(query: QueryStream): AsyncIterable<Cells>;
|
|
23
|
+
end(): Promise<void>;
|
|
24
|
+
}
|
|
25
|
+
export type PgClientCtor = new (options: {
|
|
26
|
+
connectionString: string;
|
|
27
|
+
}) => IPgClient;
|
|
28
|
+
type PgNativeCtor = new (options: {
|
|
29
|
+
connectionString: string;
|
|
30
|
+
}) => PgNativeClient;
|
|
31
|
+
export type PgModule = {
|
|
32
|
+
Client: PgNativeCtor;
|
|
33
|
+
default?: {
|
|
34
|
+
Client: PgNativeCtor;
|
|
35
|
+
};
|
|
36
|
+
};
|
|
37
|
+
export declare const pgClient: (imported: PgModule) => PgClientCtor;
|
|
38
|
+
export {};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { PostgresDriver } from "./PostgresDriver.js";
|
package/dist/scalar.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type ITransaction, type IsolationLevelName } from "@datasources/core";
|
|
2
|
+
import type { PostgresConnection } from "./connection.js";
|
|
3
|
+
export declare class PostgresTransaction implements ITransaction {
|
|
4
|
+
readonly connection: PostgresConnection;
|
|
5
|
+
readonly isolation: IsolationLevelName;
|
|
6
|
+
constructor(connection: PostgresConnection, isolation?: IsolationLevelName);
|
|
7
|
+
commit(): Promise<void>;
|
|
8
|
+
abort(): Promise<void>;
|
|
9
|
+
}
|
package/package.json
CHANGED
|
@@ -1,23 +1,27 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deterministic-code/datasources-postgres",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
|
-
"types": "./
|
|
6
|
+
"types": "./dist/index.d.ts",
|
|
7
7
|
"exports": {
|
|
8
|
-
".":
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"import": "./src/index.ts",
|
|
11
|
+
"default": "./src/index.ts"
|
|
12
|
+
}
|
|
9
13
|
},
|
|
10
14
|
"files": [
|
|
15
|
+
"dist",
|
|
11
16
|
"src"
|
|
12
17
|
],
|
|
13
18
|
"publishConfig": {
|
|
14
19
|
"access": "public"
|
|
15
20
|
},
|
|
16
|
-
"license": "MIT",
|
|
17
21
|
"dependencies": {
|
|
18
|
-
"@deterministic-code/datasources": "^0.1.0",
|
|
19
22
|
"@types/pg": "^8.15.5",
|
|
20
23
|
"pg": "^8.16.0",
|
|
21
|
-
"pg-query-stream": "^4.17.0"
|
|
24
|
+
"pg-query-stream": "^4.17.0",
|
|
25
|
+
"@deterministic-code/datasources": "^0.1.1"
|
|
22
26
|
}
|
|
23
27
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BulkUpdateStatementTextRenderer, type Cell } from "@
|
|
1
|
+
import { BulkUpdateStatementTextRenderer, type Cell } from "@datasources/core";
|
|
2
2
|
import { columnTypes } from "./columnTypes.ts";
|
|
3
3
|
import { isBase64 } from "./converters/BinaryConverter.ts";
|
|
4
4
|
import { isIsoDate } from "./converters/DateTimeConverter.ts";
|
|
@@ -2,7 +2,7 @@ import {
|
|
|
2
2
|
ColumnDefault,
|
|
3
3
|
CreateTableStatementTextRenderer,
|
|
4
4
|
type Column,
|
|
5
|
-
} from "@
|
|
5
|
+
} from "@datasources/core";
|
|
6
6
|
import { PostgresRowTypeConverters } from "./converters/PostgresRowTypeConverters.ts";
|
|
7
7
|
import { columnTypes } from "./columnTypes.ts";
|
|
8
8
|
|
package/src/PostgresDriver.ts
CHANGED
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
type ITransaction,
|
|
11
11
|
type IsolationLevel,
|
|
12
12
|
type TypeConverter,
|
|
13
|
-
} from "@
|
|
13
|
+
} from "@datasources/core";
|
|
14
14
|
import { PostgresConnection } from "./connection.ts";
|
|
15
15
|
import { PostgresCommand } from "./command.ts";
|
|
16
16
|
import { PostgresTransaction } from "./transaction.ts";
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BulkUpdateStatement,
|
|
3
|
+
type Cell,
|
|
4
|
+
ColumnDefault,
|
|
5
|
+
CreateTableStatement,
|
|
6
|
+
DefaultStatementTextRenderer,
|
|
7
|
+
ForeignKey,
|
|
8
|
+
SelectInsertedIdStatement,
|
|
9
|
+
NotNull,
|
|
10
|
+
PrimaryKey,
|
|
11
|
+
ReferentialAction,
|
|
12
|
+
SelectStatement,
|
|
13
|
+
StatementTextRenderer,
|
|
14
|
+
} from "@datasources/core";
|
|
15
|
+
import { describe, expect, it } from "vitest";
|
|
16
|
+
import { PostgresCreateTableStatementRenderer } from "./PostgresCreateTableStatementRenderer.ts";
|
|
17
|
+
import { PostgresStatementRenderer } from "./PostgresStatementRenderer.ts";
|
|
18
|
+
|
|
19
|
+
describe("PostgresStatementRenderer", () => {
|
|
20
|
+
const renderer = new StatementTextRenderer(t => new PostgresStatementRenderer(t));
|
|
21
|
+
|
|
22
|
+
it("renders inserted id and create table", () => {
|
|
23
|
+
expect(renderer.render(new SelectInsertedIdStatement("users", "id"))).toBe(
|
|
24
|
+
"SELECT lastval() AS id",
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
const create = new CreateTableStatement(
|
|
28
|
+
"orders",
|
|
29
|
+
[
|
|
30
|
+
{ name: "id", type: "INTEGER", default: new ColumnDefault("0", "df_id"), notNull: new NotNull("nn_id") },
|
|
31
|
+
{ name: "user_id", type: "INTEGER" },
|
|
32
|
+
],
|
|
33
|
+
[
|
|
34
|
+
new PrimaryKey("pk_orders", ["id"]),
|
|
35
|
+
new ForeignKey("fk_orders_user", ["user_id"], "users", ["id"], ReferentialAction.Cascade),
|
|
36
|
+
],
|
|
37
|
+
);
|
|
38
|
+
expect(renderer.render(create)).toBe(new PostgresCreateTableStatementRenderer().render(create));
|
|
39
|
+
expect(renderer.render(new SelectStatement({ table: "users", fields: ["id"] }))).toBe(
|
|
40
|
+
"SELECT id FROM users",
|
|
41
|
+
);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it("renders type default and identity", () => {
|
|
45
|
+
const create = new CreateTableStatement("users", [
|
|
46
|
+
{ name: "id", type: "int", identity: true },
|
|
47
|
+
{ name: "is_enabled", type: "bool", default: new ColumnDefault("1") },
|
|
48
|
+
{ name: "is_disabled", type: "bool", default: new ColumnDefault("0", "df_off") },
|
|
49
|
+
{ name: "amount", type: "decimal", default: new ColumnDefault("1") },
|
|
50
|
+
{ name: "flag", type: "bool", default: new ColumnDefault("true") },
|
|
51
|
+
]);
|
|
52
|
+
expect(renderer.render(create)).toBe(
|
|
53
|
+
"CREATE TABLE users (id INTEGER GENERATED BY DEFAULT AS IDENTITY, is_enabled BOOLEAN DEFAULT TRUE, is_disabled BOOLEAN CONSTRAINT df_off DEFAULT FALSE, amount NUMERIC DEFAULT 1, flag BOOLEAN DEFAULT true)",
|
|
54
|
+
);
|
|
55
|
+
expect(DefaultStatementTextRenderer.instance.render(create)).toBe(
|
|
56
|
+
"CREATE TABLE users (id int, is_enabled bool DEFAULT 1, is_disabled bool CONSTRAINT df_off DEFAULT 0, amount decimal DEFAULT 1, flag bool DEFAULT true)",
|
|
57
|
+
);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it("renders bulk update with column types", () => {
|
|
61
|
+
const update = new BulkUpdateStatement("parents", ["label"], ["id"], [
|
|
62
|
+
{ fields: [{ name: "label", value: "a" }], ids: [{ name: "id", value: 1 }] },
|
|
63
|
+
{ fields: [{ name: "label", value: "b" }], ids: [{ name: "id", value: 2 }] },
|
|
64
|
+
]);
|
|
65
|
+
expect(renderer.render(update)).toBe(
|
|
66
|
+
"UPDATE parents SET label = data.label FROM (SELECT $label__0::TEXT AS label, $id__0::INTEGER AS id UNION ALL SELECT $label__1::TEXT AS label, $id__1::INTEGER AS id) AS data WHERE parents.id = data.id",
|
|
67
|
+
);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it("renders bulk update cell types", () => {
|
|
71
|
+
const update = new BulkUpdateStatement("t", ["flag", "n", "at", "blob", "x"], ["id"], [
|
|
72
|
+
{
|
|
73
|
+
fields: [
|
|
74
|
+
{ name: "flag", value: true },
|
|
75
|
+
{ name: "n", value: 1.5 },
|
|
76
|
+
{ name: "at", value: new Date("2020-01-02T03:04:05.000Z") },
|
|
77
|
+
{ name: "blob", value: new Uint8Array([1]) },
|
|
78
|
+
{ name: "x", value: null },
|
|
79
|
+
] as { name: string; value: Cell }[],
|
|
80
|
+
ids: [{ name: "id", value: 1n }],
|
|
81
|
+
},
|
|
82
|
+
]);
|
|
83
|
+
expect(renderer.render(update)).toBe(
|
|
84
|
+
"UPDATE t SET flag = data.flag, n = data.n, at = data.at, blob = data.blob, x = data.x FROM (SELECT $flag__0::BOOLEAN AS flag, $n__0::DOUBLE PRECISION AS n, $at__0::TIMESTAMPTZ AS at, $blob__0::BYTEA AS blob, $x__0 AS x, $id__0::INTEGER AS id) AS data WHERE t.id = data.id",
|
|
85
|
+
);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it("renders bulk update iso created as timestamptz", () => {
|
|
89
|
+
const update = new BulkUpdateStatement("contacts", ["created"], ["id"], [
|
|
90
|
+
{ fields: [{ name: "created", value: "2020-01-02T03:04:05.000Z" }], ids: [{ name: "id", value: 1 }] },
|
|
91
|
+
]);
|
|
92
|
+
expect(renderer.render(update)).toBe(
|
|
93
|
+
"UPDATE contacts SET created = data.created FROM (SELECT $created__0::TIMESTAMPTZ AS created, $id__0::INTEGER AS id) AS data WHERE contacts.id = data.id",
|
|
94
|
+
);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it("renders bulk update base64 version as bytea", () => {
|
|
98
|
+
const update = new BulkUpdateStatement("contacts", ["version"], ["id"], [
|
|
99
|
+
{ fields: [{ name: "version", value: "AA==" }], ids: [{ name: "id", value: 1 }] },
|
|
100
|
+
]);
|
|
101
|
+
expect(renderer.render(update)).toBe(
|
|
102
|
+
"UPDATE contacts SET version = data.version FROM (SELECT $version__0::BYTEA AS version, $id__0::INTEGER AS id) AS data WHERE contacts.id = data.id",
|
|
103
|
+
);
|
|
104
|
+
});
|
|
105
|
+
});
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
SelectInsertedIdStatementTextRenderer,
|
|
6
6
|
StatementRenderer,
|
|
7
7
|
type StatementTextRenderer,
|
|
8
|
-
} from "@
|
|
8
|
+
} from "@datasources/core";
|
|
9
9
|
import { PostgresBulkUpdateStatementTextRenderer } from "./PostgresBulkUpdateStatementTextRenderer.ts";
|
|
10
10
|
import { PostgresCreateTableStatementRenderer } from "./PostgresCreateTableStatementRenderer.ts";
|
|
11
11
|
|
package/src/bind.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { parametersToRecord, type Cell, type Parameter } from "@
|
|
1
|
+
import { parametersToRecord, type Cell, type Parameter } from "@datasources/core";
|
|
2
2
|
import { isBase64 } from "./converters/BinaryConverter.ts";
|
|
3
3
|
import { isIsoDate } from "./converters/DateTimeConverter.ts";
|
|
4
4
|
import { reverse } from "./converters/PostgresRowTypeConverters.ts";
|
package/src/command.ts
CHANGED
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
type IReader,
|
|
18
18
|
type IStatement,
|
|
19
19
|
type Parameter,
|
|
20
|
-
} from "@
|
|
20
|
+
} from "@datasources/core";
|
|
21
21
|
import { bindParameters, toPostgres } from "./bind.ts";
|
|
22
22
|
import type { PostgresConnection } from "./connection.ts";
|
|
23
23
|
import type { PostgresExecuteResult } from "./i-postgres-driver.ts";
|
package/src/connection.ts
CHANGED
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
type IConnection,
|
|
9
9
|
type IsolationLevelName,
|
|
10
10
|
type ITransaction,
|
|
11
|
-
} from "@
|
|
11
|
+
} from "@datasources/core";
|
|
12
12
|
import { createRequire } from "node:module";
|
|
13
13
|
import { PostgresCommand } from "./command.ts";
|
|
14
14
|
import type { PostgresConnectionOptions } from "./connection-options.ts";
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { BinaryConverter, isBase64 } from "./BinaryConverter.ts";
|
|
3
|
+
|
|
4
|
+
describe("BinaryConverter", () => {
|
|
5
|
+
it("maps binary", () => {
|
|
6
|
+
const converter = new BinaryConverter();
|
|
7
|
+
const raw = new Uint8Array([1, 2, 3, 4]);
|
|
8
|
+
expect(converter.convert(raw)).toBe(raw);
|
|
9
|
+
expect(converter.reverseConvert(raw)).toBe(raw);
|
|
10
|
+
expect(converter.reverseConvert("AQIDBA==")).toEqual(raw);
|
|
11
|
+
expect(isBase64("AA==")).toBe(true);
|
|
12
|
+
expect(isBase64("ada")).toBe(false);
|
|
13
|
+
});
|
|
14
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { BoolConverter } from "./BoolConverter.ts";
|
|
3
|
+
|
|
4
|
+
describe("BoolConverter", () => {
|
|
5
|
+
it("maps bool", () => {
|
|
6
|
+
const converter = new BoolConverter();
|
|
7
|
+
expect(converter.convert(1)).toBe(true);
|
|
8
|
+
expect(converter.convert(0)).toBe(false);
|
|
9
|
+
expect(converter.convert(true)).toBe(true);
|
|
10
|
+
expect(converter.reverseConvert(true)).toBe(true);
|
|
11
|
+
expect(converter.reverseConvert(false)).toBe(false);
|
|
12
|
+
expect(converter.reverseConvert("x")).toBe("x");
|
|
13
|
+
});
|
|
14
|
+
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { TypeConverter, type Cell } from "@
|
|
1
|
+
import { TypeConverter, type Cell } from "@datasources/core";
|
|
2
2
|
|
|
3
3
|
export class BoolConverter extends TypeConverter<Cell, Cell> {
|
|
4
4
|
convert = (from: Cell): Cell => (typeof from === "number" ? from !== 0 : from);
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { DateTimeConverter } from "./DateTimeConverter.ts";
|
|
3
|
+
|
|
4
|
+
describe("DateTimeConverter", () => {
|
|
5
|
+
it("maps datetime", () => {
|
|
6
|
+
const converter = new DateTimeConverter();
|
|
7
|
+
const date = new Date("2020-01-02T03:04:05.000Z");
|
|
8
|
+
expect(converter.convert("2020-01-02T03:04:05.000Z")).toEqual(date);
|
|
9
|
+
expect(converter.convert(date)).toBe(date);
|
|
10
|
+
expect(converter.reverseConvert(date)).toBe("2020-01-02T03:04:05.000Z");
|
|
11
|
+
expect(converter.reverseConvert("x")).toBe("x");
|
|
12
|
+
});
|
|
13
|
+
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { DecimalConverter } from "./DecimalConverter.ts";
|
|
3
|
+
|
|
4
|
+
describe("DecimalConverter", () => {
|
|
5
|
+
it("maps decimal", () => {
|
|
6
|
+
const converter = new DecimalConverter();
|
|
7
|
+
expect(converter.convert("10.5")).toBe("10.5");
|
|
8
|
+
expect(converter.reverseConvert(10.5)).toBe("10.5");
|
|
9
|
+
expect(converter.reverseConvert("10.5")).toBe("10.5");
|
|
10
|
+
});
|
|
11
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { PostgresDriver } from "../PostgresDriver.ts";
|
|
3
|
+
import { reverse, PostgresRowTypeConverters } from "./PostgresRowTypeConverters.ts";
|
|
4
|
+
|
|
5
|
+
describe("PostgresRowTypeConverters", () => {
|
|
6
|
+
it("registers default types", () => {
|
|
7
|
+
expect(Object.keys(new PostgresDriver().rowTypeConverters)).toEqual(Object.keys(PostgresRowTypeConverters));
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it("reverses booleans, dates, and bytes", () => {
|
|
11
|
+
const when = new Date("2024-01-02T00:00:00.000Z");
|
|
12
|
+
expect(reverse(true)).toBe(true);
|
|
13
|
+
expect(reverse(false)).toBe(false);
|
|
14
|
+
expect(reverse(when)).toBe("2024-01-02T00:00:00.000Z");
|
|
15
|
+
expect(reverse(new Uint8Array([1, 2]))).toEqual(new Uint8Array([1, 2]));
|
|
16
|
+
expect(reverse("a")).toBe("a");
|
|
17
|
+
});
|
|
18
|
+
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type Cell, type TypeConverter } from "@
|
|
1
|
+
import { type Cell, type TypeConverter } from "@datasources/core";
|
|
2
2
|
import { BinaryConverter } from "./BinaryConverter.ts";
|
|
3
3
|
import { BoolConverter } from "./BoolConverter.ts";
|
|
4
4
|
import { DateTimeConverter } from "./DateTimeConverter.ts";
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { UuidConverter } from "./UuidConverter.ts";
|
|
3
|
+
|
|
4
|
+
describe("UuidConverter", () => {
|
|
5
|
+
it("maps uuid", () => {
|
|
6
|
+
const converter = new UuidConverter();
|
|
7
|
+
const id = "550e8400-e29b-41d4-a716-446655440000";
|
|
8
|
+
expect(converter.convert(id)).toBe(id);
|
|
9
|
+
expect(converter.reverseConvert(id)).toBe(id);
|
|
10
|
+
});
|
|
11
|
+
});
|
package/src/i-postgres-driver.ts
CHANGED
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
|
|
3
|
+
vi.mock("pg", () => {
|
|
4
|
+
class Client {
|
|
5
|
+
async connect(): Promise<void> {}
|
|
6
|
+
async query() {
|
|
7
|
+
return { rowCount: 0, rows: [] };
|
|
8
|
+
}
|
|
9
|
+
async end(): Promise<void> {}
|
|
10
|
+
}
|
|
11
|
+
return { Client, default: { Client } };
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
import { Datasource, DatasourceFactory, DatasourceError, IsolationLevel, CRUDRepository, ReadonlyRepository, untyped, Native, ParameterDirection, columnType, RowTypeConverter, CreateTableStatement, ExecuteStoredProcedureStatement, ReturnStatement, SelectStatement, type IDriver } from "@datasources/core";
|
|
15
|
+
import { bindParameters, toPostgres } from "./bind.ts";
|
|
16
|
+
import { PostgresCommand } from "./command.ts";
|
|
17
|
+
import { PostgresConnection } from "./connection.ts";
|
|
18
|
+
import { PostgresRowTypeConverters } from "./converters/PostgresRowTypeConverters.ts";
|
|
19
|
+
import { pgClient, type IPgClient, type PgModule, type PgQueryResult } from "./i-postgres-driver.ts";
|
|
20
|
+
import { PostgresDriver } from "./PostgresDriver.ts";
|
|
21
|
+
import { PostgresTransaction } from "./transaction.ts";
|
|
22
|
+
import type { Scalar } from "./scalar.ts";
|
|
23
|
+
|
|
24
|
+
class FakeClient implements IPgClient {
|
|
25
|
+
static latest: FakeClient;
|
|
26
|
+
queries: Array<{ text: string; values?: Scalar[] }> = [];
|
|
27
|
+
connected = false;
|
|
28
|
+
ended = false;
|
|
29
|
+
result: PgQueryResult = { rowCount: 2, rows: [{ id: 5 }] };
|
|
30
|
+
|
|
31
|
+
constructor(_options: { connectionString: string }) {
|
|
32
|
+
FakeClient.latest = this;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async connect(): Promise<void> {
|
|
36
|
+
this.connected = true;
|
|
37
|
+
}
|
|
38
|
+
async query(text: string, values?: Scalar[]): Promise<PgQueryResult> {
|
|
39
|
+
this.queries.push({ text, values });
|
|
40
|
+
return this.result;
|
|
41
|
+
}
|
|
42
|
+
async *rows(text: string, values?: Scalar[]): AsyncGenerator<PgQueryResult["rows"][number]> {
|
|
43
|
+
this.queries.push({ text, values });
|
|
44
|
+
for (const row of this.result.rows) yield row;
|
|
45
|
+
}
|
|
46
|
+
async end(): Promise<void> {
|
|
47
|
+
this.ended = true;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
afterEach(() => {
|
|
52
|
+
DatasourceFactory.reset();
|
|
53
|
+
DatasourceFactory.register("postgres", new PostgresDriver());
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
describe("bind", () => {
|
|
57
|
+
it("rewrites named parameters and inserts", () => {
|
|
58
|
+
expect(bindParameters()).toEqual({});
|
|
59
|
+
expect(bindParameters([{ name: "id", value: 1 }])).toEqual({ id: 1 });
|
|
60
|
+
expect(toPostgres("SELECT * FROM t WHERE id = $id AND n = $id", { id: 1 }))
|
|
61
|
+
.toEqual({ text: "SELECT * FROM t WHERE id = $1 AND n = $1", values: [1] });
|
|
62
|
+
expect(toPostgres("SELECT 1")).toEqual({ text: "SELECT 1", values: [] });
|
|
63
|
+
expect(toPostgres("SELECT * FROM t WHERE id = $id")).toEqual({
|
|
64
|
+
text: "SELECT * FROM t WHERE id = $1",
|
|
65
|
+
values: [null],
|
|
66
|
+
});
|
|
67
|
+
expect(toPostgres(`INSERT INTO "contact_sources" ("name") VALUES ('Manual')`).text)
|
|
68
|
+
.toBe(`INSERT INTO "contact_sources" ("name") VALUES ('Manual')`);
|
|
69
|
+
expect(toPostgres("SELECT * FROM OldContactsTbl").text).toBe(`SELECT * FROM "OldContactsTbl"`);
|
|
70
|
+
expect(bindParameters([{ name: "at", value: new Date("2020-01-02T03:04:05.000Z") }]))
|
|
71
|
+
.toEqual({ at: "2020-01-02T03:04:05.000Z" });
|
|
72
|
+
expect(toPostgres("SELECT $at", { at: "2020-01-02T03:04:05.000Z" })).toEqual({
|
|
73
|
+
text: "SELECT $1",
|
|
74
|
+
values: [new Date("2020-01-02T03:04:05.000Z")],
|
|
75
|
+
});
|
|
76
|
+
expect(toPostgres("SELECT $v", { v: "AA==" })).toEqual({
|
|
77
|
+
text: "SELECT $1",
|
|
78
|
+
values: [Uint8Array.from([0])],
|
|
79
|
+
});
|
|
80
|
+
expect(toPostgres("SELECT $n", { n: "ada" }).values).toEqual(["ada"]);
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
describe("PostgresConnection", () => {
|
|
85
|
+
it("opens, runs SQL, transacts, and closes", async () => {
|
|
86
|
+
const connection = new PostgresConnection({
|
|
87
|
+
url: "postgres://db/app",
|
|
88
|
+
schema: "iso",
|
|
89
|
+
Client: FakeClient,
|
|
90
|
+
});
|
|
91
|
+
await connection.open();
|
|
92
|
+
await expect(connection.open()).rejects.toThrow("Connection is already open");
|
|
93
|
+
const client = FakeClient.latest;
|
|
94
|
+
expect(client.connected).toBe(true);
|
|
95
|
+
const command = new PostgresCommand(connection);
|
|
96
|
+
expect(await command.execute("INSERT INTO items (id) VALUES ($id)", [{ name: "id", value: 1 }]))
|
|
97
|
+
.toMatchObject({ rowsAffected: 2, result: null });
|
|
98
|
+
client.result = { rowCount: 0, rows: [] };
|
|
99
|
+
expect(await command.executeNative(new Native("INSERT INTO items (id) VALUES (1)"))).toEqual({
|
|
100
|
+
rowsAffected: 0,
|
|
101
|
+
result: null,
|
|
102
|
+
parameters: undefined,
|
|
103
|
+
});
|
|
104
|
+
client.result = { rowCount: null, rows: [{ id: 9 }] };
|
|
105
|
+
expect(await command.executeNative(new Native("UPDATE items SET n = 1"))).toMatchObject({
|
|
106
|
+
rowsAffected: 1,
|
|
107
|
+
result: null,
|
|
108
|
+
});
|
|
109
|
+
expect(await command.executeNative(new Native(" "))).toEqual({ rowsAffected: 0, parameters: undefined });
|
|
110
|
+
client.result = { rowCount: null, rows: [{ id: 1 }] };
|
|
111
|
+
expect(await command.executeReader("SELECT * FROM items")).toBeInstanceOf(Object);
|
|
112
|
+
const reader = await command.executeReader("SELECT * FROM items");
|
|
113
|
+
expect(await reader.next()).toBe(true);
|
|
114
|
+
expect(await command.executeStatement(new CreateTableStatement("users", [{ name: "id", type: "INTEGER" }])))
|
|
115
|
+
.toMatchObject({ rowsAffected: 1 });
|
|
116
|
+
expect(await (await command.executeReaderNative(new Native("SELECT 1"))).next()).toBe(true);
|
|
117
|
+
await connection.startTransaction();
|
|
118
|
+
await connection.startTransaction(IsolationLevel.ReadUncommitted);
|
|
119
|
+
await connection.startTransaction(IsolationLevel.ReadCommitted);
|
|
120
|
+
await connection.startTransaction(IsolationLevel.RepeatableRead);
|
|
121
|
+
await connection.startTransaction(IsolationLevel.Serializable);
|
|
122
|
+
await new PostgresTransaction(connection).commit();
|
|
123
|
+
await new PostgresTransaction(connection).abort();
|
|
124
|
+
expect(client.queries.map((q) => q.text)).toEqual([
|
|
125
|
+
"SET search_path TO iso",
|
|
126
|
+
"INSERT INTO items (id) VALUES ($1)",
|
|
127
|
+
"INSERT INTO items (id) VALUES (1)",
|
|
128
|
+
"UPDATE items SET n = 1",
|
|
129
|
+
"SELECT * FROM items",
|
|
130
|
+
"SELECT * FROM items",
|
|
131
|
+
"CREATE TABLE users (id INTEGER)",
|
|
132
|
+
"SELECT 1",
|
|
133
|
+
"BEGIN ISOLATION LEVEL READ COMMITTED",
|
|
134
|
+
"BEGIN ISOLATION LEVEL READ UNCOMMITTED",
|
|
135
|
+
"BEGIN ISOLATION LEVEL READ COMMITTED",
|
|
136
|
+
"BEGIN ISOLATION LEVEL REPEATABLE READ",
|
|
137
|
+
"BEGIN ISOLATION LEVEL SERIALIZABLE",
|
|
138
|
+
"COMMIT",
|
|
139
|
+
"ROLLBACK",
|
|
140
|
+
]);
|
|
141
|
+
await connection.close();
|
|
142
|
+
expect(client.ended).toBe(true);
|
|
143
|
+
await expect(connection.exec("SELECT 1")).rejects.toThrow("not open");
|
|
144
|
+
await expect(command.executeNative(new Native("x"))).rejects.toThrow("not open");
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it("loads the pg client from a module", async () => {
|
|
148
|
+
const Client = class implements IPgClient {
|
|
149
|
+
async connect(): Promise<void> {}
|
|
150
|
+
async query() {
|
|
151
|
+
return { rowCount: 0, rows: [] };
|
|
152
|
+
}
|
|
153
|
+
async *rows() {}
|
|
154
|
+
async end(): Promise<void> {}
|
|
155
|
+
};
|
|
156
|
+
const named = new PostgresConnection({
|
|
157
|
+
url: "postgres://db/app",
|
|
158
|
+
Client: pgClient({ Client } as PgModule),
|
|
159
|
+
});
|
|
160
|
+
await named.open();
|
|
161
|
+
await named.close();
|
|
162
|
+
const viaDefault = new PostgresConnection({
|
|
163
|
+
url: "postgres://db/app",
|
|
164
|
+
Client: pgClient({ Client, default: { Client } } as PgModule),
|
|
165
|
+
});
|
|
166
|
+
await viaDefault.open();
|
|
167
|
+
await viaDefault.close();
|
|
168
|
+
expect(PostgresConnection.from({ DATASOURCE_URL: "postgres://db/app" }).url).toBe("postgres://db/app");
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it("opens, reopens, transacts, and closes through the connection", async () => {
|
|
172
|
+
const connection = new PostgresConnection({
|
|
173
|
+
url: "postgres://db/app",
|
|
174
|
+
schema: "s",
|
|
175
|
+
Client: FakeClient,
|
|
176
|
+
});
|
|
177
|
+
await connection.open();
|
|
178
|
+
expect(connection.createCommand()).toBeInstanceOf(PostgresCommand);
|
|
179
|
+
expect(connection.isOpen).toBe(true);
|
|
180
|
+
expect(connection.schema).toBe("s");
|
|
181
|
+
const txn = await connection.startTransaction();
|
|
182
|
+
await txn.commit();
|
|
183
|
+
const txn2 = await connection.startTransaction(IsolationLevel.ReadUncommitted);
|
|
184
|
+
expect(txn2.isolation).toBe(IsolationLevel.ReadUncommitted);
|
|
185
|
+
await txn2.abort();
|
|
186
|
+
expect(txn.isolation).toBe("readCommitted");
|
|
187
|
+
await connection.close();
|
|
188
|
+
expect(FakeClient.latest.ended).toBe(true);
|
|
189
|
+
expect(() => connection.ensureOpen()).toThrow(DatasourceError);
|
|
190
|
+
await expect(connection.startTransaction()).rejects.toThrow(DatasourceError);
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it("defaults url and requires a client to open", async () => {
|
|
194
|
+
const connection = new PostgresConnection();
|
|
195
|
+
expect(connection.url).toBe("");
|
|
196
|
+
await expect(connection.open()).rejects.toThrow(DatasourceError);
|
|
197
|
+
});
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
describe("PostgresCommand", () => {
|
|
201
|
+
async function setup() {
|
|
202
|
+
const connection = new PostgresConnection({ Client: FakeClient });
|
|
203
|
+
await connection.open();
|
|
204
|
+
return { connection, command: new PostgresCommand(connection), client: FakeClient.latest };
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
it("executes SQL through the client", async () => {
|
|
208
|
+
const { command, client } = await setup();
|
|
209
|
+
expect(await command.execute("DELETE FROM items", [{ name: "id", value: 1 }]))
|
|
210
|
+
.toEqual({ result: null, rowsAffected: 2, parameters: [{ name: "id", value: 1 }] });
|
|
211
|
+
expect(await command.executeNative(new Native(""))).toEqual({ result: undefined, rowsAffected: 0, parameters: undefined });
|
|
212
|
+
const reader = await command.executeReader("SELECT * FROM items");
|
|
213
|
+
expect(await reader.next()).toBe(true);
|
|
214
|
+
expect(reader.current).toEqual({ id: 5 });
|
|
215
|
+
const empty = await command.executeReaderNative(new Native(""));
|
|
216
|
+
expect(await empty.next()).toBe(false);
|
|
217
|
+
expect(await (await command.executeReaderNative(new Native(";"))).next()).toBe(false);
|
|
218
|
+
client.result = { rowCount: 1, rows: [{ add_one: 5 }] };
|
|
219
|
+
const n = { name: "n", value: 4 };
|
|
220
|
+
expect(await command.executeStatement(new ReturnStatement("add_one", [n]), [n]))
|
|
221
|
+
.toEqual({ result: 5, rowsAffected: 1, parameters: [n] });
|
|
222
|
+
client.result = { rowCount: 0, rows: [] };
|
|
223
|
+
expect(await command.executeStatement(new SelectStatement({ table: "dual", fields: ["1"] }))).toMatchObject({
|
|
224
|
+
result: null,
|
|
225
|
+
rowsAffected: 0,
|
|
226
|
+
});
|
|
227
|
+
client.result = { rowCount: 1, rows: [{}] };
|
|
228
|
+
expect(await command.executeStatement(new SelectStatement({ table: "dual", fields: ["1"] }))).toMatchObject({ result: null });
|
|
229
|
+
client.result = { rowCount: 0, rows: [] };
|
|
230
|
+
expect(await command.executeNative(new Native("SELECT 1"))).toEqual({
|
|
231
|
+
result: null,
|
|
232
|
+
rowsAffected: 0,
|
|
233
|
+
parameters: undefined,
|
|
234
|
+
});
|
|
235
|
+
client.result = { rowCount: 1, rows: [{ id: null }] };
|
|
236
|
+
expect(await command.executeNative(new Native("SELECT flag($b)"), [{ name: "b", value: true }])).toMatchObject({ result: null });
|
|
237
|
+
expect(await command.executeNative(new Native("SELECT when($d)"), [{ name: "d", value: new Date("2026-09-22T00:00:00.000Z") }])).toMatchObject({ result: null });
|
|
238
|
+
expect(await command.executeNative(new Native("SELECT blob($b)"), [{ name: "b", value: new Uint8Array([1, 2, 3]) }])).toMatchObject({ result: null });
|
|
239
|
+
const inout = { name: "result", value: 4, direction: ParameterDirection.InputOutput };
|
|
240
|
+
client.result = { rowCount: 1, rows: [{ result: { n: 1 } }] };
|
|
241
|
+
expect(await command.executeNative(new Native("CALL bump($result)"), [inout])).toMatchObject({
|
|
242
|
+
parameters: [{ name: "result", value: "{\"n\":1}", direction: ParameterDirection.InputOutput }],
|
|
243
|
+
});
|
|
244
|
+
client.result = { rowCount: 1, rows: [{ result: 5 }] };
|
|
245
|
+
expect(await command.executeStatement(new ExecuteStoredProcedureStatement("bump", [inout]), [inout])).toMatchObject({
|
|
246
|
+
result: null,
|
|
247
|
+
parameters: [{ name: "result", value: 5, direction: ParameterDirection.InputOutput }],
|
|
248
|
+
});
|
|
249
|
+
client.result = { rowCount: 1, rows: [{ id: 1 }] };
|
|
250
|
+
expect(
|
|
251
|
+
await command.executeNative(new Native("CALL noop($n)"), [
|
|
252
|
+
{ name: "n", value: 1, direction: ParameterDirection.Input },
|
|
253
|
+
]),
|
|
254
|
+
).toMatchObject({ rowsAffected: 1 });
|
|
255
|
+
expect(await command.executeNative(new Native("CALL noop()"))).toMatchObject({ rowsAffected: 1 });
|
|
256
|
+
client.result = { rowCount: 1, rows: [{ other: 9 }] };
|
|
257
|
+
expect(
|
|
258
|
+
await command.executeNative(new Native("CALL bump($missing)"), [
|
|
259
|
+
{ name: "missing", value: 1, direction: ParameterDirection.Output },
|
|
260
|
+
]),
|
|
261
|
+
).toMatchObject({
|
|
262
|
+
parameters: [{ name: "missing", value: 1, direction: ParameterDirection.Output }],
|
|
263
|
+
});
|
|
264
|
+
});
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
describe("Postgres datasource", () => {
|
|
268
|
+
it("registers and constructs children", async () => {
|
|
269
|
+
expect(
|
|
270
|
+
await new Datasource("postgres", new PostgresDriver()).createConnection(),
|
|
271
|
+
).toBeInstanceOf(PostgresConnection);
|
|
272
|
+
const of = (): IDriver => ({
|
|
273
|
+
connection: () => new PostgresConnection({ Client: FakeClient }),
|
|
274
|
+
command: (c) => c.createCommand(),
|
|
275
|
+
transaction: (c, isolation = "readCommitted") =>
|
|
276
|
+
new PostgresTransaction(c as PostgresConnection, isolation),
|
|
277
|
+
rowTypeConverters: PostgresRowTypeConverters,
|
|
278
|
+
columnType,
|
|
279
|
+
});
|
|
280
|
+
const ds = new Datasource("postgres", of());
|
|
281
|
+
expect(ds.provider).toBe("postgres");
|
|
282
|
+
const connection = await ds.createConnection();
|
|
283
|
+
await (connection as PostgresConnection).open();
|
|
284
|
+
const command = await ds.createCommand(connection);
|
|
285
|
+
expect(command).toBeInstanceOf(PostgresCommand);
|
|
286
|
+
expect(command.connection).toBe(connection);
|
|
287
|
+
await command.execute("DELETE FROM items");
|
|
288
|
+
await command.executeReaderNative(new Native("SELECT 1"));
|
|
289
|
+
await expect(ds.createCommand()).rejects.toThrow("Connection is already open");
|
|
290
|
+
const repo = ds.createRepository(untyped(), connection);
|
|
291
|
+
expect(ds.createReadonlyRepository(untyped(), connection).constructor).toBe(ReadonlyRepository);
|
|
292
|
+
expect(repo).toBeInstanceOf(CRUDRepository);
|
|
293
|
+
expect(new Datasource("postgres", of()).createRepository(untyped())).toBeInstanceOf(CRUDRepository);
|
|
294
|
+
expect(Datasource.create({ PROVIDER: "postgres" }).provider).toBe("postgres");
|
|
295
|
+
const provided = PostgresConnection.from({
|
|
296
|
+
DATASOURCE_URL: "postgres://db/app?schema=s",
|
|
297
|
+
});
|
|
298
|
+
expect(provided.url).toBe("postgres://db/app?schema=s");
|
|
299
|
+
expect(provided.schema).toBe("s");
|
|
300
|
+
expect(PostgresConnection.from({ DATASOURCE_URL: "postgres://db/app?schema=app" }).schema).toBe("app");
|
|
301
|
+
expect(PostgresConnection.from().url).toBe("");
|
|
302
|
+
expect(PostgresConnection.from().schema).toBeUndefined();
|
|
303
|
+
const provider = new PostgresDriver();
|
|
304
|
+
expect(provider.columnType("int")).toBe("INTEGER");
|
|
305
|
+
expect(provider.columnType("uuid")).toBe("UUID");
|
|
306
|
+
expect(provider.columnType("string")).toBe("TEXT");
|
|
307
|
+
expect(provider.columnType("bool")).toBe("BOOLEAN");
|
|
308
|
+
expect(provider.columnType("decimal")).toBe("NUMERIC");
|
|
309
|
+
expect(provider.columnType("float")).toBe("DOUBLE PRECISION");
|
|
310
|
+
expect(provider.columnType("datetime")).toBe("TIMESTAMPTZ");
|
|
311
|
+
expect(provider.columnType("binary")).toBe("BYTEA");
|
|
312
|
+
expect(() => provider.columnType("xml")).toThrow('Unknown postgres type "xml"');
|
|
313
|
+
expect(provider.command(connection)).toBeInstanceOf(PostgresCommand);
|
|
314
|
+
expect(provider.transaction(connection as PostgresConnection).isolation).toBe("readCommitted");
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
it("maps property targets", () => {
|
|
318
|
+
const provider = new PostgresDriver();
|
|
319
|
+
const created = "2020-01-02T03:04:05.000Z";
|
|
320
|
+
const converter = RowTypeConverter.from(provider.rowTypeConverters, {
|
|
321
|
+
Key: "uuid",
|
|
322
|
+
Active: "bool",
|
|
323
|
+
Login: "string",
|
|
324
|
+
Created: "datetime",
|
|
325
|
+
});
|
|
326
|
+
const row = converter.convert({
|
|
327
|
+
Key: "11111111-1111-1111-1111-111111111111",
|
|
328
|
+
Active: 1,
|
|
329
|
+
Login: "ada",
|
|
330
|
+
Created: created,
|
|
331
|
+
});
|
|
332
|
+
expect(row).toEqual({
|
|
333
|
+
Key: "11111111-1111-1111-1111-111111111111",
|
|
334
|
+
Active: true,
|
|
335
|
+
Login: "ada",
|
|
336
|
+
Created: new Date(created),
|
|
337
|
+
});
|
|
338
|
+
expect(converter.reverseConvert(row)).toEqual({
|
|
339
|
+
Key: "11111111-1111-1111-1111-111111111111",
|
|
340
|
+
Active: true,
|
|
341
|
+
Login: "ada",
|
|
342
|
+
Created: created,
|
|
343
|
+
});
|
|
344
|
+
});
|
|
345
|
+
});
|
package/src/transaction.ts
CHANGED
|
@@ -2,7 +2,7 @@ import {
|
|
|
2
2
|
IsolationLevel,
|
|
3
3
|
type ITransaction,
|
|
4
4
|
type IsolationLevelName,
|
|
5
|
-
} from "@
|
|
5
|
+
} from "@datasources/core";
|
|
6
6
|
import type { PostgresConnection } from "./connection.ts";
|
|
7
7
|
|
|
8
8
|
export class PostgresTransaction implements ITransaction {
|