@deterministic-code/datasources-postgres 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.
Files changed (48) hide show
  1. package/dist/PostgresBulkUpdateStatementTextRenderer.d.ts +4 -0
  2. package/dist/PostgresCreateTableStatementRenderer.d.ts +6 -0
  3. package/dist/PostgresDriver.d.ts +12 -0
  4. package/dist/PostgresStatementRenderer.d.ts +7 -0
  5. package/dist/bind.d.ts +7 -0
  6. package/dist/columnTypes.d.ts +1 -0
  7. package/dist/command.d.ts +14 -0
  8. package/dist/connection-options.d.ts +6 -0
  9. package/dist/connection.d.ts +21 -0
  10. package/dist/converters/BinaryConverter.d.ts +6 -0
  11. package/dist/converters/BoolConverter.d.ts +5 -0
  12. package/dist/converters/DateTimeConverter.d.ts +6 -0
  13. package/dist/converters/DecimalConverter.d.ts +5 -0
  14. package/dist/converters/PostgresRowTypeConverters.d.ts +3 -0
  15. package/dist/converters/UuidConverter.d.ts +5 -0
  16. package/dist/i-postgres-driver.d.ts +38 -0
  17. package/dist/index.d.ts +1 -0
  18. package/dist/scalar.d.ts +3 -0
  19. package/dist/transaction.d.ts +9 -0
  20. package/package.json +25 -4
  21. package/src/PostgresBulkUpdateStatementTextRenderer.ts +27 -0
  22. package/src/PostgresCreateTableStatementRenderer.ts +33 -0
  23. package/src/PostgresDriver.ts +55 -0
  24. package/src/PostgresStatementRenderer.test.ts +105 -0
  25. package/src/PostgresStatementRenderer.ts +34 -0
  26. package/src/bind.ts +50 -0
  27. package/src/columnTypes.ts +10 -0
  28. package/src/command.ts +129 -0
  29. package/src/connection-options.ts +7 -0
  30. package/src/connection.ts +95 -0
  31. package/src/converters/BinaryConverter.test.ts +14 -0
  32. package/src/converters/BinaryConverter.ts +20 -0
  33. package/src/converters/BoolConverter.test.ts +14 -0
  34. package/src/converters/BoolConverter.ts +6 -0
  35. package/src/converters/DateTimeConverter.test.ts +13 -0
  36. package/src/converters/DateTimeConverter.ts +11 -0
  37. package/src/converters/DecimalConverter.test.ts +11 -0
  38. package/src/converters/DecimalConverter.ts +7 -0
  39. package/src/converters/PostgresRowTypeConverters.test.ts +18 -0
  40. package/src/converters/PostgresRowTypeConverters.ts +24 -0
  41. package/src/converters/UuidConverter.test.ts +11 -0
  42. package/src/converters/UuidConverter.ts +6 -0
  43. package/src/i-postgres-driver.ts +61 -0
  44. package/src/index.ts +1 -0
  45. package/src/postgres.test.ts +345 -0
  46. package/src/scalar.ts +3 -0
  47. package/src/transaction.ts +21 -0
  48. package/README.md +0 -3
