prostgles-server 2.0.183 → 2.0.186
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/DboBuilder.d.ts +1 -1
- package/dist/DboBuilder.d.ts.map +1 -1
- package/dist/DboBuilder.js +2 -1
- package/dist/DboBuilder.js.map +1 -1
- package/dist/PublishParser.d.ts +1 -1
- package/dist/PublishParser.d.ts.map +1 -1
- package/dist/PublishParser.js.map +1 -1
- package/dist/QueryBuilder.d.ts +7 -3
- package/dist/QueryBuilder.d.ts.map +1 -1
- package/dist/TableConfig.d.ts +24 -5
- package/dist/TableConfig.d.ts.map +1 -1
- package/dist/TableConfig.js +60 -21
- package/dist/TableConfig.js.map +1 -1
- package/lib/DboBuilder.d.ts +1 -1
- package/lib/DboBuilder.d.ts.map +1 -1
- package/lib/DboBuilder.js +2 -1
- package/lib/DboBuilder.ts +3 -2
- package/lib/PublishParser.d.ts +1 -1
- package/lib/PublishParser.d.ts.map +1 -1
- package/lib/PublishParser.ts +2 -2
- package/lib/TableConfig.d.ts +24 -5
- package/lib/TableConfig.d.ts.map +1 -1
- package/lib/TableConfig.js +60 -21
- package/lib/TableConfig.ts +376 -323
- package/package.json +2 -2
- package/tests/client/PID.txt +1 -1
- package/tests/client/package-lock.json +15 -15
- package/tests/client/package.json +1 -1
- package/tests/server/package-lock.json +3 -3
package/lib/TableConfig.ts
CHANGED
|
@@ -1,63 +1,90 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { doesNotMatch } from "assert";
|
|
2
|
+
import { getKeys, asName, AnyObject, TableInfo } from "prostgles-types";
|
|
3
|
+
import { DboBuilder, isPlainObject, JoinInfo } from "./DboBuilder";
|
|
3
4
|
import { ALLOWED_EXTENSION, ALLOWED_CONTENT_TYPE } from "./FileManager";
|
|
4
5
|
import { DB, DBHandlerServer, Prostgles } from "./Prostgles";
|
|
5
6
|
import { asValue } from "./PubSubManager";
|
|
6
7
|
|
|
7
8
|
type ColExtraInfo = {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
min?: string | number;
|
|
10
|
+
max?: string | number;
|
|
11
|
+
hint?: string;
|
|
11
12
|
};
|
|
12
13
|
|
|
13
|
-
type
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
export type I18N_Config<LANG_IDS> = {
|
|
15
|
+
[lang_id in keyof LANG_IDS]: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const parseI18N = <LANG_IDS, Def extends string | undefined>(params: {
|
|
19
|
+
config?: I18N_Config<LANG_IDS> | string;
|
|
20
|
+
lang?: keyof LANG_IDS | string;
|
|
21
|
+
defaultLang: keyof LANG_IDS | string;
|
|
22
|
+
defaultValue: Def;
|
|
23
|
+
}): Def | string => {
|
|
24
|
+
const { config, lang, defaultLang, defaultValue } = params;
|
|
25
|
+
if(config){
|
|
26
|
+
if(isPlainObject(config)){
|
|
27
|
+
//@ts-ignore
|
|
28
|
+
return config[lang] ?? config[defaultLang];
|
|
29
|
+
} else if(typeof config === "string"){
|
|
30
|
+
return config;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return defaultValue;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
type BaseTableDefinition<LANG_IDS = AnyObject> = {
|
|
38
|
+
info?: {
|
|
39
|
+
label?: string | I18N_Config<LANG_IDS>;
|
|
40
|
+
}
|
|
41
|
+
dropIfExistsCascade?: boolean;
|
|
42
|
+
dropIfExists?: boolean;
|
|
16
43
|
}
|
|
17
44
|
|
|
18
45
|
type LookupTableDefinition<LANG_IDS> = {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
}
|
|
46
|
+
isLookupTable: {
|
|
47
|
+
values: {
|
|
48
|
+
[id_value: string]: {} | {
|
|
49
|
+
[lang_id in keyof LANG_IDS]: string
|
|
50
|
+
}
|
|
25
51
|
}
|
|
52
|
+
}
|
|
26
53
|
}
|
|
27
54
|
|
|
28
55
|
type BaseColumn<LANG_IDS> = {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
56
|
+
/**
|
|
57
|
+
* Will add these values to .getColumns() result
|
|
58
|
+
*/
|
|
59
|
+
info?: ColExtraInfo;
|
|
33
60
|
|
|
34
|
-
|
|
61
|
+
label?: string | Partial<{ [lang_id in keyof LANG_IDS]: string; }>;
|
|
35
62
|
}
|
|
36
63
|
|
|
37
64
|
type SQLDefColumn = {
|
|
38
65
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
66
|
+
/**
|
|
67
|
+
* Raw sql statement used in creating/adding column
|
|
68
|
+
*/
|
|
69
|
+
sqlDefinition?: string;
|
|
43
70
|
}
|
|
44
71
|
|
|
45
72
|
type TextColDef = {
|
|
46
|
-
|
|
47
|
-
|
|
73
|
+
defaultValue?: string;
|
|
74
|
+
nullable?: boolean;
|
|
48
75
|
}
|
|
49
76
|
|
|
50
77
|
type TextColumn = TextColDef & {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
78
|
+
isText: true;
|
|
79
|
+
/**
|
|
80
|
+
* Value will be trimmed before update/insert
|
|
81
|
+
*/
|
|
82
|
+
trimmed?: boolean;
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Value will be lower cased before update/insert
|
|
86
|
+
*/
|
|
87
|
+
lowerCased?: boolean;
|
|
61
88
|
}
|
|
62
89
|
|
|
63
90
|
/**
|
|
@@ -65,364 +92,390 @@ type TextColumn = TextColDef & {
|
|
|
65
92
|
* Requires this table to have a primary key AND a valid fileTable config
|
|
66
93
|
*/
|
|
67
94
|
type MediaColumn = ({
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
95
|
+
|
|
96
|
+
name: string;
|
|
97
|
+
label?: string;
|
|
98
|
+
files: "one" | "many";
|
|
72
99
|
} & (
|
|
73
100
|
{
|
|
74
101
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
102
|
+
/**
|
|
103
|
+
* https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/accept
|
|
104
|
+
*/
|
|
105
|
+
allowedContentType?: Record<Partial<("audio/*" | "video/*" | "image/*" | "text/*" | ALLOWED_CONTENT_TYPE)>, 1>
|
|
79
106
|
} |
|
|
80
|
-
{
|
|
81
|
-
|
|
107
|
+
{
|
|
108
|
+
allowedExtensions?: Record<Partial<ALLOWED_EXTENSION>, 1>
|
|
82
109
|
}
|
|
83
|
-
));
|
|
110
|
+
));
|
|
84
111
|
|
|
85
112
|
type ReferencedColumn = {
|
|
86
113
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
114
|
+
/**
|
|
115
|
+
* Will create a lookup table that this column will reference
|
|
116
|
+
*/
|
|
117
|
+
references?: TextColDef & {
|
|
91
118
|
|
|
92
119
|
|
|
93
|
-
|
|
120
|
+
tableName: string;
|
|
94
121
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
122
|
+
/**
|
|
123
|
+
* Defaults to id
|
|
124
|
+
*/
|
|
125
|
+
columnName?: string;
|
|
126
|
+
}
|
|
100
127
|
}
|
|
101
128
|
|
|
102
129
|
type JoinDef = {
|
|
103
|
-
|
|
104
|
-
|
|
130
|
+
sourceTable: string;
|
|
131
|
+
targetTable: string;
|
|
105
132
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
133
|
+
/**
|
|
134
|
+
* E.g.: [sourceCol: string, targetCol: string][];
|
|
135
|
+
*/
|
|
136
|
+
on: [string, string][];
|
|
110
137
|
}
|
|
111
138
|
|
|
112
139
|
/**
|
|
113
140
|
* Used in specifying a join path to a table. This column name can then be used in select
|
|
114
141
|
*/
|
|
115
142
|
type NamedJoinColumn = {
|
|
116
|
-
|
|
117
|
-
|
|
143
|
+
label?: string;
|
|
144
|
+
joinDef: JoinDef[];
|
|
118
145
|
}
|
|
119
146
|
|
|
120
147
|
type ColumnConfig<LANG_IDS = { en: 1 }> = NamedJoinColumn | MediaColumn | (BaseColumn<LANG_IDS> & (SQLDefColumn | ReferencedColumn | TextColumn))
|
|
121
148
|
|
|
122
149
|
type TableDefinition<LANG_IDS> = {
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
}
|
|
150
|
+
columns?: {
|
|
151
|
+
[column_name: string]: ColumnConfig<LANG_IDS>
|
|
152
|
+
},
|
|
153
|
+
constraints?: {
|
|
154
|
+
[constraint_name: string]: string
|
|
155
|
+
},
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Similar to unique constraints but expressions are allowed inside definition
|
|
159
|
+
*/
|
|
160
|
+
replaceUniqueIndexes?: boolean;
|
|
161
|
+
indexes?: {
|
|
162
|
+
[index_name: string]: {
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Overrides replaceUniqueIndexes
|
|
166
|
+
*/
|
|
167
|
+
replace?: boolean;
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Causes the system to check for duplicate values in the table when the index is created (if data already exist) and each time data is added.
|
|
171
|
+
* Attempts to insert or update data which would result in duplicate entries will generate an error.
|
|
172
|
+
*/
|
|
173
|
+
unique?: boolean;
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* When this option is used, PostgreSQL will build the index without taking any locks that prevent
|
|
177
|
+
* concurrent inserts, updates, or deletes on the table; whereas a standard index build locks out writes (but not reads) on the table until it's done.
|
|
178
|
+
* There are several caveats to be aware of when using this option — see Building Indexes Concurrently.
|
|
179
|
+
*/
|
|
180
|
+
concurrently?: boolean;
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Table name
|
|
184
|
+
*/
|
|
185
|
+
// on?: string;
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Raw sql statement used excluding parentheses. e.g.: column_name
|
|
189
|
+
*/
|
|
190
|
+
definition: string;
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* The name of the index method to be used.
|
|
194
|
+
* Choices are btree, hash, gist, and gin. The default method is btree.
|
|
195
|
+
*/
|
|
196
|
+
using?: "btree" | "hash" | "gist" | "gin"
|
|
171
197
|
}
|
|
198
|
+
}
|
|
172
199
|
}
|
|
173
200
|
|
|
174
201
|
/**
|
|
175
202
|
* Helper utility to create lookup tables for TEXT columns
|
|
176
203
|
*/
|
|
177
204
|
export type TableConfig<LANG_IDS = { en: 1 }> = {
|
|
178
|
-
|
|
205
|
+
[table_name: string]: BaseTableDefinition<LANG_IDS> & (TableDefinition<LANG_IDS> | LookupTableDefinition<LANG_IDS>);
|
|
179
206
|
}
|
|
180
207
|
|
|
181
208
|
/**
|
|
182
209
|
* Will be run between initSQL and fileTable
|
|
183
210
|
*/
|
|
184
|
-
export default class TableConfigurator {
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
211
|
+
export default class TableConfigurator<LANG_IDS = { en: 1 }> {
|
|
212
|
+
|
|
213
|
+
config?: TableConfig<LANG_IDS>;
|
|
214
|
+
get dbo(): DBHandlerServer {
|
|
215
|
+
if (!this.prostgles.dbo) throw "this.prostgles.dbo missing"
|
|
216
|
+
return this.prostgles.dbo
|
|
217
|
+
};
|
|
218
|
+
get db(): DB {
|
|
219
|
+
if (!this.prostgles.db) throw "this.prostgles.db missing"
|
|
220
|
+
return this.prostgles.db
|
|
221
|
+
};
|
|
222
|
+
// sidKeyName: string;
|
|
223
|
+
prostgles: Prostgles
|
|
224
|
+
|
|
225
|
+
constructor(prostgles: Prostgles) {
|
|
226
|
+
this.config = prostgles.opts.tableConfig as any;
|
|
227
|
+
this.prostgles = prostgles;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
getColumnConfig = (tableName: string, colName: string): ColumnConfig | undefined => {
|
|
231
|
+
const tconf = this.config?.[tableName];
|
|
232
|
+
if (tconf && "columns" in tconf) {
|
|
233
|
+
return tconf.columns?.[colName];
|
|
201
234
|
}
|
|
235
|
+
return undefined;
|
|
236
|
+
}
|
|
202
237
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
return undefined;
|
|
238
|
+
getTableInfo = (params: { tableName: string; lang?: string }): TableInfo["info"] | undefined => {
|
|
239
|
+
const tconf = this.config?.[params.tableName];
|
|
240
|
+
|
|
241
|
+
return {
|
|
242
|
+
label: parseI18N<LANG_IDS, string>({ config: tconf?.info?.label, lang: params.lang, defaultLang: "en", defaultValue: params.tableName })
|
|
209
243
|
}
|
|
244
|
+
}
|
|
210
245
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
if("info" in colConf){
|
|
217
|
-
result = {
|
|
218
|
-
...(result ?? {}),
|
|
219
|
-
...colConf?.info
|
|
220
|
-
}
|
|
221
|
-
}
|
|
246
|
+
getColInfo = (params: { col: string, table: string, lang?: string }): (ColExtraInfo & { label?: string }) | undefined => {
|
|
247
|
+
const colConf = this.getColumnConfig(params.table, params.col);
|
|
248
|
+
let result: (ColExtraInfo & { label?: string }) | undefined = undefined;
|
|
249
|
+
if (colConf) {
|
|
222
250
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
251
|
+
if ("info" in colConf) {
|
|
252
|
+
result = {
|
|
253
|
+
...(result ?? {}),
|
|
254
|
+
...colConf?.info
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Get labels from TableConfig if specified
|
|
260
|
+
*/
|
|
261
|
+
if (colConf.label) {
|
|
262
|
+
const { lang } = params;
|
|
263
|
+
const lbl = colConf?.label;
|
|
264
|
+
if (["string", "object"].includes(typeof lbl)) {
|
|
265
|
+
if (typeof lbl === "string") {
|
|
266
|
+
result ??= {};
|
|
267
|
+
result.label = lbl
|
|
268
|
+
} else if (lang && (lbl?.[lang as "en"] || lbl?.en)) {
|
|
269
|
+
result ??= {};
|
|
270
|
+
result.label = (lbl?.[lang as "en"]) || lbl?.en;
|
|
271
|
+
}
|
|
240
272
|
}
|
|
241
273
|
|
|
242
|
-
|
|
243
|
-
return result;
|
|
274
|
+
}
|
|
244
275
|
}
|
|
245
276
|
|
|
246
|
-
checkColVal = (params: {col: string, table: string, value: any }): void => {
|
|
247
|
-
const conf = this.getColInfo(params);
|
|
248
|
-
if(conf){
|
|
249
|
-
const { value } = params;
|
|
250
|
-
const { min, max } = conf;
|
|
251
|
-
if(min !== undefined && value !== undefined && value < min) throw `${params.col} must be less than ${min}`
|
|
252
|
-
if(max !== undefined && value !== undefined && value > max) throw `${params.col} must be greater than ${max}`
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
277
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
278
|
+
return result;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
checkColVal = (params: { col: string, table: string, value: any }): void => {
|
|
282
|
+
const conf = this.getColInfo(params);
|
|
283
|
+
if (conf) {
|
|
284
|
+
const { value } = params;
|
|
285
|
+
const { min, max } = conf;
|
|
286
|
+
if (min !== undefined && value !== undefined && value < min) throw `${params.col} must be less than ${min}`
|
|
287
|
+
if (max !== undefined && value !== undefined && value > max) throw `${params.col} must be greater than ${max}`
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
getJoinInfo = (sourceTable: string, targetTable: string): JoinInfo | undefined => {
|
|
292
|
+
if (
|
|
293
|
+
this.config &&
|
|
294
|
+
sourceTable in this.config &&
|
|
295
|
+
this.config[sourceTable] &&
|
|
296
|
+
"columns" in this.config[sourceTable]
|
|
297
|
+
) {
|
|
298
|
+
const td = this.config[sourceTable];
|
|
299
|
+
if ("columns" in td && td.columns?.[targetTable]) {
|
|
300
|
+
const cd = td.columns[targetTable];
|
|
301
|
+
if ("joinDef" in cd) {
|
|
302
|
+
const { joinDef } = cd;
|
|
303
|
+
const res: JoinInfo = {
|
|
304
|
+
expectOne: false,
|
|
305
|
+
paths: joinDef.map(({ sourceTable, targetTable: table, on }) => ({
|
|
306
|
+
source: sourceTable,
|
|
307
|
+
target: targetTable,
|
|
308
|
+
table,
|
|
309
|
+
on
|
|
310
|
+
})),
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
return res;
|
|
281
314
|
}
|
|
282
|
-
|
|
315
|
+
}
|
|
283
316
|
}
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
317
|
+
return undefined;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
async init() {
|
|
321
|
+
let queries: string[] = [];
|
|
322
|
+
|
|
323
|
+
if (!this.config || !this.prostgles.pgp) throw "config or pgp missing"
|
|
324
|
+
/* Create lookup tables */
|
|
325
|
+
Object.keys(this.config).map(tableName => {
|
|
326
|
+
const tableConf = this.config![tableName];
|
|
327
|
+
const { dropIfExists = false, dropIfExistsCascade = false } = tableConf;
|
|
328
|
+
if (dropIfExistsCascade) {
|
|
329
|
+
queries.push(`DROP TABLE IF EXISTS ${tableName} CASCADE;`);
|
|
330
|
+
} else if (dropIfExists) {
|
|
331
|
+
queries.push(`DROP TABLE IF EXISTS ${tableName} ;`);
|
|
332
|
+
}
|
|
333
|
+
if ("isLookupTable" in tableConf && Object.keys(tableConf.isLookupTable?.values).length) {
|
|
334
|
+
const rows = Object.keys(tableConf.isLookupTable?.values).map(id => ({ id, ...(tableConf.isLookupTable?.values[id]) }));
|
|
335
|
+
if (dropIfExists || dropIfExistsCascade || !this.dbo?.[tableName]) {
|
|
336
|
+
const keys = Object.keys(rows[0]).filter(k => k !== "id");
|
|
337
|
+
queries.push(`CREATE TABLE IF NOT EXISTS ${tableName} (
|
|
303
338
|
id TEXT PRIMARY KEY
|
|
304
|
-
${keys.length? (", " + keys.map(k => asName(k) + " TEXT ").join(", ")) : ""}
|
|
339
|
+
${keys.length ? (", " + keys.map(k => asName(k) + " TEXT ").join(", ")) : ""}
|
|
305
340
|
);`);
|
|
306
341
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
342
|
+
rows.map(row => {
|
|
343
|
+
const values = this.prostgles.pgp!.helpers.values(row)
|
|
344
|
+
queries.push(this.prostgles.pgp!.as.format(`INSERT INTO ${tableName} (${["id", ...keys].map(t => asName(t)).join(", ")}) ` + " VALUES ${values:raw} ;", { values }))
|
|
345
|
+
});
|
|
346
|
+
// console.log("Created lookup table " + tableName)
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
if (queries.length) {
|
|
352
|
+
const q = queries.join("\n");
|
|
353
|
+
console.log("TableConfig: \n", q)
|
|
354
|
+
await this.db.multi(q);
|
|
355
|
+
await this.prostgles.refreshDBO()
|
|
356
|
+
}
|
|
357
|
+
queries = [];
|
|
358
|
+
|
|
359
|
+
/* Create referenced columns */
|
|
360
|
+
await Promise.all(Object.keys(this.config).map(async tableName => {
|
|
361
|
+
const tableConf = this.config![tableName];
|
|
362
|
+
if ("columns" in tableConf) {
|
|
363
|
+
const getColDef = (name: string, colConf: ColumnConfig): string => {
|
|
364
|
+
const colNameEsc = asName(name);
|
|
365
|
+
const getTextDef = (colConf: TextColDef) => {
|
|
366
|
+
const { nullable, defaultValue } = colConf;
|
|
367
|
+
return ` TEXT ${!nullable ? " NOT NULL " : ""} ${defaultValue ? ` DEFAULT ${asValue(defaultValue)} ` : ""}`
|
|
368
|
+
}
|
|
369
|
+
if ("references" in colConf && colConf.references) {
|
|
370
|
+
|
|
371
|
+
const { tableName: lookupTable, columnName: lookupCol = "id" } = colConf.references;
|
|
372
|
+
return ` ${colNameEsc} ${getTextDef(colConf.references)} REFERENCES ${lookupTable} (${lookupCol}) `;
|
|
373
|
+
|
|
374
|
+
} else if ("sqlDefinition" in colConf && colConf.sqlDefinition) {
|
|
375
|
+
|
|
376
|
+
return ` ${colNameEsc} ${colConf.sqlDefinition} `;
|
|
377
|
+
|
|
378
|
+
} else if ("isText" in colConf && colConf.isText) {
|
|
379
|
+
let checks = "", cArr = [];
|
|
380
|
+
if (colConf.lowerCased) {
|
|
381
|
+
cArr.push(`${colNameEsc} = LOWER(${colNameEsc})`)
|
|
313
382
|
}
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
383
|
+
if (colConf.trimmed) {
|
|
384
|
+
cArr.push(`${colNameEsc} = BTRIM(${colNameEsc})`)
|
|
385
|
+
}
|
|
386
|
+
if (cArr.length) {
|
|
387
|
+
checks = `CHECK (${cArr.join(" AND ")})`
|
|
388
|
+
}
|
|
389
|
+
return ` ${colNameEsc} ${getTextDef(colConf)} ${checks}`;
|
|
390
|
+
} else {
|
|
391
|
+
throw "Unknown column config: " + JSON.stringify(colConf);
|
|
392
|
+
}
|
|
321
393
|
}
|
|
322
|
-
queries = [];
|
|
323
|
-
|
|
324
|
-
/* Create referenced columns */
|
|
325
|
-
await Promise.all(Object.keys(this.config).map(async tableName => {
|
|
326
|
-
const tableConf = this.config![tableName];
|
|
327
|
-
if("columns" in tableConf){
|
|
328
|
-
const getColDef = (name: string, colConf: ColumnConfig): string => {
|
|
329
|
-
const colNameEsc = asName(name);
|
|
330
|
-
const getTextDef = (colConf: TextColDef) => {
|
|
331
|
-
const { nullable, defaultValue } = colConf;
|
|
332
|
-
return ` TEXT ${!nullable? " NOT NULL " : ""} ${defaultValue? ` DEFAULT ${asValue(defaultValue)} ` : "" }`
|
|
333
|
-
}
|
|
334
|
-
if("references" in colConf && colConf.references){
|
|
335
|
-
|
|
336
|
-
const { tableName: lookupTable, columnName: lookupCol = "id" } = colConf.references;
|
|
337
|
-
return ` ${colNameEsc} ${getTextDef(colConf.references)} REFERENCES ${lookupTable} (${lookupCol}) `;
|
|
338
|
-
|
|
339
|
-
} else if("sqlDefinition" in colConf && colConf.sqlDefinition){
|
|
340
|
-
|
|
341
|
-
return ` ${colNameEsc} ${colConf.sqlDefinition} `;
|
|
342
|
-
|
|
343
|
-
} else if("isText" in colConf && colConf.isText){
|
|
344
|
-
let checks = "", cArr = [];
|
|
345
|
-
if(colConf.lowerCased){
|
|
346
|
-
cArr.push(`${colNameEsc} = LOWER(${colNameEsc})`)
|
|
347
|
-
}
|
|
348
|
-
if(colConf.trimmed){
|
|
349
|
-
cArr.push(`${colNameEsc} = BTRIM(${colNameEsc})`)
|
|
350
|
-
}
|
|
351
|
-
if(cArr.length){
|
|
352
|
-
checks = `CHECK (${cArr.join(" AND ")})`
|
|
353
|
-
}
|
|
354
|
-
return ` ${colNameEsc} ${getTextDef(colConf)} ${checks}`;
|
|
355
|
-
} else {
|
|
356
|
-
throw "Unknown column config: " + JSON.stringify(colConf);
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
const colDefs: string[] = [];
|
|
361
|
-
Object.keys(tableConf.columns).filter(c => !("joinDef" in tableConf.columns[c])).map(colName => {
|
|
362
|
-
const colConf = tableConf.columns[colName];
|
|
363
|
-
|
|
364
|
-
if(!this.dbo[tableName]){
|
|
365
|
-
colDefs.push(getColDef(colName, colConf))
|
|
366
|
-
} else if(!colDefs.length && !this.dbo[tableName].columns?.find(c => colName === c.name)) {
|
|
367
|
-
|
|
368
|
-
if("references" in colConf && colConf.references){
|
|
369
|
-
|
|
370
|
-
const { tableName: lookupTable, } = colConf.references;
|
|
371
|
-
queries.push(`
|
|
372
|
-
ALTER TABLE ${asName(tableName)}
|
|
373
|
-
ADD COLUMN ${getColDef(colName, colConf)};
|
|
374
|
-
`)
|
|
375
|
-
console.log(`TableConfigurator: ${tableName}(${colName})` + " referenced lookup table " + lookupTable);
|
|
376
394
|
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
395
|
+
const colDefs: string[] = [];
|
|
396
|
+
if (tableConf.columns) {
|
|
397
|
+
getKeys(tableConf?.columns).filter(c => !("joinDef" in tableConf.columns![c])).map(colName => {
|
|
398
|
+
const colConf = tableConf.columns![colName];
|
|
399
|
+
|
|
400
|
+
if (!this.dbo[tableName]) {
|
|
401
|
+
colDefs.push(getColDef(colName, colConf))
|
|
402
|
+
} else if (!colDefs.length && !this.dbo[tableName].columns?.find(c => colName === c.name)) {
|
|
403
|
+
|
|
404
|
+
if ("references" in colConf && colConf.references) {
|
|
405
|
+
|
|
406
|
+
const { tableName: lookupTable, } = colConf.references;
|
|
407
|
+
queries.push(`
|
|
408
|
+
ALTER TABLE ${asName(tableName)}
|
|
409
|
+
ADD COLUMN ${getColDef(colName, colConf)};
|
|
410
|
+
`)
|
|
411
|
+
console.log(`TableConfigurator: ${tableName}(${colName})` + " referenced lookup table " + lookupTable);
|
|
412
|
+
|
|
413
|
+
} else if ("sqlDefinition" in colConf && colConf.sqlDefinition) {
|
|
414
|
+
|
|
415
|
+
queries.push(`
|
|
380
416
|
ALTER TABLE ${asName(tableName)}
|
|
381
417
|
ADD COLUMN ${getColDef(colName, colConf)};
|
|
382
418
|
`)
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
419
|
+
console.log(`TableConfigurator: created/added column ${tableName}(${colName}) ` + colConf.sqlDefinition)
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
});
|
|
423
|
+
}
|
|
387
424
|
|
|
388
|
-
|
|
389
|
-
|
|
425
|
+
if (colDefs.length) {
|
|
426
|
+
queries.push(`CREATE TABLE ${asName(tableName)} (
|
|
390
427
|
${colDefs.join(", \n")}
|
|
391
428
|
);`)
|
|
392
|
-
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
if("constraints" in tableConf && tableConf.constraints){
|
|
396
|
-
getKeys(tableConf.constraints).map(constraintName => {
|
|
397
|
-
queries.push(`ALTER TABLE ${asName(tableName)} ADD CONSTRAINT ${asName(constraintName)} ${tableConf.constraints![constraintName]} ;`);
|
|
398
|
-
});
|
|
399
|
-
}
|
|
400
|
-
if("indexes" in tableConf && tableConf.indexes){
|
|
401
|
-
getKeys(tableConf.indexes).map(indexName => {
|
|
402
|
-
const { concurrently, unique, using, definition, replace } = tableConf.indexes![indexName];
|
|
403
|
-
if(replace || typeof replace !== "boolean" && tableConf.replaceUniqueIndexes){
|
|
404
|
-
queries.push(`DROP INDEX IF EXISTS ${asName(indexName)} ;`);
|
|
405
|
-
}
|
|
406
|
-
queries.push(`CREATE ${unique? "UNIQUE" : ""} ${!concurrently? "" : "CONCURRENTLY"} INDEX ${asName(indexName)} ON ${asName(tableName)} ${!using? "" : ("USING " + using)} (${definition}) ;`);
|
|
407
|
-
});
|
|
408
|
-
}
|
|
409
|
-
}));
|
|
410
|
-
|
|
411
|
-
if(queries.length){
|
|
412
|
-
const q = queries.join("\n");
|
|
413
|
-
console.log("TableConfig: \n", q)
|
|
414
|
-
await this.db.multi(q);
|
|
429
|
+
console.error("TableConfigurator: Created table: \n" + queries[0])
|
|
415
430
|
}
|
|
431
|
+
}
|
|
432
|
+
if ("constraints" in tableConf && tableConf.constraints) {
|
|
433
|
+
const constraints = await getTableConstraings(this.db, tableName);
|
|
434
|
+
getKeys(tableConf.constraints).map(constraintName => {
|
|
435
|
+
if(!constraints.some(c => c.conname === constraintName)){
|
|
436
|
+
queries.push(`ALTER TABLE ${asName(tableName)} ADD CONSTRAINT ${asName(constraintName)} ${tableConf.constraints![constraintName]} ;`);
|
|
437
|
+
}
|
|
438
|
+
});
|
|
439
|
+
}
|
|
440
|
+
if ("indexes" in tableConf && tableConf.indexes) {
|
|
441
|
+
getKeys(tableConf.indexes).map(indexName => {
|
|
442
|
+
const { concurrently, unique, using, definition, replace } = tableConf.indexes![indexName];
|
|
443
|
+
if (replace || typeof replace !== "boolean" && tableConf.replaceUniqueIndexes) {
|
|
444
|
+
queries.push(`DROP INDEX IF EXISTS ${asName(indexName)} ;`);
|
|
445
|
+
}
|
|
446
|
+
queries.push(`CREATE ${unique ? "UNIQUE" : ""} ${!concurrently ? "" : "CONCURRENTLY"} INDEX ${asName(indexName)} ON ${asName(tableName)} ${!using ? "" : ("USING " + using)} (${definition}) ;`);
|
|
447
|
+
});
|
|
448
|
+
}
|
|
449
|
+
}));
|
|
450
|
+
|
|
451
|
+
if (queries.length) {
|
|
452
|
+
const q = queries.join("\n");
|
|
453
|
+
console.log("TableConfig: \n", q)
|
|
454
|
+
await this.db.multi(q);
|
|
416
455
|
}
|
|
456
|
+
}
|
|
417
457
|
}
|
|
418
458
|
|
|
419
459
|
|
|
420
|
-
async function columnExists(args: {tableName: string; colName: string; db: DB }){
|
|
421
|
-
|
|
422
|
-
|
|
460
|
+
async function columnExists(args: { tableName: string; colName: string; db: DB }) {
|
|
461
|
+
const { db, tableName, colName } = args;
|
|
462
|
+
return Boolean((await db.oneOrNone(`
|
|
423
463
|
SELECT column_name, table_name
|
|
424
464
|
FROM information_schema.columns
|
|
425
465
|
WHERE table_name=${asValue(tableName)} and column_name=${asValue(colName)}
|
|
426
466
|
LIMIT 1;
|
|
427
467
|
`))?.column_name);
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
function getTableConstraings(db: DB, tableName: string): Promise<{ oid: number; conname: string; definition: string; }[]>{
|
|
471
|
+
return db.any(`
|
|
472
|
+
SELECT con.*, pg_get_constraintdef(con.oid)
|
|
473
|
+
FROM pg_catalog.pg_constraint con
|
|
474
|
+
INNER JOIN pg_catalog.pg_class rel
|
|
475
|
+
ON rel.oid = con.conrelid
|
|
476
|
+
INNER JOIN pg_catalog.pg_namespace nsp
|
|
477
|
+
ON nsp.oid = connamespace
|
|
478
|
+
WHERE 1=1
|
|
479
|
+
AND nsp.nspname = current_schema
|
|
480
|
+
AND rel.relname = ` + "${tableName}", { tableName })
|
|
428
481
|
}
|