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