@@ -0,0 +1,4 @@
1
+ import { BulkUpdateStatementTextRenderer, type Cell } from "@datasources/core";
2
+ export declare class PostgresBulkUpdateStatementTextRenderer extends BulkUpdateStatementTextRenderer {
3
+ protected cell: (column: string, index: number, value: Cell) => string;
4
+ }
@@ -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,6 @@
1
+ import type { PgClientCtor } from "./i-postgres-driver.js";
2
+ export interface PostgresConnectionOptions {
3
+ url?: string;
4
+ schema?: string;
5
+ Client?: PgClientCtor;
6
+ }
@@ -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,5 @@
1
+ import { TypeConverter, type Cell } from "@datasources/core";
2
+ export declare class BoolConverter extends TypeConverter<Cell, Cell> {
3
+ convert: (from: Cell) => Cell;
4
+ reverseConvert: (to: Cell) => Cell;
5
+ }
@@ -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,5 @@
1
+ import { TypeConverter, type Cell } from "@datasources/core";
2
+ export declare class DecimalConverter extends TypeConverter<Cell, Cell> {
3
+ convert: (from: Cell) => Cell;
4
+ reverseConvert: (to: Cell) => Cell;
5
+ }
@@ -0,0 +1,3 @@
1
+ import { type Cell, type TypeConverter } from "@datasources/core";
2
+ export declare const PostgresRowTypeConverters: Record<string, TypeConverter<Cell, Cell>>;
3
+ export declare const reverse: (value: Cell) => Cell;
@@ -0,0 +1,5 @@
1
+ import { TypeConverter, type Cell } from "@datasources/core";
2
+ export declare class UuidConverter extends TypeConverter<Cell, Cell> {
3
+ convert: (from: Cell) => Cell;
4
+ reverseConvert: (to: Cell) => Cell;
5
+ }
@@ -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 {};
@@ -0,0 +1 @@
1
+ export { PostgresDriver } from "./PostgresDriver.js";
@@ -0,0 +1,3 @@
1
+ export type Scalar = string | number | boolean | bigint | null;
2
+ export type Wire = Scalar | Date | Uint8Array;
3
+ export type Cells = Record<string, Scalar>;
@@ -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,6 +1,27 @@
1
1
  {
2
2
  "name": "@deterministic-code/datasources-postgres",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
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/pg": "^8.15.5",
23
+ "pg": "^8.16.0",
24
+ "pg-query-stream": "^4.17.0",
25
+ "@deterministic-code/datasources": "^0.1.1"
26
+ }
27
+ }
@@ -0,0 +1,27 @@
1
+ import { BulkUpdateStatementTextRenderer, type Cell } from "@datasources/core";
2
+ import { columnTypes } from "./columnTypes.ts";
3
+ import { isBase64 } from "./converters/BinaryConverter.ts";
4
+ import { isIsoDate } from "./converters/DateTimeConverter.ts";
5
+
6
+ const logical = (value: Cell): string | undefined =>
7
+ value === null
8
+ ? undefined
9
+ : typeof value === "boolean"
10
+ ? "bool"
11
+ : typeof value === "bigint" || (typeof value === "number" && Number.isInteger(value))
12
+ ? "int"
13
+ : typeof value === "number"
14
+ ? "float"
15
+ : value instanceof Date || (typeof value === "string" && isIsoDate(value))
16
+ ? "datetime"
17
+ : value instanceof Uint8Array || (typeof value === "string" && isBase64(value))
18
+ ? "binary"
19
+ : "string";
20
+
21
+ export class PostgresBulkUpdateStatementTextRenderer extends BulkUpdateStatementTextRenderer {
22
+ protected override cell = (column: string, index: number, value: Cell): string => {
23
+ const type = logical(value);
24
+ const sql = type ? columnTypes[type] : undefined;
25
+ return sql ? `$${column}__${index}::${sql} AS ${column}` : `$${column}__${index} AS ${column}`;
26
+ };
27
+ }
@@ -0,0 +1,33 @@
1
+ import {
2
+ ColumnDefault,
3
+ CreateTableStatementTextRenderer,
4
+ type Column,
5
+ } from "@datasources/core";
6
+ import { PostgresRowTypeConverters } from "./converters/PostgresRowTypeConverters.ts";
7
+ import { columnTypes } from "./columnTypes.ts";
8
+
9
+ export class PostgresCreateTableStatementRenderer extends CreateTableStatementTextRenderer {
10
+ protected typeName(column: Column): string {
11
+ return columnTypes[column.type] ?? column.type;
12
+ }
13
+
14
+ protected columnDefault(column: Column): string {
15
+ const literal = sqlLiteral(column);
16
+ const value = column.default;
17
+ return value == null || literal == null
18
+ ? super.columnDefault(column)
19
+ : super.columnDefault({ ...column, default: new ColumnDefault(literal, value.name) });
20
+ }
21
+
22
+ protected identity(column: Column): string {
23
+ return column.identity ? " GENERATED BY DEFAULT AS IDENTITY" : "";
24
+ }
25
+ }
26
+
27
+ function sqlLiteral(column: Column): string | undefined {
28
+ const raw = column.default?.value;
29
+ const converter = PostgresRowTypeConverters[column.type];
30
+ if (raw == null || !converter || !/^-?\d+$/.test(raw)) return undefined;
31
+ const converted = converter.convert(Number(raw));
32
+ return converted === true ? "TRUE" : converted === false ? "FALSE" : undefined;
33
+ }
@@ -0,0 +1,55 @@
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 { PostgresConnection } from "./connection.ts";
15
+ import { PostgresCommand } from "./command.ts";
16
+ import { PostgresTransaction } from "./transaction.ts";
17
+ import { PostgresRowTypeConverters } from "./converters/PostgresRowTypeConverters.ts";
18
+ import { PostgresStatementRenderer } from "./PostgresStatementRenderer.ts";
19
+ import { columnTypes } from "./columnTypes.ts";
20
+
21
+ export { columnTypes };
22
+
23
+ const statements = new StatementTextRenderer(t => new PostgresStatementRenderer(t));
24
+
25
+ export class PostgresDriver implements IDriver {
26
+ readonly statementRenderer = statements;
27
+ readonly information: Information = {
28
+ descriptions: [
29
+ "PostgreSQL server driver.",
30
+ "Full connection, command, transaction, and repository support.",
31
+ ],
32
+ overrides: [
33
+ "Overrides SelectInsertedIdStatement to SELECT lastval() AS {column}",
34
+ "Overrides CreateTableStatement identity to GENERATED BY DEFAULT AS IDENTITY",
35
+ "Overrides CreateTableStatement boolean defaults (0/1 → TRUE/FALSE)",
36
+ "Overrides CreateTableStatement type mapping (int→INTEGER, uuid→UUID, string→TEXT, bool→BOOLEAN, decimal→NUMERIC, float→DOUBLE PRECISION, datetime→TIMESTAMPTZ, binary→BYTEA)",
37
+ ],
38
+ changeLogs: [],
39
+ };
40
+ rowTypeConverters: Record<string, TypeConverter<Cell, Cell>> = PostgresRowTypeConverters;
41
+ columnType = (name: string): string =>
42
+ columnTypes[name] ??
43
+ (() => {
44
+ throw new DatasourceError(`Unknown postgres type "${name}"`);
45
+ })();
46
+ connection = (options?: DatasourceOptions): IConnection =>
47
+ PostgresConnection.from(options);
48
+ command = (connection: IConnection): ICommand =>
49
+ new PostgresCommand(connection as PostgresConnection, statements);
50
+ transaction = (
51
+ connection: IConnection,
52
+ isolation: IsolationLevel = "readCommitted",
53
+ ): ITransaction =>
54
+ new PostgresTransaction(connection as PostgresConnection, isolation);
55
+ }
@@ -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
+ });
@@ -0,0 +1,34 @@
1
+ import {
2
+ BulkUpdateStatement,
3
+ CreateTableStatement,
4
+ SelectInsertedIdStatement,
5
+ SelectInsertedIdStatementTextRenderer,
6
+ StatementRenderer,
7
+ type StatementTextRenderer,
8
+ } from "@datasources/core";
9
+ import { PostgresBulkUpdateStatementTextRenderer } from "./PostgresBulkUpdateStatementTextRenderer.ts";
10
+ import { PostgresCreateTableStatementRenderer } from "./PostgresCreateTableStatementRenderer.ts";
11
+
12
+ class PostgresSelectInsertedIdStatementTextRenderer extends SelectInsertedIdStatementTextRenderer {
13
+ override render(statement: SelectInsertedIdStatement): string {
14
+ return `SELECT lastval() AS ${statement.column}`;
15
+ }
16
+ }
17
+
18
+ export class PostgresStatementRenderer extends StatementRenderer {
19
+ constructor(text: StatementTextRenderer) {
20
+ super(text);
21
+ }
22
+
23
+ protected override visitSelectInsertedId(inserted: SelectInsertedIdStatement): string {
24
+ return new PostgresSelectInsertedIdStatementTextRenderer().render(inserted);
25
+ }
26
+
27
+ protected override visitCreateTable(create: CreateTableStatement): string {
28
+ return new PostgresCreateTableStatementRenderer().render(create);
29
+ }
30
+
31
+ protected override visitBulkUpdate(update: BulkUpdateStatement): string {
32
+ return new PostgresBulkUpdateStatementTextRenderer().render(update);
33
+ }
34
+ }
package/src/bind.ts ADDED
@@ -0,0 +1,50 @@
1
+ import { parametersToRecord, type Cell, type Parameter } from "@datasources/core";
2
+ import { isBase64 } from "./converters/BinaryConverter.ts";
3
+ import { isIsoDate } from "./converters/DateTimeConverter.ts";
4
+ import { reverse } from "./converters/PostgresRowTypeConverters.ts";
5
+ import type { Scalar, Wire } from "./scalar.ts";
6
+
7
+ const NAME = /\$([A-Za-z_][A-Za-z0-9_]*)/g;
8
+
9
+ const quoteMixedCase = (sql: string): string =>
10
+ sql.replace(/\$[A-Za-z_][A-Za-z0-9_]*|'[^']*'|"[^"]*"|`[^`]*`|\b[A-Za-z_][A-Za-z0-9_]*\b/g, (token) =>
11
+ token.startsWith("$") || token.startsWith("'") || token.startsWith("\"") || token.startsWith("`")
12
+ || !/[A-Z]/.test(token) || !/[a-z]/.test(token)
13
+ ? token
14
+ : `"${token}"`,
15
+ );
16
+
17
+ const wire = (value: Scalar): Wire =>
18
+ typeof value !== "string"
19
+ ? value
20
+ : isIsoDate(value)
21
+ ? new Date(value)
22
+ : isBase64(value)
23
+ ? Uint8Array.from(Buffer.from(value, "base64"))
24
+ : value;
25
+
26
+ export const toPostgres = (
27
+ sql: string,
28
+ params?: Record<string, Scalar>,
29
+ ): { text: string; values: Wire[] } => {
30
+ const names: string[] = [];
31
+ const text = quoteMixedCase(sql).replace(NAME, (_match, name: string) => {
32
+ let index = names.indexOf(name);
33
+ if (index < 0) {
34
+ names.push(name);
35
+ index = names.length - 1;
36
+ }
37
+ return `$${index + 1}`;
38
+ });
39
+ return { text, values: names.map((name) => wire(params?.[name] ?? null)) };
40
+ };
41
+
42
+ export const bindParameters = (
43
+ parameters?: Parameter[] | null,
44
+ ): Record<string, Scalar> => {
45
+ const record = parametersToRecord(parameters);
46
+ const bound: Record<string, Scalar> = {};
47
+ for (const [name, value] of Object.entries(record))
48
+ bound[name] = reverse(value as Cell) as Scalar;
49
+ return bound;
50
+ };
@@ -0,0 +1,10 @@
1
+ export const columnTypes: Record<string, string> = {
2
+ int: "INTEGER",
3
+ uuid: "UUID",
4
+ string: "TEXT",
5
+ bool: "BOOLEAN",
6
+ decimal: "NUMERIC",
7
+ float: "DOUBLE PRECISION",
8
+ datetime: "TIMESTAMPTZ",
9
+ binary: "BYTEA",
10
+ };