imodel-pg 0.1.0
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/index.d.mts +26 -0
- package/index.mjs +1248 -0
- package/package.json +14 -0
package/index.d.mts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* imodel v0.1.0
|
|
3
|
+
* (c) 2019-2025 undefined
|
|
4
|
+
* @license undefined
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import pg from 'pg';
|
|
8
|
+
import { IConnection } from 'imodel';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
*
|
|
12
|
+
* @param {pg.Pool | (() => pg.PoolClient | PromiseLike<pg.PoolClient>)} [pool]
|
|
13
|
+
* @param {(client?: pg.PoolClient?) => Promise<string>} [version]
|
|
14
|
+
* @returns {IConnection<PgEnvTrans>}
|
|
15
|
+
*/
|
|
16
|
+
declare function _default(pool?: pg.Pool | (() => pg.PoolClient | PromiseLike<pg.PoolClient>), version?: (client?: pg.PoolClient | null) => Promise<string>): IConnection<PgEnvTrans>;
|
|
17
|
+
/**
|
|
18
|
+
* @param {pg.PoolClient} client
|
|
19
|
+
* @returns {PgEnvTrans}
|
|
20
|
+
*/
|
|
21
|
+
declare function toTransaction(client: pg.PoolClient): PgEnvTrans;
|
|
22
|
+
type PgEnvTrans = {
|
|
23
|
+
client?: pg.PoolClient | null;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export { type PgEnvTrans, _default as default, toTransaction };
|
package/index.mjs
ADDED
|
@@ -0,0 +1,1248 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* imodel v0.1.0
|
|
3
|
+
* (c) 2019-2025 undefined
|
|
4
|
+
* @license undefined
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import pg from 'pg';
|
|
8
|
+
import Sql from 'tagged-sql';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
*
|
|
12
|
+
* @param {string} s
|
|
13
|
+
* @param {object} options
|
|
14
|
+
* @param {number} [options.precision] 精确度
|
|
15
|
+
* @param {number} [options.scale] 缩放比
|
|
16
|
+
* @param {number} [options.size] 占用空间大小
|
|
17
|
+
* @param {boolean} [define]
|
|
18
|
+
* @returns
|
|
19
|
+
*/
|
|
20
|
+
function getType(s, {
|
|
21
|
+
precision,
|
|
22
|
+
scale,
|
|
23
|
+
size
|
|
24
|
+
}, define) {
|
|
25
|
+
switch (s) {
|
|
26
|
+
case 'id':
|
|
27
|
+
return define ? 'bigserial' : 'bigint';
|
|
28
|
+
case 'rid':
|
|
29
|
+
return 'bigint';
|
|
30
|
+
case 'ref':
|
|
31
|
+
return 'string';
|
|
32
|
+
case 'i2':
|
|
33
|
+
case 'i4':
|
|
34
|
+
case 'i8':
|
|
35
|
+
case 'u2':
|
|
36
|
+
case 'u4':
|
|
37
|
+
case 'u8':
|
|
38
|
+
case 'ref16':
|
|
39
|
+
case 'i16':
|
|
40
|
+
case 'seq16':
|
|
41
|
+
return 'smallint';
|
|
42
|
+
case 'u16':
|
|
43
|
+
case 'i32':
|
|
44
|
+
case 'seq32':
|
|
45
|
+
return 'integer';
|
|
46
|
+
case 'u32':
|
|
47
|
+
case 'ref52':
|
|
48
|
+
case 'ref64':
|
|
49
|
+
case 'i52':
|
|
50
|
+
case 'i64':
|
|
51
|
+
case 'u64':
|
|
52
|
+
case 'seq64':
|
|
53
|
+
return 'bigint';
|
|
54
|
+
case 'f32':
|
|
55
|
+
return 'real';
|
|
56
|
+
case 'f64':
|
|
57
|
+
return 'double precision';
|
|
58
|
+
case 'decimal':
|
|
59
|
+
case 'numeric':
|
|
60
|
+
if (!precision) {
|
|
61
|
+
return s;
|
|
62
|
+
}
|
|
63
|
+
if (!scale) {
|
|
64
|
+
return `${s}(${precision})`;
|
|
65
|
+
}
|
|
66
|
+
return `${s}(${precision}, ${scale})`;
|
|
67
|
+
case 'char':
|
|
68
|
+
return size ? `char(${size})` : `char`;
|
|
69
|
+
case 'string':
|
|
70
|
+
return size ? `varchar(${size || 255})` : `varchar`;
|
|
71
|
+
case 'text':
|
|
72
|
+
return 'text';
|
|
73
|
+
case 'bin':
|
|
74
|
+
return 'bytea';
|
|
75
|
+
case 'timestamp':
|
|
76
|
+
{
|
|
77
|
+
if (!precision) {
|
|
78
|
+
return `timestamp with time zone`;
|
|
79
|
+
}
|
|
80
|
+
return `timestamp(${precision}) with time zone`;
|
|
81
|
+
}
|
|
82
|
+
case 'date':
|
|
83
|
+
return 'date';
|
|
84
|
+
case 'datetime':
|
|
85
|
+
{
|
|
86
|
+
if (!precision) {
|
|
87
|
+
return `datetime without time zone`;
|
|
88
|
+
}
|
|
89
|
+
return `datetime(${precision}) without time zone`;
|
|
90
|
+
}
|
|
91
|
+
case 'time':
|
|
92
|
+
{
|
|
93
|
+
if (!precision) {
|
|
94
|
+
return `time without time zone`;
|
|
95
|
+
}
|
|
96
|
+
return `time(${precision}) without time zone`;
|
|
97
|
+
}
|
|
98
|
+
case 'timetz':
|
|
99
|
+
{
|
|
100
|
+
if (!precision) {
|
|
101
|
+
return `time with time zone`;
|
|
102
|
+
}
|
|
103
|
+
return `time(${precision}) with time zone`;
|
|
104
|
+
}
|
|
105
|
+
case 'interval':
|
|
106
|
+
{
|
|
107
|
+
if (!precision) {
|
|
108
|
+
return 'interval';
|
|
109
|
+
}
|
|
110
|
+
return `interval(${precision})`;
|
|
111
|
+
}
|
|
112
|
+
case 'bool':
|
|
113
|
+
return 'boolean';
|
|
114
|
+
case 'ip':
|
|
115
|
+
case 'ipv4':
|
|
116
|
+
case 'ipv6':
|
|
117
|
+
return 'cidr';
|
|
118
|
+
case 'mac48':
|
|
119
|
+
return 'macaddr';
|
|
120
|
+
case 'mac64':
|
|
121
|
+
return 'macaddr8';
|
|
122
|
+
case 'uuid':
|
|
123
|
+
return 'uuid';
|
|
124
|
+
case 'object':
|
|
125
|
+
return 'jsonb';
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** @import { Environment, ColumnOptions } from 'imodel' */
|
|
130
|
+
/**
|
|
131
|
+
*
|
|
132
|
+
* @param {any} v
|
|
133
|
+
* @param {string} type
|
|
134
|
+
* @returns
|
|
135
|
+
*/
|
|
136
|
+
function toSqlString(v, type) {
|
|
137
|
+
if (typeof v === 'bigint' || typeof v === 'number') {
|
|
138
|
+
return String(v);
|
|
139
|
+
}
|
|
140
|
+
if (typeof v === 'string') {
|
|
141
|
+
return `'${v.replace(/\\/g, '\\\\').replace(/'/g, '\\\'')}'`;
|
|
142
|
+
}
|
|
143
|
+
if (v === true) {
|
|
144
|
+
return 'TRUE';
|
|
145
|
+
}
|
|
146
|
+
if (v === false) {
|
|
147
|
+
return 'FALSE';
|
|
148
|
+
}
|
|
149
|
+
if (v instanceof Date) {
|
|
150
|
+
return `'${v.toISOString()}'`;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
*
|
|
155
|
+
* @param {Environment} env
|
|
156
|
+
* @param {any} value
|
|
157
|
+
* @param {string} type
|
|
158
|
+
* @param {boolean} [array]
|
|
159
|
+
* @returns
|
|
160
|
+
*/
|
|
161
|
+
function getDefault(env, value, type, array) {
|
|
162
|
+
if (value === undefined) {
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
if (value === env.values.now) {
|
|
166
|
+
return Sql`now()`;
|
|
167
|
+
}
|
|
168
|
+
const v = typeof value === 'function' ? value() : value;
|
|
169
|
+
if (v === undefined) {
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
if (v instanceof Sql) {
|
|
173
|
+
return v;
|
|
174
|
+
}
|
|
175
|
+
if (v === null) {
|
|
176
|
+
return Sql`NULL`;
|
|
177
|
+
}
|
|
178
|
+
if (array) {
|
|
179
|
+
const values = Array.isArray(v) ? v : [v];
|
|
180
|
+
const t = values.map(v => toSqlString(v)).filter(Boolean).join(',');
|
|
181
|
+
return Sql(`'{${t.replace(/\\/g, '\\\\').replace(/'/g, '\\\'')}}'`);
|
|
182
|
+
}
|
|
183
|
+
const t = toSqlString(v);
|
|
184
|
+
if (t) {
|
|
185
|
+
return Sql(t);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
*
|
|
190
|
+
* @param {string} name
|
|
191
|
+
* @param {string} type
|
|
192
|
+
* @param {ColumnOptions<any>} columnOptions
|
|
193
|
+
* @param {Environment} env
|
|
194
|
+
* @returns {Sql}
|
|
195
|
+
*/
|
|
196
|
+
function add(name, type, {
|
|
197
|
+
unique,
|
|
198
|
+
nullable = true,
|
|
199
|
+
default: defaultValue,
|
|
200
|
+
array,
|
|
201
|
+
precision,
|
|
202
|
+
scale,
|
|
203
|
+
size,
|
|
204
|
+
serial
|
|
205
|
+
}, env) {
|
|
206
|
+
const t = getType(type, {
|
|
207
|
+
precision,
|
|
208
|
+
scale,
|
|
209
|
+
size
|
|
210
|
+
}, serial);
|
|
211
|
+
const defValueSql = getDefault(env, defaultValue, type, array);
|
|
212
|
+
return Sql(Sql`ADD COLUMN ${Sql.Field(name)}`, Sql(`${t}${array ? '[]' : ''}`), nullable ? Sql`` : Sql`NOT NULL`, defValueSql ? Sql`DEFAULT ${defValueSql}` : Sql``);
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
*
|
|
216
|
+
* @param {string} column
|
|
217
|
+
* @param {string} newName
|
|
218
|
+
* @returns {Sql}
|
|
219
|
+
*/
|
|
220
|
+
function rename(column, newName) {
|
|
221
|
+
return Sql`RENAME COLUMN ${Sql.Field(column)} TO ${Sql.Field(newName)}`;
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
*
|
|
225
|
+
* @param {string} column
|
|
226
|
+
* @returns {Sql}
|
|
227
|
+
*/
|
|
228
|
+
function drop(column) {
|
|
229
|
+
return Sql`DROP COLUMN ${Sql.Table(column)}`;
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
*
|
|
233
|
+
* @param {string} name
|
|
234
|
+
* @param {string} type
|
|
235
|
+
* @param {object} options
|
|
236
|
+
* @param {boolean} [options.array] 是否为数组格式
|
|
237
|
+
* @param {number} [options.precision] 精确度
|
|
238
|
+
* @param {number} [options.scale] 缩放比
|
|
239
|
+
* @param {number} [options.size] 占用空间大小
|
|
240
|
+
* @returns
|
|
241
|
+
*/
|
|
242
|
+
function changeType(name, type, {
|
|
243
|
+
precision,
|
|
244
|
+
scale,
|
|
245
|
+
size,
|
|
246
|
+
array
|
|
247
|
+
}) {
|
|
248
|
+
const t = getType(type, {
|
|
249
|
+
precision,
|
|
250
|
+
scale,
|
|
251
|
+
size
|
|
252
|
+
});
|
|
253
|
+
const field = Sql.Field(name);
|
|
254
|
+
return Sql`ALTER COLUMN ${field} TYPE ${Sql(`${t}${array ? '[]' : ''}`)}`;
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
*
|
|
258
|
+
* @param {string} name
|
|
259
|
+
* @param {boolean} [nullable]
|
|
260
|
+
* @returns
|
|
261
|
+
*/
|
|
262
|
+
function changeNull(name, nullable) {
|
|
263
|
+
return Sql(Sql`ALTER COLUMN ${Sql.Field(name)}`, nullable ? Sql`DROP NOT NULL` : Sql`SET NOT NULL`);
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
*
|
|
267
|
+
* @param {Environment} env
|
|
268
|
+
* @param {string} name
|
|
269
|
+
* @param {any} defValue
|
|
270
|
+
* @param {string} type
|
|
271
|
+
* @param {boolean} [array]
|
|
272
|
+
* @returns
|
|
273
|
+
*/
|
|
274
|
+
function changeDefault(env, name, defValue, type, array) {
|
|
275
|
+
const v = getDefault(env, defValue, type, array);
|
|
276
|
+
return Sql(Sql`ALTER COLUMN ${Sql.Field(name)}`, v ? Sql`DEFAULT ${v}` : Sql` DROP DEFAULT`);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/** @import { Environment, ColumnOptions } from 'imodel' */
|
|
280
|
+
/**
|
|
281
|
+
*
|
|
282
|
+
* @param {string} table
|
|
283
|
+
* @param {string} name
|
|
284
|
+
* @param {string} type
|
|
285
|
+
* @param {ColumnOptions<any>} options
|
|
286
|
+
* @param {Environment} env
|
|
287
|
+
* @returns {Sql}
|
|
288
|
+
*/
|
|
289
|
+
function addColumn(table, name, type, options, env) {
|
|
290
|
+
return Sql`ALTER TABLE ${Sql.Table(table)} ${add(name, type, options, env)}`;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/** @import { Environment, ColumnOptions } from 'imodel' */
|
|
294
|
+
/**
|
|
295
|
+
*
|
|
296
|
+
* @param {string} table
|
|
297
|
+
* @param {string} name
|
|
298
|
+
* @param {string} type
|
|
299
|
+
* @param {ColumnOptions<any>} columnOptions
|
|
300
|
+
* @param {Environment} env
|
|
301
|
+
* @returns {Sql}
|
|
302
|
+
*/
|
|
303
|
+
function changeColumn(table, name, type, {
|
|
304
|
+
nullable,
|
|
305
|
+
default: defValue,
|
|
306
|
+
array,
|
|
307
|
+
precision,
|
|
308
|
+
scale,
|
|
309
|
+
size
|
|
310
|
+
}, env) {
|
|
311
|
+
return Sql(Sql`ALTER TABLE ${Sql.Table(table)}`, changeType(name, type, {
|
|
312
|
+
precision,
|
|
313
|
+
scale,
|
|
314
|
+
size,
|
|
315
|
+
array
|
|
316
|
+
}), changeNull(name, nullable), changeDefault(env, name, defValue, type, array));
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
*
|
|
321
|
+
* @param {string} table
|
|
322
|
+
* @param {string} name
|
|
323
|
+
* @param {boolean} nullable
|
|
324
|
+
* @returns {Sql}
|
|
325
|
+
*/
|
|
326
|
+
function changeColumnNull(table, name, nullable) {
|
|
327
|
+
return Sql(Sql`ALTER TABLE ${Sql.Table(table)}`, changeNull(name, nullable));
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
*
|
|
332
|
+
* @param {string} table
|
|
333
|
+
* @param {string} column
|
|
334
|
+
* @returns {Sql}
|
|
335
|
+
*/
|
|
336
|
+
function dropColumn(table, column) {
|
|
337
|
+
return Sql`ALTER TABLE ${Sql.Table(table)} ${drop(column)}`;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
*
|
|
342
|
+
* @param {string} table
|
|
343
|
+
* @param {string} column
|
|
344
|
+
* @param {string} newName
|
|
345
|
+
* @returns {Sql}
|
|
346
|
+
*/
|
|
347
|
+
function renameColumn(table, column, newName) {
|
|
348
|
+
const t = Sql.Table(table);
|
|
349
|
+
return Sql`ALTER TABLE${t}${rename(column, newName)}`;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/** @import { IndexOptions } from 'imodel' */
|
|
353
|
+
/**
|
|
354
|
+
*
|
|
355
|
+
* @param {string} table
|
|
356
|
+
* @param {string} name
|
|
357
|
+
* @param {string[]} fields
|
|
358
|
+
* @param {IndexOptions} [indexOptions]
|
|
359
|
+
* @returns {Sql}
|
|
360
|
+
*/
|
|
361
|
+
function addIndex(table, name, fields, {
|
|
362
|
+
unique,
|
|
363
|
+
include
|
|
364
|
+
} = {}) {
|
|
365
|
+
const create = unique ? Sql`CREATE UNIQUE INDEX` : Sql`CREATE INDEX`;
|
|
366
|
+
const id = Sql.Id(`${table}#${name}`);
|
|
367
|
+
const allColumns = Sql`, `.glue(...fields.map(v => Sql.Field(v)));
|
|
368
|
+
const fieldSqls = Sql`ON ${Sql.Table(table)}(${allColumns})`;
|
|
369
|
+
const includes = include?.length ? Sql`INCLUDE (${Sql`, `.glue(...include.map(v => Sql.Field(v)))})` : undefined;
|
|
370
|
+
return Sql(create, id, fieldSqls, includes);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
*
|
|
375
|
+
* @param {string} table
|
|
376
|
+
* @param {string} name
|
|
377
|
+
* @returns {Sql}
|
|
378
|
+
*/
|
|
379
|
+
function dropIndex(table, name) {
|
|
380
|
+
return Sql`DROP INDEX ${Sql.Id(`${table}#${name}`)}`;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/** @import { Environment, Column } from 'imodel' */
|
|
384
|
+
/**
|
|
385
|
+
*
|
|
386
|
+
* @param {string} table
|
|
387
|
+
* @param {Column<any>[]} fields
|
|
388
|
+
* @param {Environment} env
|
|
389
|
+
* @returns {Sql}
|
|
390
|
+
*/
|
|
391
|
+
function createTable(table, fields, env) {
|
|
392
|
+
const fieldsSql = fields.map(({
|
|
393
|
+
name,
|
|
394
|
+
type,
|
|
395
|
+
nullable = true,
|
|
396
|
+
serial,
|
|
397
|
+
default: defaultValue,
|
|
398
|
+
array,
|
|
399
|
+
precision,
|
|
400
|
+
scale,
|
|
401
|
+
size
|
|
402
|
+
}) => {
|
|
403
|
+
const t = getType(type, {
|
|
404
|
+
precision,
|
|
405
|
+
scale,
|
|
406
|
+
size
|
|
407
|
+
}, serial);
|
|
408
|
+
const typeSql = Sql(`${t}${array ? '[]' : ''}`);
|
|
409
|
+
const nullSql = nullable ? Sql`` : Sql`NOT NULL`;
|
|
410
|
+
const defValueSql = getDefault(env, defaultValue, type, array);
|
|
411
|
+
const defaultSql = defValueSql ? Sql`DEFAULT ${defValueSql}` : Sql``;
|
|
412
|
+
return Sql(Sql.Field(name), typeSql, nullSql, defaultSql);
|
|
413
|
+
});
|
|
414
|
+
const primary = fields.filter(v => v.primary).map(v => v.name);
|
|
415
|
+
if (primary.length) {
|
|
416
|
+
const c = primary.map(c => Sql.Field(c));
|
|
417
|
+
fieldsSql.push(Sql` PRIMARY KEY (${Sql`,`.glue(...c)})`);
|
|
418
|
+
}
|
|
419
|
+
const sql = Sql`CREATE TABLE ${Sql.Table(table)}
|
|
420
|
+
(${Sql`,`.glue(...fieldsSql)})`;
|
|
421
|
+
return sql;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/**
|
|
425
|
+
*
|
|
426
|
+
* @param {string} table
|
|
427
|
+
* @returns {Sql}
|
|
428
|
+
*/
|
|
429
|
+
function dropTable(table) {
|
|
430
|
+
return Sql`DROP TABLE ${Sql.Table(table)}`;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
/**
|
|
434
|
+
*
|
|
435
|
+
* @param {string} table
|
|
436
|
+
* @param {string} newName
|
|
437
|
+
* @returns {Sql}
|
|
438
|
+
*/
|
|
439
|
+
function renameTable(table, newName) {
|
|
440
|
+
const t = Sql.Table(table);
|
|
441
|
+
const nn = Sql.Table(newName);
|
|
442
|
+
return Sql`ALTER TABLE ${t} RENAME TO ${nn}`;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/**
|
|
446
|
+
*
|
|
447
|
+
* @param {string} view
|
|
448
|
+
* @param {Sql} query
|
|
449
|
+
* @returns {Sql}
|
|
450
|
+
*/
|
|
451
|
+
function createView(view, query) {
|
|
452
|
+
return Sql`CREATE VIEW ${Sql.Table(view)} ${query}`;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
/**
|
|
456
|
+
*
|
|
457
|
+
* @param {string} view
|
|
458
|
+
* @returns {Sql}
|
|
459
|
+
*/
|
|
460
|
+
function dropView(view) {
|
|
461
|
+
return Sql`DROP VIEW ${Sql.Table(view)}`;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
/**
|
|
465
|
+
*
|
|
466
|
+
* @param {string} view
|
|
467
|
+
* @returns {Sql}
|
|
468
|
+
*/
|
|
469
|
+
function dropMaterializedView(view) {
|
|
470
|
+
return Sql`DROP MATERIALIZED VIEW ${Sql.Table(view)}`;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
/**
|
|
474
|
+
*
|
|
475
|
+
* @param {string} view
|
|
476
|
+
* @param {Sql} query
|
|
477
|
+
* @returns {Sql}
|
|
478
|
+
*/
|
|
479
|
+
function createMaterializedView(view, query) {
|
|
480
|
+
return Sql`CREATE MATERIALIZED VIEW ${Sql.Table(view)} ${query}`;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
/**
|
|
484
|
+
*
|
|
485
|
+
* @param {[string, boolean]} it
|
|
486
|
+
* @returns {Sql | Sql.Field}
|
|
487
|
+
*/
|
|
488
|
+
function getOrderItem(it) {
|
|
489
|
+
const [f, d] = it;
|
|
490
|
+
if (d) {
|
|
491
|
+
return Sql`${Sql.Field(f)} DESC`;
|
|
492
|
+
}
|
|
493
|
+
return Sql.Field(f);
|
|
494
|
+
}
|
|
495
|
+
/**
|
|
496
|
+
*
|
|
497
|
+
* @param {[string, boolean][]} [sort]
|
|
498
|
+
* @returns
|
|
499
|
+
*/
|
|
500
|
+
function getOrder(sort) {
|
|
501
|
+
if (!sort) {
|
|
502
|
+
return undefined;
|
|
503
|
+
}
|
|
504
|
+
if (!sort.length) {
|
|
505
|
+
return undefined;
|
|
506
|
+
}
|
|
507
|
+
return Sql`ORDER BY ${Sql`,`.glue(...sort.map(getOrderItem))}`;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
/** @import { Select } from 'imodel' */
|
|
511
|
+
/**
|
|
512
|
+
*
|
|
513
|
+
* @param {Sql.Table} table
|
|
514
|
+
* @param {string} k
|
|
515
|
+
* @param {Select | Sql | Sql.Field} v
|
|
516
|
+
* @returns
|
|
517
|
+
*/
|
|
518
|
+
function getField(table, k, v) {
|
|
519
|
+
if (!v) {
|
|
520
|
+
return;
|
|
521
|
+
}
|
|
522
|
+
if (v instanceof Sql.Field) {
|
|
523
|
+
return v.table ? v : table.field(v.field);
|
|
524
|
+
}
|
|
525
|
+
if (v instanceof Sql) {
|
|
526
|
+
return v;
|
|
527
|
+
}
|
|
528
|
+
const f = v.table ? Sql.Field(v.field, v.table) : table.field(v.field);
|
|
529
|
+
const {
|
|
530
|
+
fn
|
|
531
|
+
} = v;
|
|
532
|
+
if (!fn) {
|
|
533
|
+
return f;
|
|
534
|
+
}
|
|
535
|
+
return Sql.FnExpr(fn, [f]);
|
|
536
|
+
}
|
|
537
|
+
/**
|
|
538
|
+
*
|
|
539
|
+
* @param {Sql.Table} table
|
|
540
|
+
* @param {([string, Select] | Sql.Like | Sql.Field)[]} select
|
|
541
|
+
* @returns {Sql}
|
|
542
|
+
*/
|
|
543
|
+
function getSelect(table, select) {
|
|
544
|
+
/** @type {(Sql.Like | Sql.Field)[]} */
|
|
545
|
+
const selectList = [];
|
|
546
|
+
for (const s of select) {
|
|
547
|
+
if (Sql.isLike(s) || s instanceof Sql.Field) {
|
|
548
|
+
selectList.push(s);
|
|
549
|
+
continue;
|
|
550
|
+
}
|
|
551
|
+
if (!Array.isArray(s)) {
|
|
552
|
+
continue;
|
|
553
|
+
}
|
|
554
|
+
const [k, v] = s;
|
|
555
|
+
const f = getField(table, k, v);
|
|
556
|
+
if (!f) {
|
|
557
|
+
continue;
|
|
558
|
+
}
|
|
559
|
+
selectList.push(Sql`${f} AS ${Sql.Id(k)}`);
|
|
560
|
+
}
|
|
561
|
+
return Sql`, `.glue(...selectList);
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
/** @import { Environment, WhereValue } from 'imodel' */
|
|
565
|
+
/**
|
|
566
|
+
*
|
|
567
|
+
* @param {string | {table?: string; field: string}} k
|
|
568
|
+
*/
|
|
569
|
+
function toField(k) {
|
|
570
|
+
if (typeof k === 'string') {
|
|
571
|
+
return Sql.Field(k);
|
|
572
|
+
}
|
|
573
|
+
const {
|
|
574
|
+
table,
|
|
575
|
+
field
|
|
576
|
+
} = k;
|
|
577
|
+
if (!table) {
|
|
578
|
+
return Sql.Field(field);
|
|
579
|
+
}
|
|
580
|
+
return Sql.Field(field, table);
|
|
581
|
+
}
|
|
582
|
+
/**
|
|
583
|
+
*
|
|
584
|
+
* @param {Environment} env
|
|
585
|
+
* @param {WhereValue[]?} [wheres]
|
|
586
|
+
* @returns {Sql | undefined}
|
|
587
|
+
*/
|
|
588
|
+
function getWhere2(env, wheres) {
|
|
589
|
+
if (!wheres?.length) {
|
|
590
|
+
return;
|
|
591
|
+
}
|
|
592
|
+
/**
|
|
593
|
+
*
|
|
594
|
+
* @param {WhereValue} where
|
|
595
|
+
* @returns {Sql?}
|
|
596
|
+
*/
|
|
597
|
+
function toSql(where) {
|
|
598
|
+
if (where instanceof Sql) {
|
|
599
|
+
return where;
|
|
600
|
+
}
|
|
601
|
+
if (where.where) {
|
|
602
|
+
let q = Sql([...where.where], ...where.values);
|
|
603
|
+
if (where.not) {
|
|
604
|
+
return Sql`NOT(${q})`;
|
|
605
|
+
}
|
|
606
|
+
return q;
|
|
607
|
+
}
|
|
608
|
+
if (where.or) {
|
|
609
|
+
/** @type {Sql[]} */
|
|
610
|
+
const orList = [];
|
|
611
|
+
for (const list of where.or) {
|
|
612
|
+
/** @type {Sql[]} */
|
|
613
|
+
// @ts-ignore
|
|
614
|
+
const andList = list.map(v => toSql(v)).filter(Boolean);
|
|
615
|
+
if (andList.length) {
|
|
616
|
+
orList.push(Sql` AND `.glue(...andList));
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
if (!orList.length) {
|
|
620
|
+
return null;
|
|
621
|
+
}
|
|
622
|
+
if (where.not) {
|
|
623
|
+
return Sql`not(${Sql` OR `.glue(...orList)})`;
|
|
624
|
+
}
|
|
625
|
+
return Sql`(${Sql` OR `.glue(...orList)})`;
|
|
626
|
+
}
|
|
627
|
+
const {
|
|
628
|
+
field: k,
|
|
629
|
+
operator: o,
|
|
630
|
+
value: v
|
|
631
|
+
} = where;
|
|
632
|
+
if (Array.isArray(k)) {
|
|
633
|
+
let {
|
|
634
|
+
not
|
|
635
|
+
} = where;
|
|
636
|
+
const field = Sql`(${Sql`,`.glue(k.map(toField))})`;
|
|
637
|
+
let values = v;
|
|
638
|
+
switch (o?.toUpperCase().replaceAll('_', '').replaceAll(' ', '')) {
|
|
639
|
+
case 'NOTIN':
|
|
640
|
+
case 'NOT':
|
|
641
|
+
case '!=':
|
|
642
|
+
case '<>':
|
|
643
|
+
not = !not;
|
|
644
|
+
break;
|
|
645
|
+
}
|
|
646
|
+
console.log(v);
|
|
647
|
+
values = Array.isArray(v) ? Sql`(${Sql`,`.glue(v.map(v => Sql`(${Array.isArray(v) && v.length ? Sql(['', ...Array(v.length - 1).fill(','), ''], ...v.map(v => v ?? null)) : v ?? null})`))})` : Sql`(${v ?? null})`;
|
|
648
|
+
return Sql`${field} ${Sql(not ? `NOT IN` : `IN`)} ${values}`;
|
|
649
|
+
}
|
|
650
|
+
let {
|
|
651
|
+
not
|
|
652
|
+
} = where;
|
|
653
|
+
let operator = (o || '').toUpperCase() || '=';
|
|
654
|
+
let values = v;
|
|
655
|
+
switch (operator.replaceAll('_', '').replaceAll(' ', '')) {
|
|
656
|
+
case 'NOTIN':
|
|
657
|
+
not = !not;
|
|
658
|
+
operator = 'IN';
|
|
659
|
+
break;
|
|
660
|
+
case 'NOTLIKE':
|
|
661
|
+
not = !not;
|
|
662
|
+
operator = 'LIKE';
|
|
663
|
+
break;
|
|
664
|
+
case 'NOTCONTAINS':
|
|
665
|
+
not = !not;
|
|
666
|
+
case 'CONTAINS':
|
|
667
|
+
{
|
|
668
|
+
if (typeof values === 'string') {
|
|
669
|
+
values = `%${values.replaceAll('%', '\\%')}%`;
|
|
670
|
+
operator = 'LIKE';
|
|
671
|
+
}
|
|
672
|
+
break;
|
|
673
|
+
}
|
|
674
|
+
case 'STARTSWITH':
|
|
675
|
+
{
|
|
676
|
+
if (typeof values === 'string') {
|
|
677
|
+
values = `${values.replaceAll('%', '\\%')}%`;
|
|
678
|
+
operator = 'LIKE';
|
|
679
|
+
}
|
|
680
|
+
break;
|
|
681
|
+
}
|
|
682
|
+
case 'ENDSWITH':
|
|
683
|
+
{
|
|
684
|
+
if (typeof values === 'string') {
|
|
685
|
+
values = `%${values.replaceAll('%', '\\%')}`;
|
|
686
|
+
operator = 'LIKE';
|
|
687
|
+
}
|
|
688
|
+
break;
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
if (not) {
|
|
692
|
+
switch (operator) {
|
|
693
|
+
case 'IN':
|
|
694
|
+
operator = 'NOT IN';
|
|
695
|
+
not = false;
|
|
696
|
+
break;
|
|
697
|
+
case 'LIKE':
|
|
698
|
+
operator = 'NOT LIKE';
|
|
699
|
+
not = false;
|
|
700
|
+
break;
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
if (Array.isArray(v) && (operator === 'IN' || operator === 'NOT IN') && v.length) {
|
|
704
|
+
values = Sql(['', ...Array(v.length - 1).fill(','), ''], ...v.map(v => v ?? null));
|
|
705
|
+
} else {
|
|
706
|
+
values = Sql`${values}`;
|
|
707
|
+
}
|
|
708
|
+
if (!not) {
|
|
709
|
+
return Sql`${toField(k)} ${Sql(operator)} ${values}`;
|
|
710
|
+
}
|
|
711
|
+
return Sql`not(${toField(k)} ${Sql(operator)} ${values})`;
|
|
712
|
+
}
|
|
713
|
+
/** @type {Sql[]} */
|
|
714
|
+
// @ts-ignore
|
|
715
|
+
const andList = wheres.map(v => toSql(v)).filter(Boolean);
|
|
716
|
+
if (!andList) {
|
|
717
|
+
return;
|
|
718
|
+
}
|
|
719
|
+
return Sql` AND `.glue(...andList);
|
|
720
|
+
}
|
|
721
|
+
/**
|
|
722
|
+
*
|
|
723
|
+
* @param {Environment} env
|
|
724
|
+
* @param {WhereValue[]?} [where]
|
|
725
|
+
* @returns {Sql | undefined}
|
|
726
|
+
*/
|
|
727
|
+
function getWhere(env, where) {
|
|
728
|
+
const andList = getWhere2(env, where);
|
|
729
|
+
if (!andList) {
|
|
730
|
+
return;
|
|
731
|
+
}
|
|
732
|
+
return Sql`WHERE ${andList}`;
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
/** @import { Environment, SelectArg } from 'imodel' */
|
|
736
|
+
/**
|
|
737
|
+
*
|
|
738
|
+
* @param {Environment} env
|
|
739
|
+
* @param {SelectArg} param1
|
|
740
|
+
* @returns {Sql}
|
|
741
|
+
*/
|
|
742
|
+
function selectSql(env, {
|
|
743
|
+
table,
|
|
744
|
+
fields,
|
|
745
|
+
alias,
|
|
746
|
+
select,
|
|
747
|
+
where,
|
|
748
|
+
sort,
|
|
749
|
+
offset,
|
|
750
|
+
limit
|
|
751
|
+
}) {
|
|
752
|
+
const Table = alias ? Sql.Table(table, alias) : Sql.Table(table);
|
|
753
|
+
const selectSql = getSelect(Table, select);
|
|
754
|
+
const orderSql = getOrder(sort);
|
|
755
|
+
const limitSql = limit && limit > 0 ? Sql('LIMIT', limit) : undefined;
|
|
756
|
+
const offsetSql = offset && offset > 0 ? Sql('OFFSET', offset) : undefined;
|
|
757
|
+
return Sql(Sql`SELECT ${selectSql} FROM ${Table}`, getWhere(env, where), orderSql, limitSql, offsetSql);
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
/** @import { Environment, CountArg } from 'imodel' */
|
|
761
|
+
/**
|
|
762
|
+
*
|
|
763
|
+
* @param {Environment} env
|
|
764
|
+
* @param {CountArg} arg
|
|
765
|
+
* @returns {Sql}
|
|
766
|
+
*/
|
|
767
|
+
function countSql(env, {
|
|
768
|
+
table,
|
|
769
|
+
alias,
|
|
770
|
+
where
|
|
771
|
+
}) {
|
|
772
|
+
const Table = alias ? Sql.Table(table, alias) : Sql.Table(table);
|
|
773
|
+
return Sql`SELECT count(*) AS count FROM ${Table} ${getWhere(env, where)}`;
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
/** @import { Environment, Fields, FieldType, WhereValue } from 'imodel' */
|
|
777
|
+
/**
|
|
778
|
+
*
|
|
779
|
+
* @param {Environment} env
|
|
780
|
+
* @param {string} table
|
|
781
|
+
* @param {Fields<keyof FieldType>} [fields]
|
|
782
|
+
* @param {WhereValue[]?} [where]
|
|
783
|
+
* @param {string[]?} [returning]
|
|
784
|
+
* @returns {Sql}
|
|
785
|
+
*/
|
|
786
|
+
function deleteSql(env, table, fields, where, returning) {
|
|
787
|
+
const returnSql = returning ? Sql`RETURNING ${Sql`, `.glue(...returning.map(k => Sql.Field(k)))}` : undefined;
|
|
788
|
+
return Sql(Sql`DELETE FROM ${Sql.Table(table)}`, getWhere(env, where), returnSql);
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
/** @import { Environment, FieldDefine, Fields, FieldType } from 'imodel' */
|
|
792
|
+
/**
|
|
793
|
+
*
|
|
794
|
+
* @param {object} data
|
|
795
|
+
* @param {string} key
|
|
796
|
+
* @param {FieldDefine<keyof FieldType, boolean, boolean>} field
|
|
797
|
+
* @param {Environment} env
|
|
798
|
+
* @returns
|
|
799
|
+
*/
|
|
800
|
+
function getValue(data, key, field, env) {
|
|
801
|
+
const val = data[key];
|
|
802
|
+
if (val === env.values.now) {
|
|
803
|
+
return Sql`now()`;
|
|
804
|
+
}
|
|
805
|
+
if (val !== undefined) {
|
|
806
|
+
return val;
|
|
807
|
+
}
|
|
808
|
+
const def = field.default;
|
|
809
|
+
return typeof def === 'function' ? def(data) ?? null : def ?? null;
|
|
810
|
+
}
|
|
811
|
+
/**
|
|
812
|
+
*
|
|
813
|
+
* @param {string} table
|
|
814
|
+
* @param {Fields<keyof FieldType>} fields
|
|
815
|
+
* @param {object[]} list
|
|
816
|
+
* @param {string[]} keys
|
|
817
|
+
* @param {Environment} env
|
|
818
|
+
* @returns {Sql}
|
|
819
|
+
*/
|
|
820
|
+
function insertSql(table, fields, list, keys, env) {
|
|
821
|
+
const key = new Set(keys);
|
|
822
|
+
const keyData = Object.entries(fields).filter(([f]) => key.has(f));
|
|
823
|
+
const values = list.map(v => Sql`(${Sql`,`.glue(...keyData.map(([k, f]) => Sql`${getValue(v, k, f, env)}`))})`);
|
|
824
|
+
return Sql`INSERT INTO ${Sql.Table(table)} (${Sql`,`.glue(...keyData.map(([k]) => Sql.Field(k)))}) VALUES ${Sql`,`.glue(...values)} RETURNING *`;
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
/** @import { Environment, Fields, FieldType, SetValue } from 'imodel' */
|
|
828
|
+
/**
|
|
829
|
+
*
|
|
830
|
+
* @param {Record<string, SetValue>} update
|
|
831
|
+
* @param {Fields<keyof FieldType>} fields
|
|
832
|
+
* @param {Environment} env
|
|
833
|
+
* @returns {Sql[]}
|
|
834
|
+
*/
|
|
835
|
+
function getSet(update, fields, env) {
|
|
836
|
+
const {
|
|
837
|
+
now,
|
|
838
|
+
increment,
|
|
839
|
+
decrement,
|
|
840
|
+
multiply,
|
|
841
|
+
divide
|
|
842
|
+
} = env.values;
|
|
843
|
+
return /** @type {Sql[]} */Object.entries(update).filter(([, v]) => v !== undefined).map(([key, value]) => {
|
|
844
|
+
const field = key in fields && fields[key];
|
|
845
|
+
if (!field) {
|
|
846
|
+
return;
|
|
847
|
+
}
|
|
848
|
+
const f = Sql.Field(key);
|
|
849
|
+
if (value instanceof Sql) {
|
|
850
|
+
return Sql`${f} = ${value}`;
|
|
851
|
+
}
|
|
852
|
+
if (value === now) {
|
|
853
|
+
return Sql`${f} = now()`;
|
|
854
|
+
}
|
|
855
|
+
if (value === null) {
|
|
856
|
+
return Sql`${f} = NULL`;
|
|
857
|
+
}
|
|
858
|
+
if (typeof value !== 'object') {
|
|
859
|
+
return Sql`${f} = ${value}`;
|
|
860
|
+
}
|
|
861
|
+
const incrementValue = value[increment];
|
|
862
|
+
if (incrementValue !== undefined) {
|
|
863
|
+
return Sql`${f} = ${f} + ${incrementValue}`;
|
|
864
|
+
}
|
|
865
|
+
const decrementValue = value[decrement];
|
|
866
|
+
if (decrementValue !== undefined) {
|
|
867
|
+
return Sql`${f} = ${f} + ${decrementValue}`;
|
|
868
|
+
}
|
|
869
|
+
const multiplyValue = value[multiply];
|
|
870
|
+
if (multiplyValue !== undefined) {
|
|
871
|
+
return Sql`${f} = ${f} + ${multiplyValue}`;
|
|
872
|
+
}
|
|
873
|
+
const divideValue = value[divide];
|
|
874
|
+
if (divideValue !== undefined) {
|
|
875
|
+
return Sql`${f} = ${f} + ${divideValue}`;
|
|
876
|
+
}
|
|
877
|
+
return Sql`${f} = ${value}`;
|
|
878
|
+
}).filter(Boolean);
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
/** @import { Environment, MatchArg, SetValue } from 'imodel' */
|
|
882
|
+
/**
|
|
883
|
+
*
|
|
884
|
+
* @param {Environment} env
|
|
885
|
+
* @param {MatchArg} param1
|
|
886
|
+
* @param {Record<string, SetValue>} update
|
|
887
|
+
* @param {string[]?} [returning]
|
|
888
|
+
* @returns {Sql}
|
|
889
|
+
*/
|
|
890
|
+
function updateSql(env, {
|
|
891
|
+
table,
|
|
892
|
+
fields,
|
|
893
|
+
where
|
|
894
|
+
}, update, returning) {
|
|
895
|
+
const setSql = Sql`, `.glue(...getSet(update, fields, env));
|
|
896
|
+
const returnSql = returning ? Sql`RETURNING ${Sql`, `.glue(...returning.map(k => Sql.Field(k)))}` : undefined;
|
|
897
|
+
return Sql(Sql`UPDATE ${Sql.Table(table)} SET ${setSql}`, getWhere(env, where), returnSql);
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
/** @import { Environment, Fields, FieldType, WhereValue } from 'imodel' */
|
|
901
|
+
const tmp = Sql.Table('__tmp').as('__tmp');
|
|
902
|
+
const tmpId = Sql.Id('__tmp', 'alias');
|
|
903
|
+
/**
|
|
904
|
+
*
|
|
905
|
+
* @param {Environment} env
|
|
906
|
+
* @param {string} table
|
|
907
|
+
* @param {Fields<keyof FieldType>} fields
|
|
908
|
+
* @param {string[]} pKeys
|
|
909
|
+
* @param {string[]} setKeys
|
|
910
|
+
* @param {Record<string, any>[]} list
|
|
911
|
+
* @param {Record<string, any>} values
|
|
912
|
+
* @param {WhereValue[]} where
|
|
913
|
+
* @param {string[]} [returning]
|
|
914
|
+
* @returns {Sql}
|
|
915
|
+
*/
|
|
916
|
+
function updateListSql(env, table, fields, pKeys, setKeys, list, values, where, returning) {
|
|
917
|
+
const dataFields = [...setKeys, ...pKeys];
|
|
918
|
+
const returnSql = returning ? Sql`RETURNING ${Sql`, `.glue(...returning.map(k => Sql.Field(k)))}` : undefined;
|
|
919
|
+
/** @type {[string, [string, string]][]} */
|
|
920
|
+
const dataFieldType = dataFields.map(f => {
|
|
921
|
+
const field = fields[f];
|
|
922
|
+
if (!field) {
|
|
923
|
+
return [f, ['', '']];
|
|
924
|
+
}
|
|
925
|
+
const type = `${getType(field.type, field)}${field.array ? '[]' : ''}`;
|
|
926
|
+
return [f, ['CAST(', ` as ${type})`]];
|
|
927
|
+
});
|
|
928
|
+
const main = Sql.Table(table);
|
|
929
|
+
return Sql`UPDATE ${main}
|
|
930
|
+
SET ${Sql`, `.glue(setKeys.map(f => Sql`${Sql.Field(f)} = ${tmp.field(f)}`), Object.entries(values).map(([f, v]) => Sql`${Sql.Field(f)} = ${v}`))}
|
|
931
|
+
FROM (values${Sql`,`.glue(list.map(data => Sql`(${Sql`,`.glue(dataFieldType.map(([f, t]) => {
|
|
932
|
+
const v = data[f] ?? null;
|
|
933
|
+
if (v === null) {
|
|
934
|
+
return Sql`${v}`;
|
|
935
|
+
}
|
|
936
|
+
return Sql(t, data[f] ?? null);
|
|
937
|
+
}))})`))}) as ${tmpId}(${Sql`,`.glue(dataFields.map(f => Sql.Field(f)))})
|
|
938
|
+
WHERE ${Sql`AND`.glue(pKeys.map(f => Sql`${main.field(f)}=${tmp.field(f)}`), getWhere2(env, where))}${returnSql}`;
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
/** @import { Environment, IConnection } from 'imodel' */
|
|
942
|
+
const {
|
|
943
|
+
types
|
|
944
|
+
} = pg;
|
|
945
|
+
types.setTypeParser(types.builtins.INT8, v => BigInt(v));
|
|
946
|
+
const dbType = 'postgres';
|
|
947
|
+
/**
|
|
948
|
+
*
|
|
949
|
+
* @param {string} text
|
|
950
|
+
* @param {readonly any[]} values
|
|
951
|
+
* @returns
|
|
952
|
+
*/
|
|
953
|
+
function echo(text, values) {
|
|
954
|
+
if (!values.length) {
|
|
955
|
+
console.info(text);
|
|
956
|
+
return;
|
|
957
|
+
}
|
|
958
|
+
console.groupCollapsed(text);
|
|
959
|
+
for (let i = 0; i < values.length; i++) {
|
|
960
|
+
console.info(`$${i + 1}:`, values[i]);
|
|
961
|
+
}
|
|
962
|
+
console.groupEnd();
|
|
963
|
+
}
|
|
964
|
+
/**
|
|
965
|
+
*
|
|
966
|
+
* @param {() => pg.PoolClient | PromiseLike<pg.PoolClient>} connect
|
|
967
|
+
* @returns
|
|
968
|
+
*/
|
|
969
|
+
function createVersion(connect) {
|
|
970
|
+
/**
|
|
971
|
+
*
|
|
972
|
+
* @param {pg.PoolClient?} [userClient]
|
|
973
|
+
* @returns
|
|
974
|
+
*/
|
|
975
|
+
async function getVersion(userClient) {
|
|
976
|
+
const client = userClient || (await connect());
|
|
977
|
+
/** @type {pg.QueryResult<{server_version: string}>} */
|
|
978
|
+
const v = await client.query('show server_version');
|
|
979
|
+
if (!userClient) {
|
|
980
|
+
client.release();
|
|
981
|
+
}
|
|
982
|
+
/** 数据库版本号 */
|
|
983
|
+
return v.rows[0]?.server_version || '';
|
|
984
|
+
}
|
|
985
|
+
/** @type {Promise<string>?} */
|
|
986
|
+
let dbVersion = null;
|
|
987
|
+
/**
|
|
988
|
+
*
|
|
989
|
+
* @param {pg.PoolClient?} [client]
|
|
990
|
+
* @returns
|
|
991
|
+
*/
|
|
992
|
+
return function version(client) {
|
|
993
|
+
dbVersion ||= getVersion(client);
|
|
994
|
+
return dbVersion;
|
|
995
|
+
};
|
|
996
|
+
}
|
|
997
|
+
/** @typedef {{client?: pg.PoolClient?}} PgEnvTrans */
|
|
998
|
+
/**
|
|
999
|
+
*
|
|
1000
|
+
* @param {pg.Pool | (() => pg.PoolClient | PromiseLike<pg.PoolClient>) | null} [pool]
|
|
1001
|
+
* @returns {() => pg.PoolClient | PromiseLike<pg.PoolClient>}
|
|
1002
|
+
*/
|
|
1003
|
+
function toPoolConnection(pool) {
|
|
1004
|
+
if (!pool) {
|
|
1005
|
+
return () => {
|
|
1006
|
+
throw new Error();
|
|
1007
|
+
};
|
|
1008
|
+
}
|
|
1009
|
+
if (typeof pool === 'function') {
|
|
1010
|
+
return pool;
|
|
1011
|
+
}
|
|
1012
|
+
return () => pool.connect();
|
|
1013
|
+
}
|
|
1014
|
+
/**
|
|
1015
|
+
*
|
|
1016
|
+
* @param {pg.Pool | (() => pg.PoolClient | PromiseLike<pg.PoolClient>)} [pool]
|
|
1017
|
+
* @param {(client?: pg.PoolClient?) => Promise<string>} [version]
|
|
1018
|
+
* @returns {IConnection<PgEnvTrans>}
|
|
1019
|
+
*/
|
|
1020
|
+
function index (pool, version) {
|
|
1021
|
+
const connect = toPoolConnection(pool);
|
|
1022
|
+
const dbVersion = typeof version === 'function' ? version : createVersion(connect);
|
|
1023
|
+
/**
|
|
1024
|
+
* @template {pg.QueryResultRow} T
|
|
1025
|
+
* @param {Sql} sql
|
|
1026
|
+
* @param {Environment<PgEnvTrans>} env
|
|
1027
|
+
* @returns {Promise<pg.QueryResult<T>>}
|
|
1028
|
+
*/
|
|
1029
|
+
async function _exec(sql, env) {
|
|
1030
|
+
const text = sql.build((_, i) => `$${i + 1}`);
|
|
1031
|
+
const {
|
|
1032
|
+
values
|
|
1033
|
+
} = sql;
|
|
1034
|
+
if (env.isDevelopment) {
|
|
1035
|
+
echo(text, values);
|
|
1036
|
+
}
|
|
1037
|
+
const {
|
|
1038
|
+
transaction
|
|
1039
|
+
} = env;
|
|
1040
|
+
const transactionClient = transaction?.client;
|
|
1041
|
+
if (transactionClient) {
|
|
1042
|
+
const client = await Promise.resolve(transactionClient);
|
|
1043
|
+
return client.query({
|
|
1044
|
+
text,
|
|
1045
|
+
values: [...values]
|
|
1046
|
+
});
|
|
1047
|
+
}
|
|
1048
|
+
const client = await connect();
|
|
1049
|
+
try {
|
|
1050
|
+
return await client.query({
|
|
1051
|
+
text,
|
|
1052
|
+
values: [...values]
|
|
1053
|
+
});
|
|
1054
|
+
} finally {
|
|
1055
|
+
client.release();
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
/**
|
|
1059
|
+
* @template {pg.QueryResultRow} T
|
|
1060
|
+
* @param {Environment<PgEnvTrans>} env
|
|
1061
|
+
* @param {Sql} sql
|
|
1062
|
+
* @returns {Promise<T[]>}
|
|
1063
|
+
*/
|
|
1064
|
+
async function query(env, sql) {
|
|
1065
|
+
/** @type {pg.QueryResult<T>} */
|
|
1066
|
+
const result = await _exec(sql, env);
|
|
1067
|
+
return result.rows;
|
|
1068
|
+
}
|
|
1069
|
+
/**
|
|
1070
|
+
* @param {Environment<PgEnvTrans>} env
|
|
1071
|
+
* @param {Sql} sql
|
|
1072
|
+
* @returns {Promise<number>}
|
|
1073
|
+
*/
|
|
1074
|
+
async function exec(env, sql) {
|
|
1075
|
+
const result = await _exec(sql, env);
|
|
1076
|
+
return result.rowCount || 0;
|
|
1077
|
+
}
|
|
1078
|
+
return {
|
|
1079
|
+
dbType,
|
|
1080
|
+
async dbVersion(env) {
|
|
1081
|
+
return dbVersion(env.transaction?.client);
|
|
1082
|
+
},
|
|
1083
|
+
query(env, sql, ...values) {
|
|
1084
|
+
const ss = Array.isArray(sql) ? Sql(sql, ...values) : sql;
|
|
1085
|
+
const {
|
|
1086
|
+
getName
|
|
1087
|
+
} = env;
|
|
1088
|
+
const s = env.names ? ss.transform((id, group) => group === 'table' ? getName(id) : id) : ss;
|
|
1089
|
+
return query(env, s);
|
|
1090
|
+
},
|
|
1091
|
+
exec(env, sql, ...values) {
|
|
1092
|
+
const ss = Array.isArray(sql) ? Sql(sql, ...values) : sql;
|
|
1093
|
+
const {
|
|
1094
|
+
getName
|
|
1095
|
+
} = env;
|
|
1096
|
+
const s = env.names ? ss.transform((id, group) => group === 'table' ? getName(id) : id) : ss;
|
|
1097
|
+
return exec(env, s);
|
|
1098
|
+
},
|
|
1099
|
+
async type(env, table) {
|
|
1100
|
+
const sql = Sql`SELECT table_type as type
|
|
1101
|
+
FROM information_schema.tables
|
|
1102
|
+
WHERE table_schema = 'public' AND table_name = ${table} LIMIT 1`;
|
|
1103
|
+
/** @type {pg.QueryResult<{ type: string; }>} */
|
|
1104
|
+
const {
|
|
1105
|
+
rows: [row]
|
|
1106
|
+
} = await _exec(sql, env);
|
|
1107
|
+
if (!row) {
|
|
1108
|
+
return null;
|
|
1109
|
+
}
|
|
1110
|
+
switch (String(row.type).toLowerCase()) {
|
|
1111
|
+
case 'base table':
|
|
1112
|
+
return 'table';
|
|
1113
|
+
case 'view':
|
|
1114
|
+
return 'view';
|
|
1115
|
+
default:
|
|
1116
|
+
return '';
|
|
1117
|
+
}
|
|
1118
|
+
},
|
|
1119
|
+
insert(env, table, fields, data, keys) {
|
|
1120
|
+
return query(env, insertSql(table, fields, data, keys, env));
|
|
1121
|
+
},
|
|
1122
|
+
select(env, arg) {
|
|
1123
|
+
return query(env, selectSql(env, arg));
|
|
1124
|
+
},
|
|
1125
|
+
count(env, argv) {
|
|
1126
|
+
return query(env, countSql(env, argv)).then(r => r[0]?.count || 0);
|
|
1127
|
+
},
|
|
1128
|
+
update(env, argv, update) {
|
|
1129
|
+
if (!Object.keys(update).length) {
|
|
1130
|
+
return Promise.resolve(0);
|
|
1131
|
+
}
|
|
1132
|
+
return exec(env, updateSql(env, argv, update));
|
|
1133
|
+
},
|
|
1134
|
+
updateReturn(env, argv, update, returning) {
|
|
1135
|
+
if (!Object.keys(update).length) {
|
|
1136
|
+
const {
|
|
1137
|
+
table,
|
|
1138
|
+
fields,
|
|
1139
|
+
where
|
|
1140
|
+
} = argv;
|
|
1141
|
+
return query(env, selectSql(env, {
|
|
1142
|
+
table,
|
|
1143
|
+
fields,
|
|
1144
|
+
where: where || [],
|
|
1145
|
+
select: returning.map(v => [v, {
|
|
1146
|
+
field: v
|
|
1147
|
+
}])
|
|
1148
|
+
}));
|
|
1149
|
+
}
|
|
1150
|
+
return query(env, updateSql(env, argv, update, returning));
|
|
1151
|
+
},
|
|
1152
|
+
updateMany(env, table, fields, update, pKeys, setKeys, list, where) {
|
|
1153
|
+
return exec(env, updateListSql(env, table, fields, pKeys, setKeys, list, update, where));
|
|
1154
|
+
},
|
|
1155
|
+
updateManyReturn(env, table, fields, update, pKeys, setKeys, list, where, returning) {
|
|
1156
|
+
return query(env, updateListSql(env, table, fields, pKeys, setKeys, list, update, where, returning));
|
|
1157
|
+
},
|
|
1158
|
+
delete(env, {
|
|
1159
|
+
table,
|
|
1160
|
+
fields,
|
|
1161
|
+
where
|
|
1162
|
+
}) {
|
|
1163
|
+
return exec(env, deleteSql(env, table, fields, where));
|
|
1164
|
+
},
|
|
1165
|
+
deleteReturn(env, {
|
|
1166
|
+
table,
|
|
1167
|
+
fields,
|
|
1168
|
+
where
|
|
1169
|
+
}, returning) {
|
|
1170
|
+
return query(env, deleteSql(env, table, fields, where, returning));
|
|
1171
|
+
},
|
|
1172
|
+
addColumn(env, table, column, type, options) {
|
|
1173
|
+
return exec(env, addColumn(table, column, type, options, env));
|
|
1174
|
+
},
|
|
1175
|
+
changeColumn(env, table, column, type, options) {
|
|
1176
|
+
return exec(env, changeColumn(table, column, type, options, env));
|
|
1177
|
+
},
|
|
1178
|
+
changeColumnNull(env, table, column, nullable) {
|
|
1179
|
+
return exec(env, changeColumnNull(table, column, nullable));
|
|
1180
|
+
},
|
|
1181
|
+
dropColumn(env, table, column) {
|
|
1182
|
+
return exec(env, dropColumn(table, column));
|
|
1183
|
+
},
|
|
1184
|
+
renameColumn(env, table, column, newName) {
|
|
1185
|
+
return exec(env, renameColumn(table, column, newName));
|
|
1186
|
+
},
|
|
1187
|
+
addIndex(env, table, name, fields, options) {
|
|
1188
|
+
return exec(env, addIndex(table, name, fields, options));
|
|
1189
|
+
},
|
|
1190
|
+
dropIndex(env, table, name) {
|
|
1191
|
+
return exec(env, dropIndex(table, name));
|
|
1192
|
+
},
|
|
1193
|
+
createTable(env, table, fields) {
|
|
1194
|
+
return exec(env, createTable(table, fields, env));
|
|
1195
|
+
},
|
|
1196
|
+
dropTable(env, table) {
|
|
1197
|
+
return exec(env, dropTable(table));
|
|
1198
|
+
},
|
|
1199
|
+
renameTable(env, table, newName) {
|
|
1200
|
+
return exec(env, renameTable(table, newName));
|
|
1201
|
+
},
|
|
1202
|
+
createView(env, view, q) {
|
|
1203
|
+
return exec(env, createView(view, selectSql(env, q)));
|
|
1204
|
+
},
|
|
1205
|
+
dropView(env, view) {
|
|
1206
|
+
return exec(env, dropView(view));
|
|
1207
|
+
},
|
|
1208
|
+
createMaterializedView(env, view, q) {
|
|
1209
|
+
return exec(env, createMaterializedView(view, selectSql(env, q)));
|
|
1210
|
+
},
|
|
1211
|
+
dropMaterializedView(env, view) {
|
|
1212
|
+
return exec(env, dropMaterializedView(view));
|
|
1213
|
+
},
|
|
1214
|
+
async transaction(env, fn) {
|
|
1215
|
+
if (env?.transaction?.client) {
|
|
1216
|
+
throw new Error();
|
|
1217
|
+
}
|
|
1218
|
+
const client = await connect();
|
|
1219
|
+
await client.query('BEGIN');
|
|
1220
|
+
/** @type {PgEnvTrans} */
|
|
1221
|
+
const transaction = {
|
|
1222
|
+
client
|
|
1223
|
+
};
|
|
1224
|
+
let error = false;
|
|
1225
|
+
try {
|
|
1226
|
+
return await fn(transaction);
|
|
1227
|
+
} catch (e) {
|
|
1228
|
+
error = true;
|
|
1229
|
+
throw e;
|
|
1230
|
+
} finally {
|
|
1231
|
+
transaction.client = null;
|
|
1232
|
+
await client.query(error ? 'ROLLBACK' : 'COMMIT');
|
|
1233
|
+
client.release();
|
|
1234
|
+
}
|
|
1235
|
+
}
|
|
1236
|
+
};
|
|
1237
|
+
}
|
|
1238
|
+
/**
|
|
1239
|
+
* @param {pg.PoolClient} client
|
|
1240
|
+
* @returns {PgEnvTrans}
|
|
1241
|
+
*/
|
|
1242
|
+
function toTransaction(client) {
|
|
1243
|
+
return {
|
|
1244
|
+
client
|
|
1245
|
+
};
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
export { index as default, toTransaction };
|
package/package.json
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "imodel-pg",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"dependencies": {
|
|
5
|
+
"pg": "^8.13.3",
|
|
6
|
+
"tagged-sql": "^0.9.0"
|
|
7
|
+
},
|
|
8
|
+
"main": "index.mjs",
|
|
9
|
+
"type": "module",
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "https://git@gitee.com/fierflame/imodel.git"
|
|
13
|
+
}
|
|
14
|
+
}
|