@deterministic-code/datasources-postgres 0.1.18 → 0.1.30
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/DefaultValueConverter.d.ts +25 -0
- package/dist/DefaultValueConverter.js +172 -0
- package/dist/DefaultValueOptions.d.ts +11 -0
- package/dist/DefaultValueOptions.js +1 -0
- package/dist/PostgresAlterTableStatementRenderer.d.ts +2 -0
- package/dist/PostgresAlterTableStatementRenderer.js +13 -3
- package/dist/PostgresCreateTableStatementRenderer.d.ts +1 -0
- package/dist/PostgresCreateTableStatementRenderer.js +10 -5
- package/dist/PostgresCreateTriggerStatementRenderer.js +4 -2
- package/dist/PostgresDriver.d.ts +12 -2
- package/dist/PostgresDriver.js +49 -34
- package/dist/PostgresDropTriggerStatementRenderer.js +2 -1
- package/dist/PostgresQuotedRenderers.d.ts +13 -0
- package/dist/PostgresQuotedRenderers.js +28 -0
- package/dist/PostgresSqlSeedStatementRenderer.d.ts +4 -0
- package/dist/PostgresSqlSeedStatementRenderer.js +53 -0
- package/dist/PostgresStatementRenderer.d.ts +7 -1
- package/dist/PostgresStatementRenderer.js +21 -1
- package/dist/PostgresStoredProcedureStatementRenderer.js +10 -8
- package/dist/TypeMap.d.ts +9 -0
- package/dist/TypeMap.js +33 -0
- package/dist/applyKeywords.d.ts +1 -0
- package/dist/applyKeywords.js +14 -0
- package/dist/procedureSpecs.d.ts +10 -0
- package/dist/procedureSpecs.js +17 -0
- package/package.json +2 -2
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { DefaultValueOptions } from "./DefaultValueOptions.js";
|
|
2
|
+
export declare const EMPTY_UUID = "00000000-0000-0000-0000-000000000000";
|
|
3
|
+
export type SpecToken = {
|
|
4
|
+
spec: RegExp;
|
|
5
|
+
specText?: (arg: string) => string;
|
|
6
|
+
};
|
|
7
|
+
export type DefaultEntry = SpecToken & {
|
|
8
|
+
native: (arg: string) => string;
|
|
9
|
+
reverse: RegExp;
|
|
10
|
+
};
|
|
11
|
+
type DefaultToken = {
|
|
12
|
+
token: string;
|
|
13
|
+
arg?: string | boolean;
|
|
14
|
+
};
|
|
15
|
+
export declare const sqlStringLiteral: (value: string | number | boolean | null | undefined) => string;
|
|
16
|
+
export declare class DefaultValueConverter {
|
|
17
|
+
static readonly spec: Record<string, Record<string, SpecToken>>;
|
|
18
|
+
readonly dictionary: Record<string, Record<string, DefaultEntry>>;
|
|
19
|
+
constructor(options: DefaultValueOptions);
|
|
20
|
+
static parse(specType: string, value: unknown): DefaultToken;
|
|
21
|
+
convert: (specType: string, text: string) => string | null;
|
|
22
|
+
reverseConvert: (specType: string, native: string) => string;
|
|
23
|
+
private static dictionaryOf;
|
|
24
|
+
}
|
|
25
|
+
export {};
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
export const EMPTY_UUID = "00000000-0000-0000-0000-000000000000";
|
|
2
|
+
const NUMERIC_TYPES = [
|
|
3
|
+
"integer",
|
|
4
|
+
"unsignedinteger",
|
|
5
|
+
"smallinteger",
|
|
6
|
+
"unsignedsmallinteger",
|
|
7
|
+
"biginteger",
|
|
8
|
+
"unsignedbiginteger",
|
|
9
|
+
"float",
|
|
10
|
+
"decimal",
|
|
11
|
+
];
|
|
12
|
+
const NUMERIC_LITERAL_TYPES = new Set([...NUMERIC_TYPES, "reference"]);
|
|
13
|
+
const CALL_FORM = /^([A-Za-z]+)\('(.*)'\)$/;
|
|
14
|
+
const quotedReverse = /^'(.*)'$/;
|
|
15
|
+
const identity = (arg) => arg;
|
|
16
|
+
const intLit = { spec: /^-?[0-9]+$/, specText: identity };
|
|
17
|
+
const uintLit = { spec: /^[0-9]+$/, specText: identity };
|
|
18
|
+
const floatLit = { spec: /^-?[0-9]+(\.[0-9]+)?$/, specText: identity };
|
|
19
|
+
export const sqlStringLiteral = (value) => `'${String(value).replace(/'/g, "''")}'`;
|
|
20
|
+
const escapeRe = (text) => text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
21
|
+
const exact = (text) => new RegExp(`^${escapeRe(text)}$`);
|
|
22
|
+
const quoted = (arg) => sqlStringLiteral(arg);
|
|
23
|
+
const callTokenName = (name) => (name === "uuid" ? "Uuid" : name);
|
|
24
|
+
const token = (specToken, native, reverse) => ({
|
|
25
|
+
...specToken,
|
|
26
|
+
native,
|
|
27
|
+
reverse,
|
|
28
|
+
});
|
|
29
|
+
export class DefaultValueConverter {
|
|
30
|
+
static spec = {
|
|
31
|
+
string: { String: { spec: /^.*$/, specText: identity } },
|
|
32
|
+
character: { String: { spec: /^.+$/, specText: identity } },
|
|
33
|
+
integer: { Numeric: intLit },
|
|
34
|
+
unsignedinteger: { Numeric: uintLit },
|
|
35
|
+
smallinteger: { Numeric: intLit },
|
|
36
|
+
unsignedsmallinteger: { Numeric: uintLit },
|
|
37
|
+
biginteger: { Numeric: intLit },
|
|
38
|
+
unsignedbiginteger: { Numeric: uintLit },
|
|
39
|
+
float: { Numeric: floatLit },
|
|
40
|
+
decimal: { Numeric: floatLit, String: { spec: /^.*$/, specText: identity } },
|
|
41
|
+
boolean: { Boolean: { spec: /^(true|false)$/, specText: identity } },
|
|
42
|
+
datetime: {
|
|
43
|
+
Now: { spec: /^Now$/ },
|
|
44
|
+
UtcNow: { spec: /^UtcNow$/ },
|
|
45
|
+
DateTime: {
|
|
46
|
+
spec: /^DateTime\('[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(Z|[+-][0-9]{2}:[0-9]{2})'\)$/,
|
|
47
|
+
specText: (arg) => `DateTime('${arg}')`,
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
binary: {
|
|
51
|
+
Hex: { spec: /^Hex\('([0-9a-fA-F]{2})*'\)$/, specText: (arg) => `Hex('${arg}')` },
|
|
52
|
+
},
|
|
53
|
+
uuid: {
|
|
54
|
+
NewId: { spec: /^NewId$/ },
|
|
55
|
+
Empty: { spec: /^Empty$/ },
|
|
56
|
+
Uuid: {
|
|
57
|
+
spec: /^uuid\('[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}'\)$/,
|
|
58
|
+
specText: (arg) => `uuid('${arg}')`,
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
reference: {},
|
|
62
|
+
};
|
|
63
|
+
dictionary;
|
|
64
|
+
constructor(options) {
|
|
65
|
+
this.dictionary = DefaultValueConverter.dictionaryOf(options);
|
|
66
|
+
}
|
|
67
|
+
static parse(specType, value) {
|
|
68
|
+
if (value === undefined || value === null)
|
|
69
|
+
return { token: "None" };
|
|
70
|
+
const v = String(value);
|
|
71
|
+
const call = CALL_FORM.exec(v);
|
|
72
|
+
if (call !== null)
|
|
73
|
+
return { token: callTokenName(call[1]), arg: call[2] };
|
|
74
|
+
if (specType === "boolean") {
|
|
75
|
+
return { token: "Boolean", arg: typeof value === "boolean" ? value : v === "true" };
|
|
76
|
+
}
|
|
77
|
+
if (NUMERIC_LITERAL_TYPES.has(specType))
|
|
78
|
+
return { token: "Numeric", arg: v };
|
|
79
|
+
if (specType === "datetime") {
|
|
80
|
+
if (v === "Now")
|
|
81
|
+
return { token: "Now" };
|
|
82
|
+
if (v === "UtcNow")
|
|
83
|
+
return { token: "UtcNow" };
|
|
84
|
+
return { token: "DateTime", arg: v };
|
|
85
|
+
}
|
|
86
|
+
if (specType === "uuid") {
|
|
87
|
+
if (v === "NewId")
|
|
88
|
+
return { token: "NewId" };
|
|
89
|
+
if (v === "Empty")
|
|
90
|
+
return { token: "Empty" };
|
|
91
|
+
return { token: "Uuid", arg: v };
|
|
92
|
+
}
|
|
93
|
+
if (specType === "binary")
|
|
94
|
+
return { token: "Hex", arg: v };
|
|
95
|
+
return { token: "String", arg: v };
|
|
96
|
+
}
|
|
97
|
+
convert = (specType, text) => {
|
|
98
|
+
const parsed = DefaultValueConverter.parse(specType, text);
|
|
99
|
+
if (parsed.token === "None")
|
|
100
|
+
return null;
|
|
101
|
+
const entries = this.dictionary[specType];
|
|
102
|
+
if (entries === undefined)
|
|
103
|
+
throw new Error(`Unknown spec field type: ${specType}`);
|
|
104
|
+
const entry = entries[parsed.token];
|
|
105
|
+
if (entry === undefined)
|
|
106
|
+
return null;
|
|
107
|
+
if (!entry.spec.test(text)) {
|
|
108
|
+
throw new Error(`default_value '${text}' is not a valid ${specType} ${parsed.token} default`);
|
|
109
|
+
}
|
|
110
|
+
const expr = entry.native(parsed.arg === undefined ? "" : String(parsed.arg));
|
|
111
|
+
return expr === "" ? null : expr;
|
|
112
|
+
};
|
|
113
|
+
reverseConvert = (specType, native) => {
|
|
114
|
+
const entries = this.dictionary[specType];
|
|
115
|
+
if (entries === undefined)
|
|
116
|
+
throw new Error(`Unknown spec field type: ${specType}`);
|
|
117
|
+
for (const [name, entry] of Object.entries(entries)) {
|
|
118
|
+
const match = entry.reverse.exec(native);
|
|
119
|
+
if (match === null)
|
|
120
|
+
continue;
|
|
121
|
+
const arg = match.slice(1).find((group) => group !== undefined) ?? "";
|
|
122
|
+
return entry.specText === undefined ? name : entry.specText(arg);
|
|
123
|
+
}
|
|
124
|
+
throw new Error(`Native default '${native}' is not a valid ${specType} default`);
|
|
125
|
+
};
|
|
126
|
+
static dictionaryOf = (opts) => {
|
|
127
|
+
const spec = DefaultValueConverter.spec;
|
|
128
|
+
const newId = opts.newId();
|
|
129
|
+
const now = opts.now();
|
|
130
|
+
const utcNow = opts.utcNow();
|
|
131
|
+
const numericEntries = (type) => {
|
|
132
|
+
const numeric = spec[type].Numeric;
|
|
133
|
+
return {
|
|
134
|
+
Numeric: token(numeric, (arg) => arg, numeric.spec),
|
|
135
|
+
String: token(numeric, (arg) => arg, numeric.spec),
|
|
136
|
+
};
|
|
137
|
+
};
|
|
138
|
+
const quote = opts.quote ?? quoted;
|
|
139
|
+
const quoteReverse = opts.quoteReverse ?? quotedReverse;
|
|
140
|
+
const quotedString = {
|
|
141
|
+
String: token(spec.string.String, quote, quoteReverse),
|
|
142
|
+
Numeric: token(spec.string.String, quote, quoteReverse),
|
|
143
|
+
};
|
|
144
|
+
const dictionary = {
|
|
145
|
+
string: quotedString,
|
|
146
|
+
character: {
|
|
147
|
+
String: token(spec.character.String, quote, quoteReverse),
|
|
148
|
+
Numeric: token(spec.character.String, quote, quoteReverse),
|
|
149
|
+
},
|
|
150
|
+
boolean: {
|
|
151
|
+
Boolean: token(spec.boolean.Boolean, (arg) => (arg === "true" ? opts.booleanOn : opts.booleanOff), new RegExp(`^(${escapeRe(opts.booleanOn)}|${escapeRe(opts.booleanOff)})$`)),
|
|
152
|
+
},
|
|
153
|
+
datetime: {
|
|
154
|
+
Now: token(spec.datetime.Now, () => now, exact(now)),
|
|
155
|
+
UtcNow: token(spec.datetime.UtcNow, () => utcNow, exact(utcNow)),
|
|
156
|
+
DateTime: token(spec.datetime.DateTime, quoted, quotedReverse),
|
|
157
|
+
},
|
|
158
|
+
binary: {
|
|
159
|
+
Hex: token(spec.binary.Hex, opts.hex, opts.hexReverse),
|
|
160
|
+
},
|
|
161
|
+
uuid: {
|
|
162
|
+
NewId: token(spec.uuid.NewId, () => newId, exact(newId)),
|
|
163
|
+
Empty: token(spec.uuid.Empty, () => quoted(EMPTY_UUID), exact(quoted(EMPTY_UUID))),
|
|
164
|
+
Uuid: token(spec.uuid.Uuid, quoted, quotedReverse),
|
|
165
|
+
},
|
|
166
|
+
reference: {},
|
|
167
|
+
};
|
|
168
|
+
for (const type of NUMERIC_TYPES)
|
|
169
|
+
dictionary[type] = numericEntries(type);
|
|
170
|
+
return dictionary;
|
|
171
|
+
};
|
|
172
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export type DefaultValueOptions = {
|
|
2
|
+
newId: () => string;
|
|
3
|
+
now: () => string;
|
|
4
|
+
utcNow: () => string;
|
|
5
|
+
booleanOn: string;
|
|
6
|
+
booleanOff: string;
|
|
7
|
+
hex: (arg: string) => string;
|
|
8
|
+
hexReverse: RegExp;
|
|
9
|
+
quote?: (arg: string) => string;
|
|
10
|
+
quoteReverse?: RegExp;
|
|
11
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { AlterTableStatementTextRenderer, type AlterAction, type Column } from "@deterministic-code/datasources";
|
|
2
2
|
export declare class PostgresAlterTableStatementRenderer extends AlterTableStatementTextRenderer {
|
|
3
|
+
protected quote(name: string): string;
|
|
3
4
|
protected typeName(column: Column): string;
|
|
5
|
+
protected columnDefault(column: Column): string;
|
|
4
6
|
protected actionSql(action: AlterAction): string;
|
|
5
7
|
private postgresAlter;
|
|
6
8
|
}
|
|
@@ -1,8 +1,18 @@
|
|
|
1
1
|
import { AlterTableStatementTextRenderer, } from "@deterministic-code/datasources";
|
|
2
|
-
import {
|
|
2
|
+
import { PostgresDriver } from "./PostgresDriver.js";
|
|
3
3
|
export class PostgresAlterTableStatementRenderer extends AlterTableStatementTextRenderer {
|
|
4
|
+
quote(name) {
|
|
5
|
+
return PostgresDriver.quote(name);
|
|
6
|
+
}
|
|
4
7
|
typeName(column) {
|
|
5
|
-
return
|
|
8
|
+
return PostgresDriver.nativeType(column);
|
|
9
|
+
}
|
|
10
|
+
columnDefault(column) {
|
|
11
|
+
return column.default == null
|
|
12
|
+
? ""
|
|
13
|
+
: column.default.name == null
|
|
14
|
+
? ` DEFAULT ${PostgresDriver.nativeDefault(column)}`
|
|
15
|
+
: ` CONSTRAINT ${this.quote(column.default.name)} DEFAULT ${PostgresDriver.nativeDefault(column)}`;
|
|
6
16
|
}
|
|
7
17
|
actionSql(action) {
|
|
8
18
|
return action.kind === "alterColumn" ? this.postgresAlter(action.column) : super.actionSql(action);
|
|
@@ -11,7 +21,7 @@ export class PostgresAlterTableStatementRenderer extends AlterTableStatementText
|
|
|
11
21
|
const name = this.quote(column.name);
|
|
12
22
|
const defaultSql = column.default == null
|
|
13
23
|
? `ALTER COLUMN ${name} DROP DEFAULT`
|
|
14
|
-
: `ALTER COLUMN ${name} SET DEFAULT ${column
|
|
24
|
+
: `ALTER COLUMN ${name} SET DEFAULT ${PostgresDriver.nativeDefault(column)}`;
|
|
15
25
|
const nullSql = column.notNull == null
|
|
16
26
|
? `ALTER COLUMN ${name} DROP NOT NULL`
|
|
17
27
|
: `ALTER COLUMN ${name} SET NOT NULL`;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { CreateTableStatementTextRenderer, type Column } from "@deterministic-code/datasources";
|
|
2
2
|
export declare class PostgresCreateTableStatementRenderer extends CreateTableStatementTextRenderer {
|
|
3
|
+
protected quote(name: string): string;
|
|
3
4
|
protected typeName(column: Column): string;
|
|
4
5
|
protected columnDefault(column: Column): string;
|
|
5
6
|
protected identity(column: Column): string;
|
|
@@ -1,16 +1,21 @@
|
|
|
1
1
|
import { ColumnDefault, CreateTableStatementTextRenderer, } from "@deterministic-code/datasources";
|
|
2
2
|
import { PostgresRowTypeConverters } from "./converters/PostgresRowTypeConverters.js";
|
|
3
|
-
import {
|
|
3
|
+
import { PostgresDriver } from "./PostgresDriver.js";
|
|
4
4
|
export class PostgresCreateTableStatementRenderer extends CreateTableStatementTextRenderer {
|
|
5
|
+
quote(name) {
|
|
6
|
+
return PostgresDriver.quote(name);
|
|
7
|
+
}
|
|
5
8
|
typeName(column) {
|
|
6
|
-
return
|
|
9
|
+
return PostgresDriver.nativeType(column);
|
|
7
10
|
}
|
|
8
11
|
columnDefault(column) {
|
|
9
12
|
const literal = sqlLiteral(column);
|
|
10
13
|
const value = column.default;
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
+
if (value == null)
|
|
15
|
+
return "";
|
|
16
|
+
if (literal != null)
|
|
17
|
+
return super.columnDefault({ ...column, default: new ColumnDefault(literal, value.name) });
|
|
18
|
+
return super.columnDefault({ ...column, default: new ColumnDefault(PostgresDriver.nativeDefault(column), value.name) });
|
|
14
19
|
}
|
|
15
20
|
identity(column) {
|
|
16
21
|
return column.identity ? " GENERATED BY DEFAULT AS IDENTITY" : "";
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import { CreateTriggerStatementTextRenderer } from "@deterministic-code/datasources";
|
|
2
|
+
import { PostgresDriver } from "./PostgresDriver.js";
|
|
2
3
|
export class PostgresCreateTriggerStatementRenderer extends CreateTriggerStatementTextRenderer {
|
|
3
4
|
render(statement) {
|
|
5
|
+
const updated = PostgresDriver.quote(statement.updatedColumn);
|
|
4
6
|
return `-- Shared updated_at function
|
|
5
7
|
CREATE OR REPLACE FUNCTION set_updated_at() RETURNS TRIGGER AS $$
|
|
6
8
|
BEGIN
|
|
7
|
-
NEW.${
|
|
9
|
+
NEW.${updated} = ${PostgresDriver.utcNow()};
|
|
8
10
|
RETURN NEW;
|
|
9
11
|
END;
|
|
10
12
|
$$ LANGUAGE plpgsql;
|
|
11
|
-
CREATE TRIGGER ${statement.name} BEFORE UPDATE ON ${statement.table} FOR EACH ROW EXECUTE FUNCTION set_updated_at()`;
|
|
13
|
+
CREATE TRIGGER ${PostgresDriver.quote(statement.name)} BEFORE UPDATE ON ${PostgresDriver.quote(statement.table)} FOR EACH ROW EXECUTE FUNCTION set_updated_at()`;
|
|
12
14
|
}
|
|
13
15
|
}
|
package/dist/PostgresDriver.d.ts
CHANGED
|
@@ -1,13 +1,23 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { StatementTextRenderer, type Cell, type Column, type DatasourceOptions, type ICommand, type IConnection, type IDriver, type Information, type ITransaction, type IsolationLevel, type RoutineParameter, type TypeConverter } from "@deterministic-code/datasources";
|
|
2
2
|
import { columnTypes } from "./columnTypes.js";
|
|
3
|
+
import { TypeMap } from "./TypeMap.js";
|
|
4
|
+
import { DefaultValueConverter } from "./DefaultValueConverter.js";
|
|
3
5
|
export { columnTypes };
|
|
4
6
|
export declare class PostgresDriver implements IDriver {
|
|
7
|
+
static readonly procedurePayloadType = "jsonb";
|
|
8
|
+
static applyKeywords: (sql: string, keyword: (word: string) => string) => string;
|
|
9
|
+
static quote: (name: string) => string;
|
|
10
|
+
static nativeType: (column: Column) => string;
|
|
11
|
+
static nativeDefault: (column: Column) => string;
|
|
12
|
+
static utcNow: () => string;
|
|
13
|
+
static nativeParam: (parameter: RoutineParameter) => string;
|
|
14
|
+
static nativeSpec: (spec: string) => string;
|
|
5
15
|
readonly statementRenderer: StatementTextRenderer;
|
|
6
16
|
readonly information: Information;
|
|
7
17
|
rowTypeConverters: Record<string, TypeConverter<Cell, Cell>>;
|
|
8
18
|
readonly quoteLeft = "\"";
|
|
9
19
|
readonly quoteRight = "\"";
|
|
10
|
-
readonly typeConverter:
|
|
20
|
+
readonly typeConverter: TypeMap;
|
|
11
21
|
readonly defaultValueConverter: DefaultValueConverter;
|
|
12
22
|
columnType: (name: string) => string;
|
|
13
23
|
connection: (options?: DatasourceOptions) => IConnection;
|
package/dist/PostgresDriver.js
CHANGED
|
@@ -1,13 +1,58 @@
|
|
|
1
|
-
import { DatasourceError,
|
|
1
|
+
import { DatasourceError, StatementTextRenderer, } from "@deterministic-code/datasources";
|
|
2
2
|
import { PostgresConnection } from "./connection.js";
|
|
3
3
|
import { PostgresCommand } from "./command.js";
|
|
4
4
|
import { PostgresTransaction } from "./transaction.js";
|
|
5
5
|
import { PostgresRowTypeConverters } from "./converters/PostgresRowTypeConverters.js";
|
|
6
6
|
import { PostgresStatementRenderer } from "./PostgresStatementRenderer.js";
|
|
7
7
|
import { columnTypes } from "./columnTypes.js";
|
|
8
|
+
import { applyKeywords } from "./applyKeywords.js";
|
|
9
|
+
import { TypeMap } from "./TypeMap.js";
|
|
10
|
+
import { DefaultValueConverter } from "./DefaultValueConverter.js";
|
|
8
11
|
export { columnTypes };
|
|
9
|
-
const statements = new StatementTextRenderer(t => new PostgresStatementRenderer(t));
|
|
12
|
+
const statements = new StatementTextRenderer(t => new PostgresStatementRenderer(t), undefined, undefined, undefined, applyKeywords);
|
|
13
|
+
const types = new TypeMap({
|
|
14
|
+
string: "TEXT",
|
|
15
|
+
character: "CHAR(1)",
|
|
16
|
+
integer: "INTEGER",
|
|
17
|
+
unsignedinteger: "BIGINT",
|
|
18
|
+
smallinteger: "SMALLINT",
|
|
19
|
+
unsignedsmallinteger: "INTEGER",
|
|
20
|
+
biginteger: "BIGINT",
|
|
21
|
+
unsignedbiginteger: "NUMERIC(20)",
|
|
22
|
+
float: "DOUBLE PRECISION",
|
|
23
|
+
decimal: "NUMERIC",
|
|
24
|
+
boolean: "BOOLEAN",
|
|
25
|
+
datetime: "TIMESTAMPTZ",
|
|
26
|
+
binary: "BYTEA",
|
|
27
|
+
uuid: "UUID",
|
|
28
|
+
reference: "INTEGER",
|
|
29
|
+
});
|
|
30
|
+
const defaults = new DefaultValueConverter({
|
|
31
|
+
newId: () => "gen_random_uuid()",
|
|
32
|
+
now: () => "LOCALTIMESTAMP",
|
|
33
|
+
utcNow: () => `(NOW() AT TIME ZONE 'UTC')`,
|
|
34
|
+
booleanOn: "TRUE",
|
|
35
|
+
booleanOff: "FALSE",
|
|
36
|
+
hex: (arg) => `'\\x${arg}'`,
|
|
37
|
+
hexReverse: /^'\\x(.*)'$/,
|
|
38
|
+
});
|
|
10
39
|
export class PostgresDriver {
|
|
40
|
+
static procedurePayloadType = "jsonb";
|
|
41
|
+
static applyKeywords = applyKeywords;
|
|
42
|
+
static quote = (name) => `"${name}"`;
|
|
43
|
+
static nativeType = (column) => column.field ? types.convertField(column.field) : columnTypes[column.type] ?? column.type;
|
|
44
|
+
static nativeDefault = (column) => column.default == null
|
|
45
|
+
? ""
|
|
46
|
+
: column.field === undefined
|
|
47
|
+
? column.default.value
|
|
48
|
+
: defaults.convert(column.field.type, column.default.value) ?? column.default.value;
|
|
49
|
+
static utcNow = () => defaults.convert("datetime", "UtcNow") ?? "";
|
|
50
|
+
static nativeParam = (parameter) => parameter.type === "payload"
|
|
51
|
+
? PostgresDriver.procedurePayloadType
|
|
52
|
+
: parameter.field
|
|
53
|
+
? types.convertField(parameter.field)
|
|
54
|
+
: types.dictionary[parameter.type] ?? parameter.type;
|
|
55
|
+
static nativeSpec = (spec) => spec === "payload" ? PostgresDriver.procedurePayloadType : types.dictionary[spec] ?? spec;
|
|
11
56
|
statementRenderer = statements;
|
|
12
57
|
information = {
|
|
13
58
|
descriptions: [
|
|
@@ -32,38 +77,8 @@ export class PostgresDriver {
|
|
|
32
77
|
rowTypeConverters = PostgresRowTypeConverters;
|
|
33
78
|
quoteLeft = '"';
|
|
34
79
|
quoteRight = '"';
|
|
35
|
-
typeConverter =
|
|
36
|
-
|
|
37
|
-
character: "CHAR(1)",
|
|
38
|
-
integer: "INTEGER",
|
|
39
|
-
unsignedinteger: "BIGINT",
|
|
40
|
-
smallinteger: "SMALLINT",
|
|
41
|
-
unsignedsmallinteger: "INTEGER",
|
|
42
|
-
biginteger: "BIGINT",
|
|
43
|
-
unsignedbiginteger: "NUMERIC(20)",
|
|
44
|
-
float: "DOUBLE PRECISION",
|
|
45
|
-
decimal: "NUMERIC",
|
|
46
|
-
boolean: "BOOLEAN",
|
|
47
|
-
datetime: "TIMESTAMPTZ",
|
|
48
|
-
binary: "BYTEA",
|
|
49
|
-
uuid: "UUID",
|
|
50
|
-
reference: "INTEGER",
|
|
51
|
-
}, {
|
|
52
|
-
dialectName: "postgres",
|
|
53
|
-
stringUnlimited: "TEXT",
|
|
54
|
-
stringSized: (n) => `VARCHAR(${n})`,
|
|
55
|
-
character: "CHAR",
|
|
56
|
-
decimal: { kind: "optional", type: "NUMERIC", fallback: "NUMERIC" },
|
|
57
|
-
});
|
|
58
|
-
defaultValueConverter = new DefaultValueConverter({
|
|
59
|
-
newId: () => "gen_random_uuid()",
|
|
60
|
-
now: () => "LOCALTIMESTAMP",
|
|
61
|
-
utcNow: () => `(NOW() AT TIME ZONE 'UTC')`,
|
|
62
|
-
booleanOn: "TRUE",
|
|
63
|
-
booleanOff: "FALSE",
|
|
64
|
-
hex: (arg) => `'\\x${arg}'`,
|
|
65
|
-
hexReverse: /^'\\x(.*)'$/,
|
|
66
|
-
});
|
|
80
|
+
typeConverter = types;
|
|
81
|
+
defaultValueConverter = defaults;
|
|
67
82
|
columnType = (name) => columnTypes[name] ??
|
|
68
83
|
(() => {
|
|
69
84
|
throw new DatasourceError(`Unknown postgres type "${name}"`);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { DropTriggerStatementTextRenderer } from "@deterministic-code/datasources";
|
|
2
|
+
import { PostgresDriver } from "./PostgresDriver.js";
|
|
2
3
|
export class PostgresDropTriggerStatementRenderer extends DropTriggerStatementTextRenderer {
|
|
3
4
|
render(statement) {
|
|
4
|
-
return `DROP TRIGGER IF EXISTS ${statement.name} ON ${statement.table}`;
|
|
5
|
+
return `DROP TRIGGER IF EXISTS ${PostgresDriver.quote(statement.name)} ON ${PostgresDriver.quote(statement.table)}`;
|
|
5
6
|
}
|
|
6
7
|
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { CreateIndexStatementTextRenderer, DropIndexStatementTextRenderer, DropTableStatementTextRenderer, type ForeignKeyStatement } from "@deterministic-code/datasources";
|
|
2
|
+
export declare class PostgresDropTableStatementRenderer extends DropTableStatementTextRenderer {
|
|
3
|
+
protected quote(name: string): string;
|
|
4
|
+
}
|
|
5
|
+
export declare class PostgresCreateIndexStatementRenderer extends CreateIndexStatementTextRenderer {
|
|
6
|
+
protected quote(name: string): string;
|
|
7
|
+
}
|
|
8
|
+
export declare class PostgresDropIndexStatementRenderer extends DropIndexStatementTextRenderer {
|
|
9
|
+
protected quote(name: string): string;
|
|
10
|
+
}
|
|
11
|
+
export declare class PostgresForeignKeyStatementRenderer {
|
|
12
|
+
render(statement: ForeignKeyStatement): string;
|
|
13
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { CreateIndexStatementTextRenderer, DropIndexStatementTextRenderer, DropTableStatementTextRenderer, } from "@deterministic-code/datasources";
|
|
2
|
+
import { PostgresDriver } from "./PostgresDriver.js";
|
|
3
|
+
export class PostgresDropTableStatementRenderer extends DropTableStatementTextRenderer {
|
|
4
|
+
quote(name) {
|
|
5
|
+
return PostgresDriver.quote(name);
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
export class PostgresCreateIndexStatementRenderer extends CreateIndexStatementTextRenderer {
|
|
9
|
+
quote(name) {
|
|
10
|
+
return PostgresDriver.quote(name);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
export class PostgresDropIndexStatementRenderer extends DropIndexStatementTextRenderer {
|
|
14
|
+
quote(name) {
|
|
15
|
+
return PostgresDriver.quote(name);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
export class PostgresForeignKeyStatementRenderer {
|
|
19
|
+
render(statement) {
|
|
20
|
+
const table = PostgresDriver.quote(statement.table);
|
|
21
|
+
const name = PostgresDriver.quote(statement.name);
|
|
22
|
+
if (statement.kind === "drop")
|
|
23
|
+
return `ALTER TABLE ${table} DROP CONSTRAINT ${name}`;
|
|
24
|
+
const cols = statement.columns.map(PostgresDriver.quote).join(", ");
|
|
25
|
+
const refs = statement.referencesColumns.map(PostgresDriver.quote).join(", ");
|
|
26
|
+
return `ALTER TABLE ${table} ADD CONSTRAINT ${name} FOREIGN KEY (${cols}) REFERENCES ${PostgresDriver.quote(statement.referencesTable)} (${refs})`;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
const quote = (ident) => `"${ident}"`;
|
|
2
|
+
const quoteString = (value) => `'${String(value).replace(/'/g, "''")}'`;
|
|
3
|
+
const truthy = (value) => value === true || value === "true" || value === "True" || value === 1;
|
|
4
|
+
const numeric = new Set([
|
|
5
|
+
"integer",
|
|
6
|
+
"unsignedinteger",
|
|
7
|
+
"smallinteger",
|
|
8
|
+
"unsignedsmallinteger",
|
|
9
|
+
"biginteger",
|
|
10
|
+
"unsignedbiginteger",
|
|
11
|
+
"reference",
|
|
12
|
+
"float",
|
|
13
|
+
"decimal",
|
|
14
|
+
]);
|
|
15
|
+
const defaultSql = (type, def) => {
|
|
16
|
+
const text = String(def);
|
|
17
|
+
if (type === "datetime" && text === "UtcNow")
|
|
18
|
+
return "(NOW() AT TIME ZONE 'UTC')";
|
|
19
|
+
if (type === "datetime" && text === "Now")
|
|
20
|
+
return "LOCALTIMESTAMP";
|
|
21
|
+
if (type === "uuid" && text === "NewId")
|
|
22
|
+
return "gen_random_uuid()";
|
|
23
|
+
if (type === "boolean")
|
|
24
|
+
return text === "true" || text === "True" ? "TRUE" : "FALSE";
|
|
25
|
+
return quoteString(text);
|
|
26
|
+
};
|
|
27
|
+
const literal = (column) => {
|
|
28
|
+
if (column.value == null)
|
|
29
|
+
return column.field.defaultValue != null ? defaultSql(column.field.type, column.field.defaultValue) : "NULL";
|
|
30
|
+
if (column.field.type === "boolean")
|
|
31
|
+
return truthy(column.value) ? "TRUE" : "FALSE";
|
|
32
|
+
if (numeric.has(column.field.type))
|
|
33
|
+
return String(column.value);
|
|
34
|
+
if (column.field.type === "binary")
|
|
35
|
+
return `'\\x${column.value}'`;
|
|
36
|
+
return quoteString(column.value);
|
|
37
|
+
};
|
|
38
|
+
export class PostgresSqlSeedStatementRenderer {
|
|
39
|
+
render(statement) {
|
|
40
|
+
const table = quote(statement.table);
|
|
41
|
+
if (statement.kind === "delete") {
|
|
42
|
+
const key = statement.columns[0];
|
|
43
|
+
return `DELETE FROM ${table} WHERE ${quote(key.column)} = ${literal(key)}`;
|
|
44
|
+
}
|
|
45
|
+
const cols = statement.columns.map((c) => quote(c.column)).join(", ");
|
|
46
|
+
const values = statement.columns.map(literal).join(", ");
|
|
47
|
+
const insert = `INSERT INTO ${table} (${cols}) VALUES (${values})`;
|
|
48
|
+
if (!statement.identity || statement.idColumn == null)
|
|
49
|
+
return insert;
|
|
50
|
+
const id = statement.idColumn;
|
|
51
|
+
return `${insert};\nSELECT setval(pg_get_serial_sequence('${statement.table}', '${id}'), (SELECT MAX(${quote(id)}) FROM ${table}))`;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { AlterTableStatement, BulkUpdateStatement, CreateBulkCreateStoredProcedureStatement, CreateBulkDeleteStoredProcedureStatement, CreateBulkUpdateStoredProcedureStatement, CreateCreateStoredProcedureStatement, CreateDeleteStoredProcedureStatement, CreateNestedReadStoredProcedureStatement, CreateReadStoredProcedureStatement, CreateTableStatement, CreateTriggerStatement, CreateUpdateStoredProcedureStatement, DropRoutineStatement, DropTriggerStatement, IdentityInsertStatement, SelectInsertedIdStatement, StatementRenderer, type StatementTextRenderer } from "@deterministic-code/datasources";
|
|
1
|
+
import { AlterTableStatement, BulkUpdateStatement, CreateBulkCreateStoredProcedureStatement, CreateBulkDeleteStoredProcedureStatement, CreateBulkUpdateStoredProcedureStatement, CreateCreateStoredProcedureStatement, CreateDeleteStoredProcedureStatement, CreateNestedReadStoredProcedureStatement, CreateReadStoredProcedureStatement, CreateTableStatement, CreateTriggerStatement, CreateUpdateStoredProcedureStatement, CreateIndexStatement, DropIndexStatement, DropRoutineStatement, DropTableStatement, DropTriggerStatement, ForeignKeyStatement, IdentityInsertStatement, type ProcedureSource, SelectInsertedIdStatement, SqlSeedStatement, StatementRenderer, type StatementTextRenderer } from "@deterministic-code/datasources";
|
|
2
2
|
export declare class PostgresStatementRenderer extends StatementRenderer {
|
|
3
3
|
constructor(text: StatementTextRenderer);
|
|
4
|
+
procedures(source: ProcedureSource): import("@deterministic-code/datasources").IStatement[];
|
|
4
5
|
protected visitSelectInsertedId(inserted: SelectInsertedIdStatement): string;
|
|
5
6
|
protected visitCreateTable(create: CreateTableStatement): string;
|
|
6
7
|
protected visitAlterTable(alter: AlterTableStatement): string;
|
|
@@ -17,4 +18,9 @@ export declare class PostgresStatementRenderer extends StatementRenderer {
|
|
|
17
18
|
protected visitCreateBulkCreateStoredProcedureStatement(statement: CreateBulkCreateStoredProcedureStatement): string;
|
|
18
19
|
protected visitCreateBulkUpdateStoredProcedureStatement(statement: CreateBulkUpdateStoredProcedureStatement): string;
|
|
19
20
|
protected visitCreateBulkDeleteStoredProcedureStatement(statement: CreateBulkDeleteStoredProcedureStatement): string;
|
|
21
|
+
protected visitSqlSeed(seed: SqlSeedStatement): string;
|
|
22
|
+
protected visitDropTable(drop: DropTableStatement): string;
|
|
23
|
+
protected visitCreateIndex(index: CreateIndexStatement): string;
|
|
24
|
+
protected visitDropIndex(index: DropIndexStatement): string;
|
|
25
|
+
protected visitForeignKey(key: ForeignKeyStatement): string;
|
|
20
26
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { SelectInsertedIdStatementTextRenderer, StatementRenderer, } from "@deterministic-code/datasources";
|
|
1
|
+
import { sqlProcedureStatementsOf, SelectInsertedIdStatementTextRenderer, StatementRenderer, } from "@deterministic-code/datasources";
|
|
2
2
|
import { PostgresAlterTableStatementRenderer } from "./PostgresAlterTableStatementRenderer.js";
|
|
3
3
|
import { PostgresBulkUpdateStatementTextRenderer } from "./PostgresBulkUpdateStatementTextRenderer.js";
|
|
4
4
|
import { PostgresCreateTableStatementRenderer } from "./PostgresCreateTableStatementRenderer.js";
|
|
@@ -7,6 +7,8 @@ import { PostgresDropRoutineStatementRenderer } from "./PostgresDropRoutineState
|
|
|
7
7
|
import { PostgresStoredProcedureStatementRenderer } from "./PostgresStoredProcedureStatementRenderer.js";
|
|
8
8
|
import { PostgresDropTriggerStatementRenderer } from "./PostgresDropTriggerStatementRenderer.js";
|
|
9
9
|
import { PostgresIdentityInsertStatementRenderer } from "./PostgresIdentityInsertStatementRenderer.js";
|
|
10
|
+
import { PostgresSqlSeedStatementRenderer } from "./PostgresSqlSeedStatementRenderer.js";
|
|
11
|
+
import { PostgresCreateIndexStatementRenderer, PostgresDropIndexStatementRenderer, PostgresDropTableStatementRenderer, PostgresForeignKeyStatementRenderer, } from "./PostgresQuotedRenderers.js";
|
|
10
12
|
class PostgresSelectInsertedIdStatementTextRenderer extends SelectInsertedIdStatementTextRenderer {
|
|
11
13
|
render(statement) {
|
|
12
14
|
const { table, column } = statement;
|
|
@@ -17,6 +19,9 @@ export class PostgresStatementRenderer extends StatementRenderer {
|
|
|
17
19
|
constructor(text) {
|
|
18
20
|
super(text);
|
|
19
21
|
}
|
|
22
|
+
procedures(source) {
|
|
23
|
+
return sqlProcedureStatementsOf(source);
|
|
24
|
+
}
|
|
20
25
|
visitSelectInsertedId(inserted) {
|
|
21
26
|
return new PostgresSelectInsertedIdStatementTextRenderer().render(inserted);
|
|
22
27
|
}
|
|
@@ -65,4 +70,19 @@ export class PostgresStatementRenderer extends StatementRenderer {
|
|
|
65
70
|
visitCreateBulkDeleteStoredProcedureStatement(statement) {
|
|
66
71
|
return new PostgresStoredProcedureStatementRenderer().renderBulkDelete(statement);
|
|
67
72
|
}
|
|
73
|
+
visitSqlSeed(seed) {
|
|
74
|
+
return new PostgresSqlSeedStatementRenderer().render(seed);
|
|
75
|
+
}
|
|
76
|
+
visitDropTable(drop) {
|
|
77
|
+
return new PostgresDropTableStatementRenderer().render(drop);
|
|
78
|
+
}
|
|
79
|
+
visitCreateIndex(index) {
|
|
80
|
+
return new PostgresCreateIndexStatementRenderer().render(index);
|
|
81
|
+
}
|
|
82
|
+
visitDropIndex(index) {
|
|
83
|
+
return new PostgresDropIndexStatementRenderer().render(index);
|
|
84
|
+
}
|
|
85
|
+
visitForeignKey(key) {
|
|
86
|
+
return new PostgresForeignKeyStatementRenderer().render(key);
|
|
87
|
+
}
|
|
68
88
|
}
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import { DefaultStatementTextRenderer } from "@deterministic-code/datasources";
|
|
2
|
-
|
|
3
|
-
const
|
|
2
|
+
import { PostgresDriver } from "./PostgresDriver.js";
|
|
3
|
+
const typeOf = (parameter) => PostgresDriver.nativeParam(parameter);
|
|
4
|
+
const params = (list) => list.map((p) => ` ${p.name} ${typeOf(p)}`).join(",\n");
|
|
5
|
+
const recordset = (columns) => columns.map((c) => `${c.name} ${typeOf(c)}`).join(", ");
|
|
4
6
|
export class PostgresStoredProcedureStatementRenderer {
|
|
5
7
|
renderCreate(statement) {
|
|
6
8
|
return `CREATE OR REPLACE FUNCTION ${statement.name}(
|
|
7
9
|
${params(statement.parameters)}
|
|
8
|
-
) RETURNS ${statement.keyType} LANGUAGE plpgsql AS $$
|
|
10
|
+
) RETURNS ${PostgresDriver.nativeSpec(statement.keyType)} LANGUAGE plpgsql AS $$
|
|
9
11
|
DECLARE
|
|
10
|
-
new_id ${statement.keyType};
|
|
12
|
+
new_id ${PostgresDriver.nativeSpec(statement.keyType)};
|
|
11
13
|
BEGIN
|
|
12
14
|
INSERT INTO ${statement.table} (${statement.parameters.map((p) => p.name).join(", ")})
|
|
13
15
|
VALUES (${statement.parameters.map((p) => p.name).join(", ")})
|
|
@@ -19,7 +21,7 @@ $$`;
|
|
|
19
21
|
renderNestedRead(statement) {
|
|
20
22
|
const select = DefaultStatementTextRenderer.instance.render(statement.select);
|
|
21
23
|
const signature = statement.arguments
|
|
22
|
-
.map((argument) => `${argument.parameter.name} ${argument.parameter
|
|
24
|
+
.map((argument) => `${argument.parameter.name} ${typeOf(argument.parameter)}`)
|
|
23
25
|
.join(", ");
|
|
24
26
|
return `CREATE OR REPLACE FUNCTION ${statement.name}(${signature}) RETURNS SETOF RECORD
|
|
25
27
|
LANGUAGE sql AS $$ ${select}; $$`;
|
|
@@ -31,7 +33,7 @@ LANGUAGE sql AS $$ ${select}; $$`;
|
|
|
31
33
|
LANGUAGE sql AS $$ SELECT ${aliased} FROM ${statement.table} t ORDER BY t.${statement.key}; $$`;
|
|
32
34
|
}
|
|
33
35
|
const signature = statement.byProperties
|
|
34
|
-
.map((c) => `${c} ${statement.parameters.find((p) => p.name === c)
|
|
36
|
+
.map((c) => `${c} ${typeOf(statement.parameters.find((p) => p.name === c) ?? { name: c, type: statement.keyType })}`)
|
|
35
37
|
.join(", ");
|
|
36
38
|
const pred = statement.byProperties
|
|
37
39
|
.map((c) => `t.${c} = ${statement.name}.${c}`)
|
|
@@ -66,7 +68,7 @@ $$`;
|
|
|
66
68
|
renderDelete(statement) {
|
|
67
69
|
const by = statement.byProperties.length > 0 ? statement.byProperties : [statement.key];
|
|
68
70
|
const signature = by
|
|
69
|
-
.map((c) => `${c} ${statement.parameters.find((p) => p.name === c)
|
|
71
|
+
.map((c) => `${c} ${typeOf(statement.parameters.find((p) => p.name === c) ?? { name: c, type: statement.keyType })}`)
|
|
70
72
|
.join(", ");
|
|
71
73
|
const pred = by.map((c) => `t.${c} = ${statement.name}.${c}`).join(" AND ");
|
|
72
74
|
return `CREATE OR REPLACE FUNCTION ${statement.name}(${signature}) RETURNS INT LANGUAGE plpgsql AS $$
|
|
@@ -113,7 +115,7 @@ $$`;
|
|
|
113
115
|
return `CREATE OR REPLACE PROCEDURE ${statement.name}(${statement.payload.name} jsonb)
|
|
114
116
|
LANGUAGE plpgsql AS $$
|
|
115
117
|
BEGIN
|
|
116
|
-
DELETE FROM ${statement.table} WHERE ${statement.key} IN (SELECT ${statement.key} FROM jsonb_to_recordset(${statement.payload.name}) AS x(${statement.key} ${statement.keyType}));
|
|
118
|
+
DELETE FROM ${statement.table} WHERE ${statement.key} IN (SELECT ${statement.key} FROM jsonb_to_recordset(${statement.payload.name}) AS x(${statement.key} ${PostgresDriver.nativeSpec(statement.keyType)}));
|
|
117
119
|
END;
|
|
118
120
|
$$`;
|
|
119
121
|
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { IMapping, SpecField } from "@deterministic-code/datasources";
|
|
2
|
+
export declare class TypeMap implements IMapping<string, string> {
|
|
3
|
+
readonly dictionary: Record<string, string>;
|
|
4
|
+
private readonly reverse;
|
|
5
|
+
constructor(dictionary: Record<string, string>);
|
|
6
|
+
convert: (from: string) => string;
|
|
7
|
+
convertField: (field: SpecField) => string;
|
|
8
|
+
reverseConvert: (native: string) => string;
|
|
9
|
+
}
|
package/dist/TypeMap.js
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export class TypeMap {
|
|
2
|
+
dictionary;
|
|
3
|
+
reverse = new Map();
|
|
4
|
+
constructor(dictionary) {
|
|
5
|
+
this.dictionary = dictionary;
|
|
6
|
+
for (const [spec, native] of Object.entries(dictionary)) {
|
|
7
|
+
if (!this.reverse.has(native))
|
|
8
|
+
this.reverse.set(native, spec);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
convert = (from) => this.dictionary[from] ??
|
|
12
|
+
(() => {
|
|
13
|
+
throw new Error(`Unknown spec field type: ${from}`);
|
|
14
|
+
})();
|
|
15
|
+
convertField = (field) => {
|
|
16
|
+
if (field.type === "string")
|
|
17
|
+
return field.size == null || field.size === "unlimited" ? "TEXT" : `VARCHAR(${field.size})`;
|
|
18
|
+
if (field.type === "character")
|
|
19
|
+
return `CHAR(${field.size == null ? 1 : Number(field.size)})`;
|
|
20
|
+
if (field.type === "decimal") {
|
|
21
|
+
if (field.size == null)
|
|
22
|
+
return "NUMERIC";
|
|
23
|
+
if (Array.isArray(field.size) && field.size.length === 2)
|
|
24
|
+
return `NUMERIC(${field.size[0]}, ${field.size[1]})`;
|
|
25
|
+
throw new Error(`decimal field "${field.name}" size must be [precision, scale] tuple; got ${field.size}`);
|
|
26
|
+
}
|
|
27
|
+
return this.convert(field.type);
|
|
28
|
+
};
|
|
29
|
+
reverseConvert = (native) => this.reverse.get(native) ??
|
|
30
|
+
(() => {
|
|
31
|
+
throw new Error(`Unknown native field type: ${native}`);
|
|
32
|
+
})();
|
|
33
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const applyKeywords: (sql: string, keyword: (word: string) => string) => string;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
const KEYWORDS = new Set([
|
|
2
|
+
"AFTER", "ALTER", "AND", "AS", "AT", "BEFORE", "BEGIN", "BETWEEN", "BIGINT", "BIGSERIAL",
|
|
3
|
+
"BOOLEAN", "BY", "BYTEA", "CASCADE", "CAST", "CHAR", "CONSTRAINT", "CREATE", "DECIMAL",
|
|
4
|
+
"DECLARE", "DEFAULT", "DELETE", "DIAGNOSTICS", "DOUBLE", "DROP", "EACH", "ELSE", "END",
|
|
5
|
+
"EXCEPTION", "EXECUTE", "EXISTS", "FALSE", "FLOAT", "FOR", "FOREIGN", "FROM", "FUNCTION",
|
|
6
|
+
"GENERATED", "GET", "IF", "IMMEDIATE", "IN", "INDEX", "INNER", "INSERT", "INT", "INTEGER",
|
|
7
|
+
"INTO", "JOIN", "KEY", "LANGUAGE", "NEW", "NOT", "NOW", "NULL", "NUMERIC", "OLD", "ON", "OR",
|
|
8
|
+
"ORDER", "OTHERS", "PRECISION", "PRIMARY", "PROCEDURE", "RAISE", "REAL", "REFERENCES",
|
|
9
|
+
"REPLACE", "RETURN", "RETURNING", "RETURNS", "ROW", "ROW_COUNT", "SELECT", "SERIAL", "SET",
|
|
10
|
+
"SETOF", "SMALLINT", "SQLCODE", "TABLE", "TEXT", "THEN", "TIME", "TIMESTAMP", "TIMESTAMPTZ",
|
|
11
|
+
"TRIGGER", "TRUE", "UNIQUE", "UPDATE", "UUID", "VALUES", "VARCHAR", "WHEN", "WHERE", "ZONE",
|
|
12
|
+
]);
|
|
13
|
+
const TOKEN = /('(?:''|[^'])*')|("(?:""|[^"])*")|(--[^\n]*)|(\/\*[\s\S]*?\*\/)|(?<word>[A-Za-z_][A-Za-z0-9_]*)/g;
|
|
14
|
+
export const applyKeywords = (sql, keyword) => sql.replace(TOKEN, (match, _s, _d, _lc, _bc, word) => word !== undefined && KEYWORDS.has(word.toUpperCase()) ? keyword(word) : match);
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export type ProcOp = "create" | "read" | "update" | "delete" | "bulkCreate" | "bulkUpdate" | "bulkDelete";
|
|
2
|
+
export type ProcSpec = {
|
|
3
|
+
op: ProcOp;
|
|
4
|
+
name: string;
|
|
5
|
+
byProperties: string[];
|
|
6
|
+
};
|
|
7
|
+
export declare const procedureSpecs: (entityName: string, byGroups: string[][], casing: {
|
|
8
|
+
routineName: (stem: string) => string;
|
|
9
|
+
pluralStem: (entity: string) => string;
|
|
10
|
+
}) => ProcSpec[];
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export const procedureSpecs = (entityName, byGroups, casing) => {
|
|
2
|
+
const name = (stem) => casing.routineName(stem);
|
|
3
|
+
const plural = casing.pluralStem(entityName);
|
|
4
|
+
const bySuffix = (props) => props.length === 0 ? "" : `_by_${props.join("_")}`;
|
|
5
|
+
return [
|
|
6
|
+
{ op: "create", name: name(`create_${plural}`), byProperties: [] },
|
|
7
|
+
{ op: "read", name: name(`read_${plural}`), byProperties: [] },
|
|
8
|
+
...byGroups.flatMap((props) => [
|
|
9
|
+
{ op: "read", name: name(`read_${plural}${bySuffix(props)}`), byProperties: props },
|
|
10
|
+
{ op: "update", name: name(`update_${plural}${bySuffix(props)}`), byProperties: props },
|
|
11
|
+
{ op: "delete", name: name(`delete_${plural}${bySuffix(props)}`), byProperties: props },
|
|
12
|
+
]),
|
|
13
|
+
{ op: "bulkCreate", name: name(`bulk_create_${plural}`), byProperties: [] },
|
|
14
|
+
{ op: "bulkUpdate", name: name(`bulk_update_${plural}`), byProperties: [] },
|
|
15
|
+
{ op: "bulkDelete", name: name(`bulk_delete_${plural}`), byProperties: [] },
|
|
16
|
+
];
|
|
17
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deterministic-code/datasources-postgres",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.30",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.js",
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
"access": "public"
|
|
19
19
|
},
|
|
20
20
|
"dependencies": {
|
|
21
|
-
"@deterministic-code/datasources": "^0.1.
|
|
21
|
+
"@deterministic-code/datasources": "^0.1.30",
|
|
22
22
|
"@types/pg": "^8.15.5",
|
|
23
23
|
"pg": "^8.16.0",
|
|
24
24
|
"pg-query-stream": "^4.17.0"
|