keuss 1.7.4 → 2.0.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/Pipeline/Queue.js +0 -1
- package/QFactory.js +11 -6
- package/Queue.js +1 -4
- package/README.md +2 -1
- package/TODO +0 -9
- package/backends/bucket-mongo-safe.js +11 -11
- package/backends/intraorder.js +13 -11
- package/backends/mongo.js +12 -9
- package/backends/pl-mongo.js +8 -5
- package/backends/postgres.js +380 -0
- package/backends/ps-mongo.js +11 -8
- package/backends/redis-list.js +8 -8
- package/backends/redis-oq.js +8 -3
- package/backends/stream-mongo.js +10 -15
- package/package.json +12 -8
- package/stats/mem.js +0 -1
- package/.github/workflows/codeql-analysis.yml +0 -72
- package/bench/all-mongo.js +0 -108
- package/bench/multi-q.js +0 -85
- package/bench/redis-oq-consumer-producer.js +0 -52
- package/bench/redis-oq-consumer.js +0 -40
- package/bench/redis-oq-producer.js +0 -43
- package/docker-compose/docker-compose.yaml +0 -18
- package/docker-compose.yaml +0 -18
- package/examples/pipelines/builder/index.js +0 -99
- package/examples/pipelines/fromRecipe/index.js +0 -127
- package/examples/pipelines/simplest/index.js +0 -38
- package/examples/pipelines/simulation-fork/index.js +0 -115
- package/examples/snippets/01-simplest-pop-push.js +0 -49
- package/examples/snippets/02-simplest-reserve-rollback-commit.js +0 -44
- package/examples/snippets/03-simplest-producer-consumer-loops.js +0 -77
- package/examples/snippets/04-bucket-mongo-safe-insert-reserve-commit.js +0 -78
- package/examples/snippets/05-insert-reserve-rollback-deadletter.js +0 -105
- package/examples/snippets/06-random-consumer-producer.js +0 -270
- package/examples/snippets/07-stream-simple.js +0 -53
- package/examples/snippets/redislabs-consumer-producer.js +0 -44
- package/examples/snippets/with-redis-stats-and-signaller-consumer-producer.js +0 -52
- package/examples/webhooks/README.md +0 -36
- package/examples/webhooks/app.js +0 -70
- package/examples/webhooks/consumer.js +0 -98
- package/examples/webhooks/index.js +0 -55
- package/examples/webhooks/package-lock.json +0 -500
- package/examples/webhooks/package.json +0 -23
- package/examples/webhooks/wh-payload.json +0 -38
- package/playground/irc.js +0 -53
- package/playground/pl-rollback.js +0 -55
- package/playground/pl1.js +0 -74
- package/playground/q1.js +0 -34
- package/playground/simple-pl.js +0 -42
- package/playground/stream-loops.js +0 -114
- package/test/backends_bucket-at-least-once.js +0 -302
- package/test/backends_deadletter.js +0 -227
- package/test/backends_payload.js +0 -542
- package/test/backends_push-pop.js +0 -170
- package/test/backends_remove.js +0 -320
- package/test/backends_reserve-commit-rollback.js +0 -1033
- package/test/intraorder.js +0 -325
- package/test/pause.js +0 -220
- package/test/pipeline-Builder.js +0 -285
- package/test/pipeline-ChoiceLink.js +0 -241
- package/test/pipeline-DirectLink.js +0 -376
- package/test/pipeline-Sink.js +0 -175
- package/test/signal.js +0 -196
- package/test/stats.js +0 -296
- package/test/stream-mongo.js +0 -166
package/Pipeline/Queue.js
CHANGED
package/QFactory.js
CHANGED
|
@@ -36,12 +36,17 @@ class QFactory {
|
|
|
36
36
|
this._stats_factory = res[1];
|
|
37
37
|
|
|
38
38
|
if (this._opts.deadletter) {
|
|
39
|
-
this._deadletter_queue = this.queue (this._opts.deadletter.queue || '__deadletter__');
|
|
40
39
|
this._max_ko = this._opts.deadletter.max_ko || 0;
|
|
41
|
-
|
|
40
|
+
const qname = this._opts.deadletter.queue || '__deadletter__';
|
|
41
|
+
debug('%s: uses deadletter queue %s, max KO is %d', this._name, qname, this._max_ko);
|
|
42
|
+
this.queue (qname, (err, dlq) => {
|
|
43
|
+
this._deadletter_queue = dlq;
|
|
44
|
+
cb (err);
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
cb();
|
|
42
49
|
}
|
|
43
|
-
|
|
44
|
-
cb();
|
|
45
50
|
});
|
|
46
51
|
}
|
|
47
52
|
|
|
@@ -53,8 +58,8 @@ class QFactory {
|
|
|
53
58
|
return this._stats_factory;
|
|
54
59
|
}
|
|
55
60
|
|
|
56
|
-
queue (name, opts) {
|
|
57
|
-
return
|
|
61
|
+
queue (name, opts, cb) {
|
|
62
|
+
return cb ();
|
|
58
63
|
}
|
|
59
64
|
|
|
60
65
|
close (cb) {
|
package/Queue.js
CHANGED
|
@@ -342,8 +342,6 @@ class Queue {
|
|
|
342
342
|
//////////////////////////////////
|
|
343
343
|
// obtain element from queue
|
|
344
344
|
pop (cid, opts, callback) {
|
|
345
|
-
// TODO fail if too many consumers?
|
|
346
|
-
|
|
347
345
|
if (this._drained) return setImmediate (() => {
|
|
348
346
|
debug ('%s: pop while drained, return error', this._name);
|
|
349
347
|
if (callback) callback ('drain');
|
|
@@ -461,7 +459,7 @@ class Queue {
|
|
|
461
459
|
if (
|
|
462
460
|
(obj.tries) && // only if we got tries
|
|
463
461
|
(this._factory.deadletter_queue ()) && // AND the factory has a deadletter queue
|
|
464
|
-
(this._factory.max_ko ()) && // AND
|
|
462
|
+
(this._factory.max_ko ()) && // AND there's a max ko attempts
|
|
465
463
|
(obj.tries > this._factory.max_ko ()) && // AND we got enough tries
|
|
466
464
|
(this.name () != '__deadletter__') // and this queue is not deadletter already
|
|
467
465
|
) {
|
|
@@ -592,7 +590,6 @@ class Queue {
|
|
|
592
590
|
}
|
|
593
591
|
}
|
|
594
592
|
|
|
595
|
-
// TODO eat up errors?
|
|
596
593
|
if (err) {
|
|
597
594
|
// get/reserve in error
|
|
598
595
|
debug ('%s - tid %s: getOrReserve_cb in error: err %o', this._name, consumer.tid, err);
|
package/README.md
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# Keuss
|
|
2
|
-
|
|
2
|
+
|
|
3
|
+
Enterprise-grade Job Queues for node.js backed by redis, MongoDB or PostgreSQL
|
|
3
4
|
|
|
4
5
|
* [Quickstart](https://pepmartinez.github.io/keuss/docs/quickstart)
|
|
5
6
|
* [Documentation](https://pepmartinez.github.io/keuss/docs/)
|
package/TODO
CHANGED
|
@@ -8,16 +8,7 @@ prio 1
|
|
|
8
8
|
* pipelines
|
|
9
9
|
+ add some commonplace transforms (etl-like)
|
|
10
10
|
+ create a server for that, allow processor code defined externally
|
|
11
|
-
* documentation
|
|
12
|
-
+ blog entries:
|
|
13
|
-
- motivations on using mongodb+redis. comparatives, pros&cons
|
|
14
|
-
- buckets
|
|
15
|
-
- pipelines
|
|
16
|
-
- tape
|
|
17
|
-
- redis-oq
|
|
18
|
-
- exotic experiments: intraorder
|
|
19
11
|
|
|
20
12
|
prio 0
|
|
21
13
|
-----------------------------------------------------------------
|
|
22
|
-
* bridges?
|
|
23
14
|
* signal queue creation or read queues on refresh?
|
|
@@ -96,8 +96,6 @@ class Bucket {
|
|
|
96
96
|
elem.mature = this._mature;
|
|
97
97
|
elem._id = this.id () + ':' + i;
|
|
98
98
|
|
|
99
|
-
// TODO optimization: set this._b[i] = null
|
|
100
|
-
|
|
101
99
|
if (is_reserve) {
|
|
102
100
|
this._b_states[i] = State.Reserved;
|
|
103
101
|
this._b_counts.Reserved++;
|
|
@@ -314,7 +312,6 @@ class BucketSet {
|
|
|
314
312
|
if (bcket.exhausted ()) {
|
|
315
313
|
debug ('bucket %s already exhausted, read another', bcket.id());
|
|
316
314
|
this._active_bucket = null;
|
|
317
|
-
// TODO loop internally!!!!!
|
|
318
315
|
cb ();
|
|
319
316
|
}
|
|
320
317
|
else {
|
|
@@ -523,9 +520,7 @@ class BucketSet {
|
|
|
523
520
|
|
|
524
521
|
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
525
522
|
class BucketMongoSafeQueue extends Queue {
|
|
526
|
-
|
|
527
523
|
/*
|
|
528
|
-
|
|
529
524
|
options:
|
|
530
525
|
bucket_max_size || 1024;
|
|
531
526
|
bucket_max_wait || 500;
|
|
@@ -540,9 +535,7 @@ class BucketMongoSafeQueue extends Queue {
|
|
|
540
535
|
//////////////////////////////////////////////
|
|
541
536
|
super (name, factory, opts, orig_opts);
|
|
542
537
|
|
|
543
|
-
this._factory = factory;
|
|
544
538
|
this._col = factory._db.collection (name);
|
|
545
|
-
this._ensureIndexes (err => {});
|
|
546
539
|
|
|
547
540
|
this._insert_bucket = {
|
|
548
541
|
_id: new mongo.ObjectID (),
|
|
@@ -821,7 +814,7 @@ class BucketMongoSafeQueue extends Queue {
|
|
|
821
814
|
|
|
822
815
|
/////////////////////////////////////////
|
|
823
816
|
_ensureIndexes (cb) {
|
|
824
|
-
this._col.createIndex ({mature : 1}, err => cb (err));
|
|
817
|
+
this._col.createIndex ({mature : 1}, err => cb (err, this));
|
|
825
818
|
}
|
|
826
819
|
|
|
827
820
|
|
|
@@ -915,10 +908,17 @@ class Factory extends QFactory_MongoDB_defaults {
|
|
|
915
908
|
this._db = mongo_conn.db();
|
|
916
909
|
}
|
|
917
910
|
|
|
918
|
-
queue (name, opts) {
|
|
919
|
-
|
|
911
|
+
queue (name, opts, cb) {
|
|
912
|
+
if (!cb) {
|
|
913
|
+
cb = opts;
|
|
914
|
+
opts = {};
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
const full_opts = {};
|
|
920
918
|
_.merge(full_opts, this._opts, opts);
|
|
921
|
-
|
|
919
|
+
|
|
920
|
+
const q = new BucketMongoSafeQueue (name, this, full_opts, opts);
|
|
921
|
+
q._ensureIndexes (cb);
|
|
922
922
|
}
|
|
923
923
|
|
|
924
924
|
close (cb) {
|
package/backends/intraorder.js
CHANGED
|
@@ -12,14 +12,10 @@ const debug = require('debug')('keuss:Queue:intraqueue');
|
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
class IntraOrderedQueue extends Queue {
|
|
15
|
-
|
|
16
15
|
//////////////////////////////////////////////
|
|
17
16
|
constructor (name, factory, opts, orig_opts) {
|
|
18
17
|
super (name, factory, opts, orig_opts);
|
|
19
|
-
|
|
20
|
-
this._factory = factory;
|
|
21
18
|
this._col = factory._db.collection (name);
|
|
22
|
-
this.ensureIndexes (err => {});
|
|
23
19
|
}
|
|
24
20
|
|
|
25
21
|
|
|
@@ -49,7 +45,6 @@ class IntraOrderedQueue extends Queue {
|
|
|
49
45
|
this._col.updateOne (q, upd, opts, (err, result) => {
|
|
50
46
|
debug ('insert: updateOne (%j, %j, %j) => (%j, %j)', q, upd, opts, err, result);
|
|
51
47
|
if (err) return callback (err);
|
|
52
|
-
// TODO result.insertedCount must be 1
|
|
53
48
|
callback (null, result.upsertedId || q._id);
|
|
54
49
|
});
|
|
55
50
|
}
|
|
@@ -308,10 +303,10 @@ class IntraOrderedQueue extends Queue {
|
|
|
308
303
|
|
|
309
304
|
//////////////////////////////////////////////////////////////////
|
|
310
305
|
// create needed indexes for O(1) functioning
|
|
311
|
-
|
|
306
|
+
_ensureIndexes (cb) {
|
|
312
307
|
async.series ([
|
|
313
|
-
cb => this._col.createIndex ({mature : 1, qcnt: 1}, cb),
|
|
314
|
-
], cb);
|
|
308
|
+
cb => this._col.createIndex ({mature : 1, qcnt: 1}, err => cb (err)),
|
|
309
|
+
], err => cb (err, this));
|
|
315
310
|
}
|
|
316
311
|
}
|
|
317
312
|
|
|
@@ -323,10 +318,17 @@ class Factory extends QFactory_MongoDB_defaults {
|
|
|
323
318
|
this._db = mongo_conn.db();
|
|
324
319
|
}
|
|
325
320
|
|
|
326
|
-
queue (name, opts) {
|
|
327
|
-
|
|
321
|
+
queue (name, opts, cb) {
|
|
322
|
+
if (!cb) {
|
|
323
|
+
cb = opts;
|
|
324
|
+
opts = {};
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
const full_opts = {};
|
|
328
328
|
_.merge(full_opts, this._opts, opts);
|
|
329
|
-
|
|
329
|
+
|
|
330
|
+
const q = new IntraOrderedQueue (name, this, full_opts, opts);
|
|
331
|
+
q._ensureIndexes (cb);
|
|
330
332
|
}
|
|
331
333
|
|
|
332
334
|
close (cb) {
|
package/backends/mongo.js
CHANGED
|
@@ -11,10 +11,7 @@ class SimpleMongoQueue extends Queue {
|
|
|
11
11
|
//////////////////////////////////////////////
|
|
12
12
|
constructor (name, factory, opts, orig_opts) {
|
|
13
13
|
super (name, factory, opts, orig_opts);
|
|
14
|
-
|
|
15
|
-
this._factory = factory;
|
|
16
14
|
this._col = factory._db.collection (name);
|
|
17
|
-
this.ensureIndexes (function (err) {});
|
|
18
15
|
}
|
|
19
16
|
|
|
20
17
|
|
|
@@ -33,7 +30,6 @@ class SimpleMongoQueue extends Queue {
|
|
|
33
30
|
insert (entry, callback) {
|
|
34
31
|
this._col.insertOne (entry, {}, (err, result) => {
|
|
35
32
|
if (err) return callback (err);
|
|
36
|
-
// TODO result.insertedCount must be 1
|
|
37
33
|
callback (null, result.insertedId);
|
|
38
34
|
});
|
|
39
35
|
}
|
|
@@ -213,8 +209,8 @@ class SimpleMongoQueue extends Queue {
|
|
|
213
209
|
|
|
214
210
|
//////////////////////////////////////////////////////////////////
|
|
215
211
|
// create needed indexes for O(1) functioning
|
|
216
|
-
|
|
217
|
-
this._col.createIndex ({mature : 1}, err => cb (err));
|
|
212
|
+
_ensureIndexes (cb) {
|
|
213
|
+
this._col.createIndex ({mature : 1}, err => cb (err, this));
|
|
218
214
|
}
|
|
219
215
|
};
|
|
220
216
|
|
|
@@ -226,10 +222,17 @@ class Factory extends QFactory_MongoDB_defaults {
|
|
|
226
222
|
this._db = mongo_conn.db();
|
|
227
223
|
}
|
|
228
224
|
|
|
229
|
-
queue (name, opts) {
|
|
230
|
-
|
|
225
|
+
queue (name, opts, cb) {
|
|
226
|
+
if (!cb) {
|
|
227
|
+
cb = opts;
|
|
228
|
+
opts = {};
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const full_opts = {};
|
|
231
232
|
_.merge(full_opts, this._opts, opts);
|
|
232
|
-
|
|
233
|
+
|
|
234
|
+
const q = new SimpleMongoQueue (name, this, full_opts, opts);
|
|
235
|
+
q._ensureIndexes (cb);
|
|
233
236
|
}
|
|
234
237
|
|
|
235
238
|
close (cb) {
|
package/backends/pl-mongo.js
CHANGED
|
@@ -119,18 +119,21 @@ class Factory extends QFactory_MongoDB_defaults {
|
|
|
119
119
|
|
|
120
120
|
|
|
121
121
|
///////////////////////////////////////////////////////////
|
|
122
|
-
queue (name, opts) {
|
|
123
|
-
if (!
|
|
122
|
+
queue (name, opts, cb) {
|
|
123
|
+
if (!cb) {
|
|
124
|
+
cb = opts;
|
|
125
|
+
opts = {};
|
|
126
|
+
}
|
|
124
127
|
|
|
125
|
-
|
|
126
|
-
|
|
128
|
+
const pl_name = opts.pipeline || 'default';
|
|
129
|
+
let pipeline = this._pipelines[pl_name];
|
|
127
130
|
|
|
128
131
|
if (!pipeline) {
|
|
129
132
|
this._pipelines[pl_name] = new Pipeline (pl_name, this);
|
|
130
133
|
pipeline = this._pipelines[pl_name];
|
|
131
134
|
}
|
|
132
135
|
|
|
133
|
-
return this._queue_from_pipeline (name, pipeline, opts);
|
|
136
|
+
return setImmediate (() => cb (null, this._queue_from_pipeline (name, pipeline, opts)));
|
|
134
137
|
}
|
|
135
138
|
|
|
136
139
|
|
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
const _ = require ('lodash');
|
|
2
|
+
const uuid = require ('uuid');
|
|
3
|
+
const pg = require ('pg');
|
|
4
|
+
|
|
5
|
+
const Queue = require ('../Queue');
|
|
6
|
+
const QFactory = require ('../QFactory');
|
|
7
|
+
|
|
8
|
+
const debug = require('debug')('keuss:Queue:postgres');
|
|
9
|
+
|
|
10
|
+
class PGQueue extends Queue {
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
// TODO escape table name properly , as identifier, and check for reserved names using pg_get_keywords(), if the name is in select * from pg_get_keywords() where catdesc = 'R', then it should not be used
|
|
14
|
+
//////////////////////////////////////////////
|
|
15
|
+
constructor (name, factory, opts, orig_opts) {
|
|
16
|
+
super (name, factory, opts, orig_opts);
|
|
17
|
+
this._pool = factory._pool;
|
|
18
|
+
this._tbl_name = this._name;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
/////////////////////////////////////////
|
|
23
|
+
static Type () {
|
|
24
|
+
return 'postgres:simple';
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
/////////////////////////////////////////
|
|
29
|
+
type () {
|
|
30
|
+
return 'postgres:simple';
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
//////////////////////////////////////////////
|
|
35
|
+
// ensure table and indexes exists
|
|
36
|
+
_init (cb) {
|
|
37
|
+
this._pool.query (`
|
|
38
|
+
CREATE TABLE IF NOT EXISTS ${this._tbl_name} (
|
|
39
|
+
_id VARCHAR(64) PRIMARY KEY,
|
|
40
|
+
_pl JSONB,
|
|
41
|
+
mature TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
|
42
|
+
tries INTEGER DEFAULT 0 NOT NULL,
|
|
43
|
+
reserved TIMESTAMPTZ
|
|
44
|
+
);
|
|
45
|
+
CREATE INDEX IF NOT EXISTS idx_${this._tbl_name}_mature ON ${this._tbl_name} (mature);
|
|
46
|
+
`, err => cb (err));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
/////////////////////////////////////////
|
|
51
|
+
// add element to queue
|
|
52
|
+
insert (entry, cb) {
|
|
53
|
+
const _id = entry.id || uuid.v4();
|
|
54
|
+
const tries = entry.tries || 0;
|
|
55
|
+
const mature = new Date (entry.mature);
|
|
56
|
+
|
|
57
|
+
const pl = {
|
|
58
|
+
payload: entry.payload,
|
|
59
|
+
hdrs: entry.hdrs || {}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (Buffer.isBuffer (pl.payload)) {
|
|
63
|
+
pl.payload = pl.payload.toString ('base64');
|
|
64
|
+
pl.type = 'buffer';
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
this._pool.query (`INSERT INTO ${this._tbl_name} VALUES($1, $2, $3, $4)`, [_id, pl, mature, tries], (err, res) => {
|
|
68
|
+
if (err) return cb (err);
|
|
69
|
+
cb (null, _id)
|
|
70
|
+
})
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
/////////////////////////////////////////
|
|
75
|
+
// get element from queue
|
|
76
|
+
get (cb) {
|
|
77
|
+
this._pool.query (`
|
|
78
|
+
DELETE FROM ${this._tbl_name}
|
|
79
|
+
WHERE _id = (
|
|
80
|
+
SELECT _id
|
|
81
|
+
FROM ${this._tbl_name}
|
|
82
|
+
WHERE mature < now()
|
|
83
|
+
ORDER BY mature
|
|
84
|
+
FOR UPDATE SKIP LOCKED
|
|
85
|
+
LIMIT 1
|
|
86
|
+
)
|
|
87
|
+
RETURNING *;
|
|
88
|
+
`, (err, res) => {
|
|
89
|
+
if (err) return cb (err);
|
|
90
|
+
|
|
91
|
+
if (_.size (res.rows) == 0) return cb (null, null); // not found
|
|
92
|
+
|
|
93
|
+
const pl = res.rows[0];
|
|
94
|
+
|
|
95
|
+
_.merge (pl, pl._pl);
|
|
96
|
+
delete (pl._pl);
|
|
97
|
+
|
|
98
|
+
if (pl.type == 'buffer') {
|
|
99
|
+
try {
|
|
100
|
+
pl.payload = Buffer.from (pl.payload, 'base64');
|
|
101
|
+
} catch (e) {
|
|
102
|
+
// TODO maybe log some error/Statistic?
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
cb (null, pl);
|
|
107
|
+
})
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
//////////////////////////////////
|
|
112
|
+
// reserve element: call cb (err, pl) where pl has an id
|
|
113
|
+
reserve (cb) {
|
|
114
|
+
const delay = this._opts.reserve_delay || 120;
|
|
115
|
+
|
|
116
|
+
this._pool.query (`
|
|
117
|
+
UPDATE ${this._tbl_name}
|
|
118
|
+
SET
|
|
119
|
+
tries = tries + 1,
|
|
120
|
+
mature = now() + make_interval(secs => ${delay}),
|
|
121
|
+
reserved = now()
|
|
122
|
+
WHERE _id = (
|
|
123
|
+
SELECT _id
|
|
124
|
+
FROM ${this._tbl_name}
|
|
125
|
+
WHERE mature < now()
|
|
126
|
+
ORDER BY mature
|
|
127
|
+
FOR UPDATE SKIP LOCKED
|
|
128
|
+
LIMIT 1
|
|
129
|
+
)
|
|
130
|
+
RETURNING *;
|
|
131
|
+
`, (err, res) => {
|
|
132
|
+
if (err) return cb (err);
|
|
133
|
+
|
|
134
|
+
if (_.size (res.rows) == 0) return cb (null, null); // not found
|
|
135
|
+
|
|
136
|
+
const pl = res.rows[0];
|
|
137
|
+
|
|
138
|
+
_.merge (pl, pl._pl);
|
|
139
|
+
delete (pl._pl);
|
|
140
|
+
|
|
141
|
+
// adjust tries to pre-update
|
|
142
|
+
pl.tries--;
|
|
143
|
+
|
|
144
|
+
if (pl.type == 'buffer') {
|
|
145
|
+
try {
|
|
146
|
+
pl.payload = Buffer.from (pl.payload, 'base64');
|
|
147
|
+
} catch (e) {
|
|
148
|
+
// TODO maybe log some error/Statistic?
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
cb (null, pl);
|
|
153
|
+
})
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
//////////////////////////////////
|
|
158
|
+
// commit previous reserve, by p.id
|
|
159
|
+
commit (id, cb) {
|
|
160
|
+
if (!uuid.validate (id)) return cb ('id [' + id + '] can not be used as commit id: not a valid UUID');
|
|
161
|
+
|
|
162
|
+
this._pool.query (`
|
|
163
|
+
DELETE FROM ${this._tbl_name}
|
|
164
|
+
WHERE _id = $1
|
|
165
|
+
AND reserved IS NOT NULL
|
|
166
|
+
`,
|
|
167
|
+
[id],
|
|
168
|
+
(err, res) => {
|
|
169
|
+
if (err) return cb (err);
|
|
170
|
+
cb (null, res && (res.rowCount == 1));
|
|
171
|
+
})
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
//////////////////////////////////
|
|
176
|
+
// rollback previous reserve, by p.id
|
|
177
|
+
rollback (id, next_t, cb) {
|
|
178
|
+
if (_.isFunction (next_t)) {
|
|
179
|
+
cb = next_t;
|
|
180
|
+
next_t = null;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (!uuid.validate (id)) return cb ('id [' + id + '] can not be used as rollback id: not a valid UUID');
|
|
184
|
+
|
|
185
|
+
const nxt = (next_t ? new Date (next_t) : Queue.now ());
|
|
186
|
+
|
|
187
|
+
this._pool.query (`
|
|
188
|
+
UPDATE ${this._tbl_name}
|
|
189
|
+
SET
|
|
190
|
+
reserved = NULL,
|
|
191
|
+
mature = $1
|
|
192
|
+
WHERE _id = $2
|
|
193
|
+
AND reserved IS NOT NULL
|
|
194
|
+
`,
|
|
195
|
+
[nxt, id],
|
|
196
|
+
(err, res) => {
|
|
197
|
+
if (err) return cb (err);
|
|
198
|
+
cb (null, res && (res.rowCount == 1));
|
|
199
|
+
})
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
//////////////////////////////////
|
|
204
|
+
// queue size including non-mature elements
|
|
205
|
+
totalSize (cb) {
|
|
206
|
+
this._pool.query (`
|
|
207
|
+
SELECT COUNT(*)
|
|
208
|
+
FROM ${this._tbl_name}
|
|
209
|
+
`,
|
|
210
|
+
(err, res) => {
|
|
211
|
+
if (err) return cb (err);
|
|
212
|
+
cb (null, parseInt (res.rows[0].count));
|
|
213
|
+
})
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
//////////////////////////////////
|
|
218
|
+
// queue size NOT including non-mature elements
|
|
219
|
+
size (cb) {
|
|
220
|
+
this._pool.query (`
|
|
221
|
+
SELECT COUNT(*)
|
|
222
|
+
FROM ${this._tbl_name}
|
|
223
|
+
WHERE mature < now()
|
|
224
|
+
`,
|
|
225
|
+
(err, res) => {
|
|
226
|
+
if (err) return cb (err);
|
|
227
|
+
cb (null, parseInt (res.rows[0].count));
|
|
228
|
+
})
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
//////////////////////////////////
|
|
233
|
+
// queue size of non-mature elements only
|
|
234
|
+
schedSize (cb) {
|
|
235
|
+
this._pool.query (`
|
|
236
|
+
SELECT COUNT(*)
|
|
237
|
+
FROM ${this._tbl_name}
|
|
238
|
+
WHERE mature >= now()
|
|
239
|
+
AND reserved IS NULL
|
|
240
|
+
`,
|
|
241
|
+
(err, res) => {
|
|
242
|
+
if (err) return cb (err);
|
|
243
|
+
cb (null, parseInt (res.rows[0].count));
|
|
244
|
+
})
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
//////////////////////////////////
|
|
249
|
+
// queue size of reserved elements only
|
|
250
|
+
resvSize (cb) {
|
|
251
|
+
this._pool.query (`
|
|
252
|
+
SELECT COUNT(*)
|
|
253
|
+
FROM ${this._tbl_name}
|
|
254
|
+
WHERE mature >= now()
|
|
255
|
+
AND reserved IS NOT NULL
|
|
256
|
+
`,
|
|
257
|
+
(err, res) => {
|
|
258
|
+
if (err) return cb (err);
|
|
259
|
+
cb (null, parseInt (res.rows[0].count));
|
|
260
|
+
})
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
/////////////////////////////////////////
|
|
265
|
+
// get element from queue
|
|
266
|
+
next_t (cb) {
|
|
267
|
+
this._pool.query (`
|
|
268
|
+
SELECT mature
|
|
269
|
+
FROM ${this._tbl_name}
|
|
270
|
+
ORDER BY mature
|
|
271
|
+
LIMIT 1
|
|
272
|
+
`, (err, res) => {
|
|
273
|
+
if (err) return cb (err);
|
|
274
|
+
if (_.size (res.rows) == 0) return cb (null, null); // not found
|
|
275
|
+
|
|
276
|
+
cb (null, res.rows[0].mature);
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
//////////////////////////////////////////////
|
|
282
|
+
// remove by id
|
|
283
|
+
remove (id, cb) {
|
|
284
|
+
if (!uuid.validate (id)) return cb ('id [' + id + '] can not be used as remove id: not a valid UUID');
|
|
285
|
+
|
|
286
|
+
this._pool.query (`
|
|
287
|
+
DELETE FROM ${this._tbl_name}
|
|
288
|
+
WHERE _id = $1
|
|
289
|
+
AND reserved IS NULL
|
|
290
|
+
`,
|
|
291
|
+
[id],
|
|
292
|
+
(err, res) => {
|
|
293
|
+
if (err) return cb (err);
|
|
294
|
+
cb (null, res && (res.rowCount == 1));
|
|
295
|
+
})
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
300
|
+
// private parts
|
|
301
|
+
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
307
|
+
class Factory extends QFactory {
|
|
308
|
+
constructor (opts, pg_pool) {
|
|
309
|
+
super (opts);
|
|
310
|
+
this._pool = pg_pool;
|
|
311
|
+
debug ('crated Factory with options %j', opts);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
queue (name, opts, cb) {
|
|
315
|
+
if (!cb) {
|
|
316
|
+
cb = opts;
|
|
317
|
+
opts = {};
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
const full_opts = {};
|
|
321
|
+
_.merge(full_opts, this._opts, opts);
|
|
322
|
+
debug ('creating queue with full_opts %j', full_opts);
|
|
323
|
+
|
|
324
|
+
const q = new PGQueue (name, this, full_opts, opts);
|
|
325
|
+
q._init (err => {
|
|
326
|
+
if (err) return cb (err);
|
|
327
|
+
cb (null, q);
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
close (cb) {
|
|
332
|
+
super.close (() => {
|
|
333
|
+
debug ('closing pool...');
|
|
334
|
+
this._pool.end (cb);
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
type () {
|
|
339
|
+
return PGQueue.Type ();
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
capabilities () {
|
|
343
|
+
return {
|
|
344
|
+
sched: true,
|
|
345
|
+
reserve: true,
|
|
346
|
+
pipeline: false,
|
|
347
|
+
tape: false,
|
|
348
|
+
remove: true
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
355
|
+
function creator (opts, cb) {
|
|
356
|
+
const _opts = opts || {};
|
|
357
|
+
|
|
358
|
+
debug ('Creator: creating pool with %j', _opts);
|
|
359
|
+
|
|
360
|
+
const dflt = {
|
|
361
|
+
user: 'pg',
|
|
362
|
+
password: 'pg',
|
|
363
|
+
host: 'localhost',
|
|
364
|
+
port: 5432,
|
|
365
|
+
database: 'pg'
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
const pool = new pg.Pool(_.merge ({}, dflt, _opts.postgres));
|
|
369
|
+
|
|
370
|
+
// ensure the pool can connect
|
|
371
|
+
pool.query ('select 1', err => {
|
|
372
|
+
if (err) return cb (err);
|
|
373
|
+
|
|
374
|
+
debug ('pool created, can connect. Creating Factory...');
|
|
375
|
+
const F = new Factory (_opts, pool);
|
|
376
|
+
F.async_init (err => cb (err, F));
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
module.exports = creator;
|