@deterministic-code/datasources-postgres 0.1.0 → 0.1.2

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 (70) hide show
  1. package/dist/PostgresAlterTableStatementRenderer.d.ts +6 -0
  2. package/dist/PostgresAlterTableStatementRenderer.js +20 -0
  3. package/dist/PostgresBulkUpdateStatementTextRenderer.d.ts +4 -0
  4. package/dist/PostgresBulkUpdateStatementTextRenderer.js +24 -0
  5. package/dist/PostgresCreateRoutineStatementRenderer.d.ts +4 -0
  6. package/dist/PostgresCreateTableStatementRenderer.d.ts +6 -0
  7. package/dist/PostgresCreateTableStatementRenderer.js +26 -0
  8. package/dist/PostgresCreateTriggerStatementRenderer.d.ts +4 -0
  9. package/dist/PostgresCreateTriggerStatementRenderer.js +13 -0
  10. package/dist/PostgresDriver.d.ts +16 -0
  11. package/dist/PostgresDriver.js +75 -0
  12. package/dist/PostgresDropRoutineStatementRenderer.d.ts +4 -0
  13. package/dist/PostgresDropRoutineStatementRenderer.js +6 -0
  14. package/dist/PostgresDropTriggerStatementRenderer.d.ts +4 -0
  15. package/dist/PostgresDropTriggerStatementRenderer.js +6 -0
  16. package/dist/PostgresIdentityInsertStatementRenderer.d.ts +4 -0
  17. package/dist/PostgresIdentityInsertStatementRenderer.js +11 -0
  18. package/dist/PostgresStatementRenderer.d.ts +19 -0
  19. package/dist/PostgresStatementRenderer.js +64 -0
  20. package/dist/PostgresStoredProcedureStatementRenderer.d.ts +10 -0
  21. package/dist/PostgresStoredProcedureStatementRenderer.js +111 -0
  22. package/dist/bind.d.ts +7 -0
  23. package/dist/bind.js +35 -0
  24. package/dist/columnTypes.d.ts +1 -0
  25. package/dist/columnTypes.js +10 -0
  26. package/dist/command.d.ts +14 -0
  27. package/dist/command.js +92 -0
  28. package/dist/connection-options.d.ts +6 -0
  29. package/dist/connection-options.js +1 -0
  30. package/dist/connection.d.ts +21 -0
  31. package/dist/connection.js +72 -0
  32. package/dist/converters/BinaryConverter.d.ts +6 -0
  33. package/dist/converters/BinaryConverter.js +14 -0
  34. package/dist/converters/BoolConverter.d.ts +5 -0
  35. package/dist/converters/BoolConverter.js +5 -0
  36. package/dist/converters/DateTimeConverter.d.ts +6 -0
  37. package/dist/converters/DateTimeConverter.js +6 -0
  38. package/dist/converters/DecimalConverter.d.ts +5 -0
  39. package/dist/converters/DecimalConverter.js +5 -0
  40. package/dist/converters/PostgresRowTypeConverters.d.ts +3 -0
  41. package/dist/converters/PostgresRowTypeConverters.js +19 -0
  42. package/dist/converters/UuidConverter.d.ts +5 -0
  43. package/dist/converters/UuidConverter.js +5 -0
  44. package/dist/i-postgres-driver.d.ts +38 -0
  45. package/dist/i-postgres-driver.js +22 -0
  46. package/dist/index.d.ts +1 -0
  47. package/dist/index.js +1 -0
  48. package/dist/scalar.js +1 -0
  49. package/dist/transaction.d.ts +9 -0
  50. package/dist/transaction.js +15 -0
  51. package/package.json +10 -7
  52. package/src/PostgresBulkUpdateStatementTextRenderer.ts +0 -27
  53. package/src/PostgresCreateTableStatementRenderer.ts +0 -33
  54. package/src/PostgresDriver.ts +0 -55
  55. package/src/PostgresStatementRenderer.ts +0 -34
  56. package/src/bind.ts +0 -50
  57. package/src/columnTypes.ts +0 -10
  58. package/src/command.ts +0 -129
  59. package/src/connection-options.ts +0 -7
  60. package/src/connection.ts +0 -95
  61. package/src/converters/BinaryConverter.ts +0 -20
  62. package/src/converters/BoolConverter.ts +0 -6
  63. package/src/converters/DateTimeConverter.ts +0 -11
  64. package/src/converters/DecimalConverter.ts +0 -7
  65. package/src/converters/PostgresRowTypeConverters.ts +0 -24
  66. package/src/converters/UuidConverter.ts +0 -6
  67. package/src/i-postgres-driver.ts +0 -61
  68. package/src/index.ts +0 -1
  69. package/src/transaction.ts +0 -21
  70. /package/{src/scalar.ts → dist/scalar.d.ts} +0 -0
