neopg 2.2.7 → 2.2.9
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/lib/ModelChain.js +10 -3
- package/lib/SchemaSync.js +87 -10
- package/package.json +1 -1
- package/postgres/connection.js +50 -30
- package/postgres/index.js +2 -1
- package/test/test-db.js +14 -0
package/lib/ModelChain.js
CHANGED
|
@@ -418,10 +418,17 @@ class ModelChain {
|
|
|
418
418
|
}
|
|
419
419
|
}
|
|
420
420
|
|
|
421
|
-
|
|
421
|
+
/**
|
|
422
|
+
*
|
|
423
|
+
* @param {object} data
|
|
424
|
+
* @param {boolean} one_result - auto return result[0] if only one item, default true
|
|
425
|
+
* @returns
|
|
426
|
+
*/
|
|
427
|
+
async update(data, one_result=true) {
|
|
422
428
|
this._ensureActive()
|
|
423
429
|
try {
|
|
424
|
-
if (!data || Object.keys(data).length === 0)
|
|
430
|
+
if (!data || Object.keys(data).length === 0)
|
|
431
|
+
throw new Error('[NeoPG] Update data cannot be empty')
|
|
425
432
|
|
|
426
433
|
if (!this._isRaw) {
|
|
427
434
|
this._prepareDataForUpdate(data)
|
|
@@ -436,7 +443,7 @@ class ModelChain {
|
|
|
436
443
|
const result = await this.sql`UPDATE ${fullTable} SET ${this.sql(data)} ${whereFragment} ${retFragment}`
|
|
437
444
|
|
|
438
445
|
if (this._returning && this._returning.length > 0) {
|
|
439
|
-
if (result.length === 1) return result[0]
|
|
446
|
+
if (result.length === 1 && one_result) return result[0]
|
|
440
447
|
return result
|
|
441
448
|
}
|
|
442
449
|
return result
|
package/lib/SchemaSync.js
CHANGED
|
@@ -102,9 +102,9 @@ class SchemaSync {
|
|
|
102
102
|
for (const c of cols) inf[c.column_name] = c;
|
|
103
103
|
|
|
104
104
|
await this.syncColumn(sql, def, inf, curTableName, debug, force, dropNotExistCol);
|
|
105
|
+
await this.autoRemoveIndex(sql, def, schema, tableName, debug);
|
|
105
106
|
await this.syncIndex(sql, def, schema, curTableName, debug);
|
|
106
107
|
await this.syncUnique(sql, def, schema, curTableName, debug);
|
|
107
|
-
await this.autoRemoveIndex(sql, def, schema, tableName, debug);
|
|
108
108
|
await this.syncReferences(sql, def, schema, curTableName, schemaOid, debug, ctx, options);
|
|
109
109
|
|
|
110
110
|
if (debug) console.log(` - 表结构同步完成 (${tableName}) - `);
|
|
@@ -290,6 +290,33 @@ class SchemaSync {
|
|
|
290
290
|
const indices = def.index || [];
|
|
291
291
|
if (!Array.isArray(indices)) return;
|
|
292
292
|
|
|
293
|
+
let gen_real_index = indname => {
|
|
294
|
+
let real_indexname
|
|
295
|
+
if (Array.isArray(indname)) {
|
|
296
|
+
real_indexname = indname.sort((a,b) => a.localeCompare(b)).join('_');
|
|
297
|
+
} else if (typeof indname === 'string') {
|
|
298
|
+
if (indname.indexOf(',') >= 0) {
|
|
299
|
+
real_indexname = indname.split(',').map(s=>s.trim()).filter(s=>s)
|
|
300
|
+
.sort((a,b)=>a.localeCompare(b))
|
|
301
|
+
.join('_');
|
|
302
|
+
} else {
|
|
303
|
+
real_indexname = indname.trim();
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
return real_indexname;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
let unique_indexs = (def.unique || []).map(a => gen_real_index(a));
|
|
311
|
+
|
|
312
|
+
let check_in_unique = (indname) => {
|
|
313
|
+
for (let a of unique_indexs) {
|
|
314
|
+
if (a === gen_real_index(indname)) return true;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
return false;
|
|
318
|
+
}
|
|
319
|
+
|
|
293
320
|
for (const indname of indices) {
|
|
294
321
|
const removeIndex = def.removeIndex || [];
|
|
295
322
|
if (removeIndex.includes(indname)) continue;
|
|
@@ -299,7 +326,21 @@ class SchemaSync {
|
|
|
299
326
|
continue;
|
|
300
327
|
}
|
|
301
328
|
|
|
302
|
-
|
|
329
|
+
if (!Array.isArray(indname) && typeof indname !== 'string') {
|
|
330
|
+
debug && console.error(`Index ${indname} 不是字符串或数组,跳过。`);
|
|
331
|
+
continue;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
if (check_in_unique(indname)) {
|
|
335
|
+
debug && console.error(`Index ${indname} 是唯一索引,不会创建index索引,跳过。`);
|
|
336
|
+
continue;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
let idxCols = Array.isArray(indname)
|
|
340
|
+
? indname.sort((a,b) => a.localeCompare(b))
|
|
341
|
+
: indname.split(',').map(s=>s.trim()).filter(s=>s)
|
|
342
|
+
.sort((a,b) => a.localeCompare(b));
|
|
343
|
+
|
|
303
344
|
const idxNamePart = idxCols.join('_');
|
|
304
345
|
const targetIdxName = `${def.tableName}_${idxNamePart}_idx`;
|
|
305
346
|
|
|
@@ -339,7 +380,14 @@ class SchemaSync {
|
|
|
339
380
|
for (const indname of uniques) {
|
|
340
381
|
if (!this._checkColumnsExist(indname, def)) continue;
|
|
341
382
|
|
|
342
|
-
|
|
383
|
+
if (!Array.isArray(indname) && typeof indname !== 'string') {
|
|
384
|
+
debug && console.error(`Index ${indname} 不是字符串或数组,跳过。`);
|
|
385
|
+
continue;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
let idxCols = Array.isArray(indname)
|
|
389
|
+
? indname.sort((a,b)=>a.localeCompare(b))
|
|
390
|
+
: indname.split(',').map(s=>s.trim()).filter(s=>s).sort((a,b)=>a.localeCompare(b));
|
|
343
391
|
|
|
344
392
|
// 监测是否等于主键
|
|
345
393
|
if (pkSet.size > 0 && idxCols.length === pkSet.size) {
|
|
@@ -368,7 +416,7 @@ class SchemaSync {
|
|
|
368
416
|
|
|
369
417
|
static async autoRemoveIndex(sql, def, schema, tableName, debug) {
|
|
370
418
|
const allIdx = await sql`
|
|
371
|
-
SELECT
|
|
419
|
+
SELECT * FROM pg_indexes
|
|
372
420
|
WHERE tablename = ${tableName}
|
|
373
421
|
AND schemaname = ${schema}
|
|
374
422
|
AND indexname != ${tableName + '_pkey'}
|
|
@@ -377,22 +425,51 @@ class SchemaSync {
|
|
|
377
425
|
if (allIdx.length === 0) return;
|
|
378
426
|
|
|
379
427
|
const currentIdxNames = allIdx.map(i => i.indexname);
|
|
428
|
+
let indexTable = Object.create(null);
|
|
429
|
+
for (let a of allIdx) {
|
|
430
|
+
indexTable[a.indexname] = a;
|
|
431
|
+
}
|
|
380
432
|
|
|
381
433
|
const indices = def.index || [];
|
|
382
434
|
const uniques = def.unique || [];
|
|
383
|
-
|
|
384
|
-
const keepSet = new Set();
|
|
385
|
-
const makeName = (n) => `${tableName}_${Array.isArray(n) ? n.map(x=>x.trim()).join('_') : n.split(',').map(x=>x.trim()).filter(x=>x).join('_')}_idx`;
|
|
386
435
|
|
|
387
|
-
|
|
388
|
-
|
|
436
|
+
const makeName = (n) => `${tableName}_${Array.isArray(n)
|
|
437
|
+
? n.map(x=>x.trim()).sort((a,b) => a.localeCompare(b)).join('_')
|
|
438
|
+
: n.split(',').map(x=>x.trim()).filter(x=>x).sort((a,b) => a.localeCompare(b)).join('_')}_idx`;
|
|
439
|
+
|
|
440
|
+
let allIndex = Object.create(null);
|
|
441
|
+
for (let k of indices) {
|
|
442
|
+
allIndex[makeName(k)] = k;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
let allUnique = Object.create(null);
|
|
446
|
+
for (let k of uniques) {
|
|
447
|
+
allUnique[makeName(k)] = k;
|
|
448
|
+
}
|
|
389
449
|
|
|
390
450
|
for (const idxName of currentIdxNames) {
|
|
391
|
-
if (!
|
|
451
|
+
if (!allIndex[idxName] && !allUnique[idxName]) {
|
|
392
452
|
if (debug) console.log('Auto removing index:', idxName);
|
|
393
453
|
await sql.unsafe(`DROP INDEX ${schema}.${idxName}`);
|
|
394
454
|
}
|
|
395
455
|
}
|
|
456
|
+
|
|
457
|
+
for (let k in allIndex) {
|
|
458
|
+
if (!indexTable[k]) continue;
|
|
459
|
+
|
|
460
|
+
if (indexTable[k].indexdef.toLowerCase().indexOf('create index') !== 0) {
|
|
461
|
+
debug && console.log('Auto remove index with inconsistent type:', k);
|
|
462
|
+
await sql.unsafe(`DROP INDEX ${schema}.${k};`);
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
for (let k in allUnique) {
|
|
467
|
+
if (!indexTable[k]) continue;
|
|
468
|
+
if (indexTable[k].indexdef.toLowerCase().indexOf('create unique') !== 0) {
|
|
469
|
+
debug && console.log('Auto remove index with inconsistent type:', k);
|
|
470
|
+
await sql.unsafe(`DROP INDEX ${schema}.${k};`);
|
|
471
|
+
}
|
|
472
|
+
}
|
|
396
473
|
}
|
|
397
474
|
|
|
398
475
|
// --- 外键同步 ---
|
package/package.json
CHANGED
package/postgres/connection.js
CHANGED
|
@@ -51,6 +51,7 @@ const errorFields = {
|
|
|
51
51
|
|
|
52
52
|
function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose = noop } = {}) {
|
|
53
53
|
const {
|
|
54
|
+
sslnegotiation,
|
|
54
55
|
ssl,
|
|
55
56
|
max,
|
|
56
57
|
user,
|
|
@@ -77,6 +78,7 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
|
|
|
77
78
|
|
|
78
79
|
let socket = null
|
|
79
80
|
, cancelMessage
|
|
81
|
+
, errorResponse = null
|
|
80
82
|
, result = new Result()
|
|
81
83
|
, incoming = Buffer.alloc(0)
|
|
82
84
|
, needsTypes = options.fetch_types
|
|
@@ -84,7 +86,7 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
|
|
|
84
86
|
, statements = {}
|
|
85
87
|
, statementId = Math.random().toString(36).slice(2)
|
|
86
88
|
, statementCount = 1
|
|
87
|
-
,
|
|
89
|
+
, closedTime = 0
|
|
88
90
|
, remaining = 0
|
|
89
91
|
, hostIndex = 0
|
|
90
92
|
, retries = 0
|
|
@@ -155,6 +157,9 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
|
|
|
155
157
|
if (terminated)
|
|
156
158
|
return queryError(q, Errors.connection('CONNECTION_DESTROYED', options))
|
|
157
159
|
|
|
160
|
+
if (stream)
|
|
161
|
+
return queryError(q, Errors.generic('COPY_IN_PROGRESS', 'You cannot execute queries during copy'))
|
|
162
|
+
|
|
158
163
|
if (q.cancelled)
|
|
159
164
|
return
|
|
160
165
|
|
|
@@ -259,25 +264,29 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
|
|
|
259
264
|
}
|
|
260
265
|
|
|
261
266
|
async function secure() {
|
|
262
|
-
|
|
263
|
-
|
|
267
|
+
if (sslnegotiation !== 'direct') {
|
|
268
|
+
write(SSLRequest)
|
|
269
|
+
const canSSL = await new Promise(r => socket.once('data', x => r(x[0] === 83))) // S
|
|
264
270
|
|
|
265
|
-
|
|
266
|
-
|
|
271
|
+
if (!canSSL && ssl === 'prefer')
|
|
272
|
+
return connected()
|
|
273
|
+
}
|
|
267
274
|
|
|
268
|
-
|
|
269
|
-
socket = tls.connect({
|
|
275
|
+
const options = {
|
|
270
276
|
socket,
|
|
271
|
-
servername: net.isIP(socket.host) ? undefined : socket.host
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
277
|
+
servername: net.isIP(socket.host) ? undefined : socket.host
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
if (sslnegotiation === 'direct')
|
|
281
|
+
options.ALPNProtocols = ['postgresql']
|
|
282
|
+
|
|
283
|
+
if (ssl === 'require' || ssl === 'allow' || ssl === 'prefer')
|
|
284
|
+
options.rejectUnauthorized = false
|
|
285
|
+
else if (typeof ssl === 'object')
|
|
286
|
+
Object.assign(options, ssl)
|
|
287
|
+
|
|
288
|
+
socket.removeAllListeners()
|
|
289
|
+
socket = tls.connect(options)
|
|
281
290
|
socket.on('secureConnect', connected)
|
|
282
291
|
socket.on('error', error)
|
|
283
292
|
socket.on('close', closed)
|
|
@@ -350,7 +359,7 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
|
|
|
350
359
|
}
|
|
351
360
|
|
|
352
361
|
function reconnect() {
|
|
353
|
-
setTimeout(connect,
|
|
362
|
+
setTimeout(connect, closedTime ? Math.max(0, closedTime + delay - performance.now()) : 0)
|
|
354
363
|
}
|
|
355
364
|
|
|
356
365
|
function connected() {
|
|
@@ -442,7 +451,7 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
|
|
|
442
451
|
return reconnect()
|
|
443
452
|
|
|
444
453
|
!hadError && (query || sent.length) && error(Errors.connection('CONNECTION_CLOSED', options, socket))
|
|
445
|
-
|
|
454
|
+
closedTime = performance.now()
|
|
446
455
|
hadError && options.shared.retries++
|
|
447
456
|
delay = (typeof backoff === 'function' ? backoff(options.shared.retries) : backoff) * 1000
|
|
448
457
|
onclose(connection, Errors.connection('CONNECTION_CLOSED', options, socket))
|
|
@@ -524,8 +533,21 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
|
|
|
524
533
|
}
|
|
525
534
|
|
|
526
535
|
function ReadyForQuery(x) {
|
|
527
|
-
|
|
528
|
-
|
|
536
|
+
if (query) {
|
|
537
|
+
if (errorResponse) {
|
|
538
|
+
query.retried
|
|
539
|
+
? errored(query.retried)
|
|
540
|
+
: query.prepared && retryRoutines.has(errorResponse.routine)
|
|
541
|
+
? retry(query, errorResponse)
|
|
542
|
+
: errored(errorResponse)
|
|
543
|
+
} else {
|
|
544
|
+
query.resolve(results || result)
|
|
545
|
+
}
|
|
546
|
+
} else if (errorResponse) {
|
|
547
|
+
errored(errorResponse)
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
query = results = errorResponse = null
|
|
529
551
|
result = new Result()
|
|
530
552
|
connectTimer.cancel()
|
|
531
553
|
|
|
@@ -590,8 +612,6 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
|
|
|
590
612
|
result.count && query.cursorFn(result)
|
|
591
613
|
write(Sync)
|
|
592
614
|
}
|
|
593
|
-
|
|
594
|
-
query.resolve(result)
|
|
595
615
|
}
|
|
596
616
|
|
|
597
617
|
function ParseComplete() {
|
|
@@ -790,13 +810,12 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
|
|
|
790
810
|
}
|
|
791
811
|
|
|
792
812
|
function ErrorResponse(x) {
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
: errored(error)
|
|
813
|
+
if (query) {
|
|
814
|
+
(query.cursorFn || query.describeFirst) && write(Sync)
|
|
815
|
+
errorResponse = Errors.postgres(parseError(x))
|
|
816
|
+
} else {
|
|
817
|
+
errored(Errors.postgres(parseError(x)))
|
|
818
|
+
}
|
|
800
819
|
}
|
|
801
820
|
|
|
802
821
|
function retry(q, error) {
|
|
@@ -849,6 +868,7 @@ function Connection(options, queues = {}, { onopen = noop, onend = noop, onclose
|
|
|
849
868
|
final(callback) {
|
|
850
869
|
socket.write(b().c().end())
|
|
851
870
|
final = callback
|
|
871
|
+
stream = null
|
|
852
872
|
}
|
|
853
873
|
})
|
|
854
874
|
query.resolve(stream)
|
package/postgres/index.js
CHANGED
|
@@ -446,8 +446,9 @@ function parseOptions(a, b) {
|
|
|
446
446
|
|
|
447
447
|
const ints = ['idle_timeout', 'connect_timeout', 'max_lifetime', 'max_pipeline', 'backoff', 'keep_alive']
|
|
448
448
|
const defaults = {
|
|
449
|
-
max : 10,
|
|
449
|
+
max : globalThis.Cloudflare ? 3 : 10,
|
|
450
450
|
ssl : false,
|
|
451
|
+
sslnegotiation : null,
|
|
451
452
|
idle_timeout : null,
|
|
452
453
|
connect_timeout : 30,
|
|
453
454
|
max_lifetime : max_lifetime,
|
package/test/test-db.js
CHANGED
|
@@ -135,6 +135,10 @@ const User = {
|
|
|
135
135
|
role: {
|
|
136
136
|
type: dataTypes.STRING(16),
|
|
137
137
|
default: 'user'
|
|
138
|
+
},
|
|
139
|
+
number: {
|
|
140
|
+
type: dataTypes.INT,
|
|
141
|
+
default: 1
|
|
138
142
|
}
|
|
139
143
|
},
|
|
140
144
|
|
|
@@ -286,6 +290,16 @@ db.add(User)
|
|
|
286
290
|
let result = await tx.model('User').where(tx.sql`level > 10`).returning('*').update(data)
|
|
287
291
|
console.log('test update returning *', result)
|
|
288
292
|
|
|
293
|
+
console.log('test for update sql fragment')
|
|
294
|
+
let upd_result = await tx.model('User')
|
|
295
|
+
.where('1=1')
|
|
296
|
+
.returning(['id', 'username', 'number'])
|
|
297
|
+
.update({
|
|
298
|
+
number: tx.sql`number + ${(Math.random() * 20) | 0}`
|
|
299
|
+
}, false);
|
|
300
|
+
|
|
301
|
+
console.log(upd_result.count, upd_result.columns, upd_result)
|
|
302
|
+
|
|
289
303
|
let sex = 3
|
|
290
304
|
console.log(
|
|
291
305
|
'test condition or',
|