@@ -0,0 +1,6 @@
1
+ import { AlterTableStatementTextRenderer, type AlterAction, type Column } from "@deterministic-code/datasources";
2
+ export declare class PostgresAlterTableStatementRenderer extends AlterTableStatementTextRenderer {
3
+ protected typeName(column: Column): string;
4
+ protected actionSql(action: AlterAction): string;
5
+ private postgresAlter;
6
+ }
@@ -0,0 +1,20 @@
1
+ import { AlterTableStatementTextRenderer, } from "@deterministic-code/datasources";
2
+ import { columnTypes } from "./columnTypes.js";
3
+ export class PostgresAlterTableStatementRenderer extends AlterTableStatementTextRenderer {
4
+ typeName(column) {
5
+ return columnTypes[column.type] ?? column.type;
6
+ }
7
+ actionSql(action) {
8
+ return action.kind === "alterColumn" ? this.postgresAlter(action.column) : super.actionSql(action);
9
+ }
10
+ postgresAlter(column) {
11
+ const name = this.quote(column.name);
12
+ const defaultSql = column.default == null
13
+ ? `ALTER COLUMN ${name} DROP DEFAULT`
14
+ : `ALTER COLUMN ${name} SET DEFAULT ${column.default.value}`;
15
+ const nullSql = column.notNull == null
16
+ ? `ALTER COLUMN ${name} DROP NOT NULL`
17
+ : `ALTER COLUMN ${name} SET NOT NULL`;
18
+ return `ALTER COLUMN ${name} TYPE ${this.typeName(column)}, ${defaultSql}, ${nullSql}`;
19
+ }
20
+ }
@@ -0,0 +1,4 @@
1
+ import { BulkUpdateStatementTextRenderer, type Cell } from "@deterministic-code/datasources";
2
+ export declare class PostgresBulkUpdateStatementTextRenderer extends BulkUpdateStatementTextRenderer {
3
+ protected cell: (column: string, index: number, value: Cell) => string;
4
+ }
@@ -0,0 +1,24 @@
1
+ import { BulkUpdateStatementTextRenderer } from "@deterministic-code/datasources";
2
+ import { columnTypes } from "./columnTypes.js";
3
+ import { isBase64 } from "./converters/BinaryConverter.js";
4
+ import { isIsoDate } from "./converters/DateTimeConverter.js";
5
+ const logical = (value) => value === null
6
+ ? undefined
7
+ : typeof value === "boolean"
8
+ ? "bool"
9
+ : typeof value === "bigint" || (typeof value === "number" && Number.isInteger(value))
10
+ ? "int"
11
+ : typeof value === "number"
12
+ ? "float"
13
+ : value instanceof Date || (typeof value === "string" && isIsoDate(value))
14
+ ? "datetime"
15
+ : value instanceof Uint8Array || (typeof value === "string" && isBase64(value))
16
+ ? "binary"
17
+ : "string";
18
+ export class PostgresBulkUpdateStatementTextRenderer extends BulkUpdateStatementTextRenderer {
19
+ cell = (column, index, value) => {
20
+ const type = logical(value);
21
+ const sql = type ? columnTypes[type] : undefined;
22
+ return sql ? `$${column}__${index}::${sql} AS ${column}` : `$${column}__${index} AS ${column}`;
23
+ };
24
+ }
@@ -0,0 +1,4 @@
1
+ import { CreateRoutineStatementTextRenderer, type CreateRoutineStatement } from "@datasources/core";
2
+ export declare class PostgresCreateRoutineStatementRenderer extends CreateRoutineStatementTextRenderer {
3
+ render(statement: CreateRoutineStatement): string;
4
+ }
@@ -0,0 +1,6 @@
1
+ import { CreateTableStatementTextRenderer, type Column } from "@deterministic-code/datasources";
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,26 @@
1
+ import { ColumnDefault, CreateTableStatementTextRenderer, } from "@deterministic-code/datasources";
2
+ import { PostgresRowTypeConverters } from "./converters/PostgresRowTypeConverters.js";
3
+ import { columnTypes } from "./columnTypes.js";
4
+ export class PostgresCreateTableStatementRenderer extends CreateTableStatementTextRenderer {
5
+ typeName(column) {
6
+ return columnTypes[column.type] ?? column.type;
7
+ }
8
+ columnDefault(column) {
9
+ const literal = sqlLiteral(column);
10
+ const value = column.default;
11
+ return value == null || literal == null
12
+ ? super.columnDefault(column)
13
+ : super.columnDefault({ ...column, default: new ColumnDefault(literal, value.name) });
14
+ }
15
+ identity(column) {
16
+ return column.identity ? " GENERATED BY DEFAULT AS IDENTITY" : "";
17
+ }
18
+ }
19
+ function sqlLiteral(column) {
20
+ const raw = column.default?.value;
21
+ const converter = PostgresRowTypeConverters[column.type];
22
+ if (raw == null || !converter || !/^-?\d+$/.test(raw))
23
+ return undefined;
24
+ const converted = converter.convert(Number(raw));
25
+ return converted === true ? "TRUE" : converted === false ? "FALSE" : undefined;
26
+ }
@@ -0,0 +1,4 @@
1
+ import { CreateTriggerStatementTextRenderer, type CreateTriggerStatement } from "@deterministic-code/datasources";
2
+ export declare class PostgresCreateTriggerStatementRenderer extends CreateTriggerStatementTextRenderer {
3
+ render(statement: CreateTriggerStatement): string;
4
+ }
@@ -0,0 +1,13 @@
1
+ import { CreateTriggerStatementTextRenderer } from "@deterministic-code/datasources";
2
+ export class PostgresCreateTriggerStatementRenderer extends CreateTriggerStatementTextRenderer {
3
+ render(statement) {
4
+ return `-- Shared updated_at function
5
+ CREATE OR REPLACE FUNCTION set_updated_at() RETURNS TRIGGER AS $$
6
+ BEGIN
7
+ NEW.${statement.updatedColumn} = NOW() AT TIME ZONE 'UTC';
8
+ RETURN NEW;
9
+ END;
10
+ $$ LANGUAGE plpgsql;
11
+ CREATE TRIGGER ${statement.name} BEFORE UPDATE ON ${statement.table} FOR EACH ROW EXECUTE FUNCTION set_updated_at()`;
12
+ }
13
+ }
@@ -0,0 +1,16 @@
1
+ import { DefaultValueConverter, DeterministicTypeConverter, StatementTextRenderer, type Cell, type DatasourceOptions, type ICommand, type IConnection, type IDriver, type Information, type ITransaction, type IsolationLevel, type TypeConverter } from "@deterministic-code/datasources";
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
+ readonly quoteLeft = "\"";
9
+ readonly quoteRight = "\"";
10
+ readonly typeConverter: DeterministicTypeConverter;
11
+ readonly defaultValueConverter: DefaultValueConverter;
12
+ columnType: (name: string) => string;
13
+ connection: (options?: DatasourceOptions) => IConnection;
14
+ command: (connection: IConnection) => ICommand;
15
+ transaction: (connection: IConnection, isolation?: IsolationLevel) => ITransaction;
16
+ }
@@ -0,0 +1,75 @@
1
+ import { DatasourceError, DefaultValueConverter, DeterministicTypeConverter, StatementTextRenderer, } from "@deterministic-code/datasources";
2
+ import { PostgresConnection } from "./connection.js";
3
+ import { PostgresCommand } from "./command.js";
4
+ import { PostgresTransaction } from "./transaction.js";
5
+ import { PostgresRowTypeConverters } from "./converters/PostgresRowTypeConverters.js";
6
+ import { PostgresStatementRenderer } from "./PostgresStatementRenderer.js";
7
+ import { columnTypes } from "./columnTypes.js";
8
+ export { columnTypes };
9
+ const statements = new StatementTextRenderer(t => new PostgresStatementRenderer(t));
10
+ export class PostgresDriver {
11
+ statementRenderer = statements;
12
+ information = {
13
+ descriptions: [
14
+ "PostgreSQL server driver.",
15
+ "Full connection, command, transaction, and repository support.",
16
+ ],
17
+ overrides: [
18
+ "Overrides SelectInsertedIdStatement to SELECT lastval() AS {column}",
19
+ "Overrides CreateTableStatement identity to GENERATED BY DEFAULT AS IDENTITY",
20
+ "Overrides CreateTableStatement boolean defaults (0/1 → TRUE/FALSE)",
21
+ "Overrides CreateTableStatement type mapping (int→INTEGER, uuid→UUID, string→TEXT, bool→BOOLEAN, decimal→NUMERIC, float→DOUBLE PRECISION, datetime→TIMESTAMPTZ, binary→BYTEA)",
22
+ "Overrides CreateCreateStoredProcedureStatement to PROCEDURE Create{Plural}",
23
+ "Overrides CreateReadStoredProcedureStatement to PROCEDURE Read{Plural}By{Properties}",
24
+ "Overrides CreateUpdateStoredProcedureStatement to PROCEDURE Update{Plural}By{Properties}",
25
+ "Overrides CreateDeleteStoredProcedureStatement to PROCEDURE Delete{Plural}By{Properties}",
26
+ "Overrides CreateBulkCreateStoredProcedureStatement to PROCEDURE BulkCreate{Plural}",
27
+ "Overrides CreateBulkUpdateStoredProcedureStatement to PROCEDURE BulkUpdate{Plural}",
28
+ "Overrides CreateBulkDeleteStoredProcedureStatement to PROCEDURE BulkDelete{Plural}",
29
+ ],
30
+ changeLogs: [],
31
+ };
32
+ rowTypeConverters = PostgresRowTypeConverters;
33
+ quoteLeft = '"';
34
+ quoteRight = '"';
35
+ typeConverter = new DeterministicTypeConverter({
36
+ string: "TEXT",
37
+ character: "CHAR(1)",
38
+ number: "INTEGER",
39
+ integer: "INTEGER",
40
+ unsignedinteger: "BIGINT",
41
+ smallinteger: "SMALLINT",
42
+ unsignedsmallinteger: "INTEGER",
43
+ biginteger: "BIGINT",
44
+ unsignedbiginteger: "NUMERIC(20)",
45
+ float: "DOUBLE PRECISION",
46
+ decimal: "NUMERIC",
47
+ boolean: "BOOLEAN",
48
+ datetime: "TIMESTAMPTZ",
49
+ binary: "BYTEA",
50
+ uuid: "UUID",
51
+ reference: "INTEGER",
52
+ }, {
53
+ dialectName: "postgres",
54
+ stringUnlimited: "TEXT",
55
+ stringSized: (n) => `VARCHAR(${n})`,
56
+ character: "CHAR",
57
+ decimal: { kind: "optional", type: "NUMERIC", fallback: "NUMERIC" },
58
+ });
59
+ defaultValueConverter = new DefaultValueConverter({
60
+ newId: () => "gen_random_uuid()",
61
+ now: () => "LOCALTIMESTAMP",
62
+ utcNow: () => `(NOW() AT TIME ZONE 'UTC')`,
63
+ booleanOn: "TRUE",
64
+ booleanOff: "FALSE",
65
+ hex: (arg) => `'\\x${arg}'`,
66
+ hexReverse: /^'\\x(.*)'$/,
67
+ });
68
+ columnType = (name) => columnTypes[name] ??
69
+ (() => {
70
+ throw new DatasourceError(`Unknown postgres type "${name}"`);
71
+ })();
72
+ connection = (options) => PostgresConnection.from(options);
73
+ command = (connection) => new PostgresCommand(connection, statements);
74
+ transaction = (connection, isolation = "readCommitted") => new PostgresTransaction(connection, isolation);
75
+ }
@@ -0,0 +1,4 @@
1
+ import { DropRoutineStatementTextRenderer, type DropRoutineStatement } from "@deterministic-code/datasources";
2
+ export declare class PostgresDropRoutineStatementRenderer extends DropRoutineStatementTextRenderer {
3
+ render(statement: DropRoutineStatement): string;
4
+ }
@@ -0,0 +1,6 @@
1
+ import { DropRoutineStatementTextRenderer } from "@deterministic-code/datasources";
2
+ export class PostgresDropRoutineStatementRenderer extends DropRoutineStatementTextRenderer {
3
+ render(statement) {
4
+ return `DROP FUNCTION IF EXISTS ${statement.name}`;
5
+ }
6
+ }
@@ -0,0 +1,4 @@
1
+ import { DropTriggerStatementTextRenderer, type DropTriggerStatement } from "@deterministic-code/datasources";
2
+ export declare class PostgresDropTriggerStatementRenderer extends DropTriggerStatementTextRenderer {
3
+ render(statement: DropTriggerStatement): string;
4
+ }
@@ -0,0 +1,6 @@
1
+ import { DropTriggerStatementTextRenderer } from "@deterministic-code/datasources";
2
+ export class PostgresDropTriggerStatementRenderer extends DropTriggerStatementTextRenderer {
3
+ render(statement) {
4
+ return `DROP TRIGGER IF EXISTS ${statement.name} ON ${statement.table}`;
5
+ }
6
+ }
@@ -0,0 +1,4 @@
1
+ import { IdentityInsertStatementTextRenderer, type IdentityInsertStatement } from "@deterministic-code/datasources";
2
+ export declare class PostgresIdentityInsertStatementRenderer extends IdentityInsertStatementTextRenderer {
3
+ render(statement: IdentityInsertStatement): string;
4
+ }
@@ -0,0 +1,11 @@
1
+ import { IdentityInsertStatementTextRenderer, } from "@deterministic-code/datasources";
2
+ export class PostgresIdentityInsertStatementRenderer extends IdentityInsertStatementTextRenderer {
3
+ render(statement) {
4
+ const inserts = super.render(statement);
5
+ if (!statement.identity || statement.inserts.length === 0)
6
+ return inserts;
7
+ const quoted = statement.inserts[0].table;
8
+ return `${inserts}
9
+ SELECT setval(pg_get_serial_sequence('${statement.table}', '${statement.idColumn}'), (SELECT MAX("${statement.idColumn}") FROM ${quoted}))`;
10
+ }
11
+ }
@@ -0,0 +1,19 @@
1
+ import { AlterTableStatement, BulkUpdateStatement, CreateBulkCreateStoredProcedureStatement, CreateBulkDeleteStoredProcedureStatement, CreateBulkUpdateStoredProcedureStatement, CreateCreateStoredProcedureStatement, CreateDeleteStoredProcedureStatement, CreateReadStoredProcedureStatement, CreateTableStatement, CreateTriggerStatement, CreateUpdateStoredProcedureStatement, DropRoutineStatement, DropTriggerStatement, IdentityInsertStatement, SelectInsertedIdStatement, StatementRenderer, type StatementTextRenderer } from "@deterministic-code/datasources";
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 visitAlterTable(alter: AlterTableStatement): string;
7
+ protected visitDropTrigger(trigger: DropTriggerStatement): string;
8
+ protected visitBulkUpdate(update: BulkUpdateStatement): string;
9
+ protected visitCreateTrigger(trigger: CreateTriggerStatement): string;
10
+ protected visitIdentityInsert(insert: IdentityInsertStatement): string;
11
+ protected visitDropRoutine(routine: DropRoutineStatement): string;
12
+ protected visitCreateCreateStoredProcedureStatement(statement: CreateCreateStoredProcedureStatement): string;
13
+ protected visitCreateReadStoredProcedureStatement(statement: CreateReadStoredProcedureStatement): string;
14
+ protected visitCreateUpdateStoredProcedureStatement(statement: CreateUpdateStoredProcedureStatement): string;
15
+ protected visitCreateDeleteStoredProcedureStatement(statement: CreateDeleteStoredProcedureStatement): string;
16
+ protected visitCreateBulkCreateStoredProcedureStatement(statement: CreateBulkCreateStoredProcedureStatement): string;
17
+ protected visitCreateBulkUpdateStoredProcedureStatement(statement: CreateBulkUpdateStoredProcedureStatement): string;
18
+ protected visitCreateBulkDeleteStoredProcedureStatement(statement: CreateBulkDeleteStoredProcedureStatement): string;
19
+ }
@@ -0,0 +1,64 @@
1
+ import { SelectInsertedIdStatementTextRenderer, StatementRenderer, } from "@deterministic-code/datasources";
2
+ import { PostgresAlterTableStatementRenderer } from "./PostgresAlterTableStatementRenderer.js";
3
+ import { PostgresBulkUpdateStatementTextRenderer } from "./PostgresBulkUpdateStatementTextRenderer.js";
4
+ import { PostgresCreateTableStatementRenderer } from "./PostgresCreateTableStatementRenderer.js";
5
+ import { PostgresCreateTriggerStatementRenderer } from "./PostgresCreateTriggerStatementRenderer.js";
6
+ import { PostgresDropRoutineStatementRenderer } from "./PostgresDropRoutineStatementRenderer.js";
7
+ import { PostgresStoredProcedureStatementRenderer } from "./PostgresStoredProcedureStatementRenderer.js";
8
+ import { PostgresDropTriggerStatementRenderer } from "./PostgresDropTriggerStatementRenderer.js";
9
+ import { PostgresIdentityInsertStatementRenderer } from "./PostgresIdentityInsertStatementRenderer.js";
10
+ class PostgresSelectInsertedIdStatementTextRenderer extends SelectInsertedIdStatementTextRenderer {
11
+ render(statement) {
12
+ return `SELECT lastval() AS ${statement.column}`;
13
+ }
14
+ }
15
+ export class PostgresStatementRenderer extends StatementRenderer {
16
+ constructor(text) {
17
+ super(text);
18
+ }
19
+ visitSelectInsertedId(inserted) {
20
+ return new PostgresSelectInsertedIdStatementTextRenderer().render(inserted);
21
+ }
22
+ visitCreateTable(create) {
23
+ return new PostgresCreateTableStatementRenderer().render(create);
24
+ }
25
+ visitAlterTable(alter) {
26
+ return new PostgresAlterTableStatementRenderer().renderAlter(alter);
27
+ }
28
+ visitDropTrigger(trigger) {
29
+ return new PostgresDropTriggerStatementRenderer().render(trigger);
30
+ }
31
+ visitBulkUpdate(update) {
32
+ return new PostgresBulkUpdateStatementTextRenderer().render(update);
33
+ }
34
+ visitCreateTrigger(trigger) {
35
+ return new PostgresCreateTriggerStatementRenderer().render(trigger);
36
+ }
37
+ visitIdentityInsert(insert) {
38
+ return new PostgresIdentityInsertStatementRenderer().render(insert);
39
+ }
40
+ visitDropRoutine(routine) {
41
+ return new PostgresDropRoutineStatementRenderer().render(routine);
42
+ }
43
+ visitCreateCreateStoredProcedureStatement(statement) {
44
+ return new PostgresStoredProcedureStatementRenderer().renderCreate(statement);
45
+ }
46
+ visitCreateReadStoredProcedureStatement(statement) {
47
+ return new PostgresStoredProcedureStatementRenderer().renderRead(statement);
48
+ }
49
+ visitCreateUpdateStoredProcedureStatement(statement) {
50
+ return new PostgresStoredProcedureStatementRenderer().renderUpdate(statement);
51
+ }
52
+ visitCreateDeleteStoredProcedureStatement(statement) {
53
+ return new PostgresStoredProcedureStatementRenderer().renderDelete(statement);
54
+ }
55
+ visitCreateBulkCreateStoredProcedureStatement(statement) {
56
+ return new PostgresStoredProcedureStatementRenderer().renderBulkCreate(statement);
57
+ }
58
+ visitCreateBulkUpdateStoredProcedureStatement(statement) {
59
+ return new PostgresStoredProcedureStatementRenderer().renderBulkUpdate(statement);
60
+ }
61
+ visitCreateBulkDeleteStoredProcedureStatement(statement) {
62
+ return new PostgresStoredProcedureStatementRenderer().renderBulkDelete(statement);
63
+ }
64
+ }
@@ -0,0 +1,10 @@
1
+ import type { CreateBulkCreateStoredProcedureStatement, CreateBulkDeleteStoredProcedureStatement, CreateBulkUpdateStoredProcedureStatement, CreateCreateStoredProcedureStatement, CreateDeleteStoredProcedureStatement, CreateReadStoredProcedureStatement, CreateUpdateStoredProcedureStatement } from "@deterministic-code/datasources";
2
+ export declare class PostgresStoredProcedureStatementRenderer {
3
+ renderCreate(statement: CreateCreateStoredProcedureStatement): string;
4
+ renderRead(statement: CreateReadStoredProcedureStatement): string;
5
+ renderUpdate(statement: CreateUpdateStoredProcedureStatement): string;
6
+ renderDelete(statement: CreateDeleteStoredProcedureStatement): string;
7
+ renderBulkCreate(statement: CreateBulkCreateStoredProcedureStatement): string;
8
+ renderBulkUpdate(statement: CreateBulkUpdateStoredProcedureStatement): string;
9
+ renderBulkDelete(statement: CreateBulkDeleteStoredProcedureStatement): string;
10
+ }
@@ -0,0 +1,111 @@
1
+ const params = (list) => list.map((p) => ` ${p.name} ${p.type}`).join(",\n");
2
+ const recordset = (columns) => columns.map((c) => `${c.name} ${c.type}`).join(", ");
3
+ export class PostgresStoredProcedureStatementRenderer {
4
+ renderCreate(statement) {
5
+ return `CREATE OR REPLACE FUNCTION ${statement.name}(
6
+ ${params(statement.parameters)}
7
+ ) RETURNS ${statement.keyType} LANGUAGE plpgsql AS $$
8
+ DECLARE
9
+ new_id ${statement.keyType};
10
+ BEGIN
11
+ INSERT INTO ${statement.table} (${statement.parameters.map((p) => p.name).join(", ")})
12
+ VALUES (${statement.parameters.map((p) => p.name).join(", ")})
13
+ RETURNING ${statement.key} INTO new_id;
14
+ RETURN new_id;
15
+ END;
16
+ $$`;
17
+ }
18
+ renderRead(statement) {
19
+ const aliased = statement.columns.map((c) => `t.${c}`).join(", ");
20
+ if (statement.byProperties.length === 0) {
21
+ return `CREATE OR REPLACE FUNCTION ${statement.name}() RETURNS SETOF ${statement.table}
22
+ LANGUAGE sql AS $$ SELECT ${aliased} FROM ${statement.table} t ORDER BY t.${statement.key}; $$`;
23
+ }
24
+ const signature = statement.byProperties
25
+ .map((c) => `${c} ${statement.parameters.find((p) => p.name === c)?.type ?? statement.keyType}`)
26
+ .join(", ");
27
+ const pred = statement.byProperties
28
+ .map((c) => `t.${c} = ${statement.name}.${c}`)
29
+ .join(" AND ");
30
+ return `CREATE OR REPLACE FUNCTION ${statement.name}(${signature}) RETURNS SETOF ${statement.table}
31
+ LANGUAGE sql AS $$ SELECT ${aliased} FROM ${statement.table} t WHERE ${pred}; $$`;
32
+ }
33
+ renderUpdate(statement) {
34
+ const sets = statement.writable.map((c) => ` ${c} = ${statement.name}.${c}`);
35
+ if (statement.auditUpdated !== undefined) {
36
+ const auditParam = statement.parameters.find((p) => p.name.includes("new_updated"));
37
+ if (auditParam)
38
+ sets.push(` ${statement.auditUpdated} = ${statement.name}.${auditParam.name}`);
39
+ }
40
+ const by = statement.byProperties.length > 0 ? statement.byProperties : [statement.key];
41
+ const pred = by.map((c) => `t.${c} = ${statement.name}.${c}`).join(" AND ");
42
+ return `CREATE OR REPLACE FUNCTION ${statement.name}(
43
+ ${params(statement.parameters)}
44
+ ) RETURNS INT LANGUAGE plpgsql AS $$
45
+ DECLARE
46
+ affected INT;
47
+ BEGIN
48
+ UPDATE ${statement.table} t
49
+ SET
50
+ ${sets.join(",\n")}
51
+ WHERE ${pred};
52
+ GET DIAGNOSTICS affected = ROW_COUNT;
53
+ RETURN affected;
54
+ END;
55
+ $$`;
56
+ }
57
+ renderDelete(statement) {
58
+ const by = statement.byProperties.length > 0 ? statement.byProperties : [statement.key];
59
+ const signature = by
60
+ .map((c) => `${c} ${statement.parameters.find((p) => p.name === c)?.type ?? statement.keyType}`)
61
+ .join(", ");
62
+ const pred = by.map((c) => `t.${c} = ${statement.name}.${c}`).join(" AND ");
63
+ return `CREATE OR REPLACE FUNCTION ${statement.name}(${signature}) RETURNS INT LANGUAGE plpgsql AS $$
64
+ DECLARE
65
+ affected INT;
66
+ BEGIN
67
+ DELETE FROM ${statement.table} t WHERE ${pred};
68
+ GET DIAGNOSTICS affected = ROW_COUNT;
69
+ RETURN affected;
70
+ END;
71
+ $$`;
72
+ }
73
+ renderBulkCreate(statement) {
74
+ const cols = statement.columns.map((c) => c.name).join(", ");
75
+ return `CREATE OR REPLACE PROCEDURE ${statement.name}(${statement.payload.name} jsonb, INOUT ids jsonb DEFAULT NULL)
76
+ LANGUAGE plpgsql AS $$
77
+ BEGIN
78
+ WITH inserted AS (
79
+ INSERT INTO ${statement.table} (${cols})
80
+ SELECT ${cols}
81
+ FROM jsonb_to_recordset(${statement.payload.name}) AS x(${recordset(statement.columns)})
82
+ RETURNING ${statement.key}
83
+ )
84
+ SELECT COALESCE(jsonb_agg(${statement.key}), '[]'::jsonb) INTO ids FROM inserted;
85
+ END;
86
+ $$`;
87
+ }
88
+ renderBulkUpdate(statement) {
89
+ const sets = statement.columns
90
+ .filter((c) => c.name !== statement.key)
91
+ .map((c) => `${c.name} = x.${c.name}`)
92
+ .join(", ");
93
+ return `CREATE OR REPLACE PROCEDURE ${statement.name}(${statement.payload.name} jsonb)
94
+ LANGUAGE plpgsql AS $$
95
+ BEGIN
96
+ UPDATE ${statement.table} t SET
97
+ ${sets}
98
+ FROM jsonb_to_recordset(${statement.payload.name}) AS x(${recordset(statement.columns)})
99
+ WHERE t.${statement.key} = x.${statement.key};
100
+ END;
101
+ $$`;
102
+ }
103
+ renderBulkDelete(statement) {
104
+ return `CREATE OR REPLACE PROCEDURE ${statement.name}(${statement.payload.name} jsonb)
105
+ LANGUAGE plpgsql AS $$
106
+ BEGIN
107
+ DELETE FROM ${statement.table} WHERE ${statement.key} IN (SELECT ${statement.key} FROM jsonb_to_recordset(${statement.payload.name}) AS x(${statement.key} ${statement.keyType}));
108
+ END;
109
+ $$`;
110
+ }
111
+ }
package/dist/bind.d.ts ADDED
@@ -0,0 +1,7 @@
1
+ import { type Parameter } from "@deterministic-code/datasources";
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>;
package/dist/bind.js ADDED
@@ -0,0 +1,35 @@
1
+ import { parametersToRecord } from "@deterministic-code/datasources";
2
+ import { isBase64 } from "./converters/BinaryConverter.js";
3
+ import { isIsoDate } from "./converters/DateTimeConverter.js";
4
+ import { reverse } from "./converters/PostgresRowTypeConverters.js";
5
+ const NAME = /\$([A-Za-z_][A-Za-z0-9_]*)/g;
6
+ const quoteMixedCase = (sql) => sql.replace(/\$[A-Za-z_][A-Za-z0-9_]*|'[^']*'|"[^"]*"|`[^`]*`|\b[A-Za-z_][A-Za-z0-9_]*\b/g, (token) => token.startsWith("$") || token.startsWith("'") || token.startsWith("\"") || token.startsWith("`")
7
+ || !/[A-Z]/.test(token) || !/[a-z]/.test(token)
8
+ ? token
9
+ : `"${token}"`);
10
+ const wire = (value) => typeof value !== "string"
11
+ ? value
12
+ : isIsoDate(value)
13
+ ? new Date(value)
14
+ : isBase64(value)
15
+ ? Uint8Array.from(Buffer.from(value, "base64"))
16
+ : value;
17
+ export const toPostgres = (sql, params) => {
18
+ const names = [];
19
+ const text = quoteMixedCase(sql).replace(NAME, (_match, name) => {
20
+ let index = names.indexOf(name);
21
+ if (index < 0) {
22
+ names.push(name);
23
+ index = names.length - 1;
24
+ }
25
+ return `$${index + 1}`;
26
+ });
27
+ return { text, values: names.map((name) => wire(params?.[name] ?? null)) };
28
+ };
29
+ export const bindParameters = (parameters) => {
30
+ const record = parametersToRecord(parameters);
31
+ const bound = {};
32
+ for (const [name, value] of Object.entries(record))
33
+ bound[name] = reverse(value);
34
+ return bound;
35
+ };
@@ -0,0 +1 @@
1
+ export declare const columnTypes: Record<string, string>;
@@ -0,0 +1,10 @@
1
+ export const columnTypes = {
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
+ };
@@ -0,0 +1,14 @@
1
+ import { CommandMiddlewareHost, Native, StatementTextRenderer, Response, type Cells, type ICommand, type IReader, type IStatement, type Parameter } from "@deterministic-code/datasources";
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,92 @@
1
+ import { ArrayReader, CommandMiddlewareHost, FirstResultReader, DatasourceError, Native, ParameterDirection, ReturnStatement, SelectStatement, StatementTextRenderer, Response, splitStatements, statementParameters, statementReader, } from "@deterministic-code/datasources";
2
+ import { bindParameters, toPostgres } from "./bind.js";
3
+ import { PostgresStatementRenderer } from "./PostgresStatementRenderer.js";
4
+ export class PostgresCommand extends CommandMiddlewareHost {
5
+ connection;
6
+ statements;
7
+ constructor(connection, statements) {
8
+ super();
9
+ this.connection = connection;
10
+ this.statements = statements ?? new StatementTextRenderer(t => new PostgresStatementRenderer(t));
11
+ }
12
+ async executeReaderNative(text, parameters) {
13
+ return this.run(text.text, parameters, async (query, params) => {
14
+ if (!query.trim())
15
+ return { value: new ArrayReader([]), rows: [] };
16
+ this.connection.ensureOpen();
17
+ const statements = splitStatements(query);
18
+ if (statements.length === 0)
19
+ return { value: new ArrayReader([]), rows: [] };
20
+ const bound = bindParameters(params);
21
+ return FirstResultReader.capture(statementReader(statements, (sql) => this.query(sql, bound)));
22
+ });
23
+ }
24
+ async executeReaderStatement(statement, parameters) {
25
+ return this.executeReaderNative(new Native(this.statements.render(statement)), parameters);
26
+ }
27
+ async executeStatement(statement, parameters) {
28
+ return statement instanceof SelectStatement || statement instanceof ReturnStatement
29
+ ? this.firstCell(statement, parameters)
30
+ : this.executeNative(new Native(this.statements.render(statement)), parameters);
31
+ }
32
+ async firstCell(statement, parameters) {
33
+ const reader = await this.executeReaderStatement(statement, parameters);
34
+ try {
35
+ if (!(await reader.next()))
36
+ return new Response(null, 0, parameters);
37
+ const row = reader.current;
38
+ /* v8 ignore next */
39
+ if (!row)
40
+ throw new DatasourceError("Reader current must be a row");
41
+ const values = Object.values(row);
42
+ return new Response(values.length === 0 ? null : values[0], 1, parameters);
43
+ }
44
+ finally {
45
+ await reader.dispose();
46
+ }
47
+ }
48
+ async executeNative(text, parameters) {
49
+ return this.run(text.text, parameters, async (query, params) => {
50
+ if (!query.trim())
51
+ return { value: new Response(undefined, 0, params), rows: [] };
52
+ this.connection.ensureOpen();
53
+ const named = bindParameters(params);
54
+ let rowsAffected = 0;
55
+ let parameters = params;
56
+ for (const part of splitStatements(query)) {
57
+ const names = new Set(statementParameters(part));
58
+ const used = Object.fromEntries(Object.entries(named).filter(([name]) => names.has(name.replace(/^[$:@]/, ""))));
59
+ const result = await this.runSql(part, used, params);
60
+ rowsAffected += result.rowsAffected;
61
+ parameters = result.parameters ?? params;
62
+ }
63
+ return {
64
+ value: new Response(null, rowsAffected, parameters),
65
+ rows: [],
66
+ };
67
+ });
68
+ }
69
+ async runSql(sql, params, parameters) {
70
+ const { text, values } = toPostgres(sql, params);
71
+ const queried = await this.connection.require().query(text, values);
72
+ const row = queried.rows[0];
73
+ return {
74
+ rowsAffected: queried.rowCount ?? queried.rows.length,
75
+ result: null,
76
+ parameters: parameters?.map((parameter) => {
77
+ if (!parameter.direction || parameter.direction === ParameterDirection.Input || !row)
78
+ return parameter;
79
+ const cells = row;
80
+ const cell = cells[parameter.name] ?? cells[parameter.name.toLowerCase()];
81
+ if (cell === undefined)
82
+ return parameter;
83
+ return { ...parameter, value: cellValue(cell) };
84
+ }),
85
+ };
86
+ }
87
+ query(sql, params) {
88
+ const { text, values } = toPostgres(sql, params);
89
+ return this.connection.require().rows(text, values);
90
+ }
91
+ }
92
+ const cellValue = (cell) => cell !== null && typeof cell === "object" ? JSON.stringify(cell) : cell;
@@ -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
+ }