pixl-server-storage 4.0.3 → 4.1.1
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/README.md +81 -10
- package/engines/Couchbase.js +0 -1
- package/engines/Filesystem.js +59 -28
- package/engines/Postgres.js +515 -0
- package/engines/Redis.js +129 -29
- package/engines/RedisCluster.js +128 -29
- package/engines/S3.js +53 -25
- package/engines/SQLite.js +65 -22
- package/package.json +1 -1
- package/test/config.json +19 -0
- package/test/data/backups/backup-2026-04-05-20-23-56.db.gz +0 -0
- package/test/data/backups/backup-2026-06-08-20-33-01.db.gz +0 -0
- package/test/data/backups/backup-2026-02-15-18-10-21.db.gz +0 -0
- package/test/data/backups/backup-2026-02-15-18-10-33.db.gz +0 -0
|
@@ -0,0 +1,515 @@
|
|
|
1
|
+
// Postgres Storage Plugin
|
|
2
|
+
// Copyright (c) 2026 Joseph Huckaby
|
|
3
|
+
// Released under the MIT License
|
|
4
|
+
|
|
5
|
+
// Requires the 'pg' module
|
|
6
|
+
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
const Path = require('path');
|
|
9
|
+
const zlib = require('zlib');
|
|
10
|
+
const Class = require("pixl-class");
|
|
11
|
+
const Component = require("pixl-server/component");
|
|
12
|
+
const pg = require('pg');
|
|
13
|
+
const Tools = require("pixl-tools");
|
|
14
|
+
const Perf = require("pixl-perf");
|
|
15
|
+
const Cache = require("pixl-cache");
|
|
16
|
+
const async = require('async');
|
|
17
|
+
const noop = function() {};
|
|
18
|
+
|
|
19
|
+
module.exports = Class.create({
|
|
20
|
+
|
|
21
|
+
__name: 'Postgres',
|
|
22
|
+
__parent: Component,
|
|
23
|
+
|
|
24
|
+
defaultConfig: {
|
|
25
|
+
min: 0,
|
|
26
|
+
max: 32,
|
|
27
|
+
host: "localhost",
|
|
28
|
+
database: "",
|
|
29
|
+
user: "",
|
|
30
|
+
password: "",
|
|
31
|
+
port: 5432,
|
|
32
|
+
statement_timeout: 5000,
|
|
33
|
+
query_timeout: 6000,
|
|
34
|
+
connectionTimeoutMillis: 30000,
|
|
35
|
+
idleTimeoutMillis: 10000,
|
|
36
|
+
table: "items"
|
|
37
|
+
},
|
|
38
|
+
|
|
39
|
+
startup: function(callback) {
|
|
40
|
+
// setup Postgres connection
|
|
41
|
+
var self = this;
|
|
42
|
+
this.logDebug(2, "Setting up Postgres", Tools.copyHashRemoveKeys( this.config.get(), { password:1 }) );
|
|
43
|
+
this.setup(callback);
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
setup: function(callback) {
|
|
47
|
+
// setup Postgres connection
|
|
48
|
+
var self = this;
|
|
49
|
+
var pg_config = this.config.get();
|
|
50
|
+
|
|
51
|
+
this.commands = {
|
|
52
|
+
create: `CREATE TABLE IF NOT EXISTS ${pg_config.table}( key TEXT PRIMARY KEY, value BYTEA NOT NULL, modified BIGINT NOT NULL )`,
|
|
53
|
+
get: `SELECT modified, value FROM ${pg_config.table} WHERE key = $key LIMIT 1`,
|
|
54
|
+
head: `SELECT modified, octet_length(value) AS len FROM ${pg_config.table} WHERE key = $key LIMIT 1`,
|
|
55
|
+
put: `INSERT INTO ${pg_config.table} (key, value, modified) VALUES ($key, $value, $now) ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value, modified = EXCLUDED.modified`,
|
|
56
|
+
delete: `DELETE FROM ${pg_config.table} WHERE key = $key`
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
// optional LRU cache
|
|
60
|
+
this.cache = null;
|
|
61
|
+
var cache_opts = pg_config.cache;
|
|
62
|
+
if (cache_opts && cache_opts.enabled) {
|
|
63
|
+
this.logDebug(3, "Setting up LRU cache", cache_opts);
|
|
64
|
+
this.cache = new Cache( Tools.copyHashRemoveKeys(cache_opts, { enabled: 1 }) );
|
|
65
|
+
this.cache.on('expire', function(item, reason) {
|
|
66
|
+
self.logDebug(9, "Expiring LRU cache object: " + item.key + " due to: " + reason, {
|
|
67
|
+
key: item.key,
|
|
68
|
+
reason: reason,
|
|
69
|
+
totalCount: self.cache.count,
|
|
70
|
+
totalBytes: self.cache.bytes
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// pass entire config to pg.Pool, sans our custom keys
|
|
76
|
+
var db = this.db = new pg.Pool(
|
|
77
|
+
Tools.copyHashRemoveKeys( pg_config, { cache: 1, table: 1 })
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
db.on('error', function(err) {
|
|
81
|
+
self.logError('db', "DB Error: " + err, err);
|
|
82
|
+
if (callback) { callback(err); callback = null; }
|
|
83
|
+
});
|
|
84
|
+
db.on('connect', function(client) {
|
|
85
|
+
self.logDebug(9, "Opened DB pool connection");
|
|
86
|
+
});
|
|
87
|
+
db.on('remove', function(client) {
|
|
88
|
+
self.logDebug(9, "Closed DB pool connection");
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
// create initial table
|
|
92
|
+
this.logDebug(3, "Creating initial table: " + pg_config.table);
|
|
93
|
+
this.query( this.commands.create, {}, function(err, res) {
|
|
94
|
+
if (err) self.logError('db', "Failed to create initial table: " + err, err);
|
|
95
|
+
else self.logDebug(3, "Setup complete");
|
|
96
|
+
if (callback) { callback(err); callback = null; }
|
|
97
|
+
} );
|
|
98
|
+
},
|
|
99
|
+
|
|
100
|
+
query(sql, args, callback) {
|
|
101
|
+
// run and log db query, and error if applicable
|
|
102
|
+
// allow for named variables in sql, expand to numbered $ variables for pg
|
|
103
|
+
var self = this;
|
|
104
|
+
|
|
105
|
+
// perform $ placeholder substitution to populate sargs array
|
|
106
|
+
var sargs = [];
|
|
107
|
+
sql = sql.replace(/\$(\w+)/g, function(m_all, name) {
|
|
108
|
+
var value = args[name];
|
|
109
|
+
sargs.push(value);
|
|
110
|
+
value = '$' + sargs.length;
|
|
111
|
+
return value;
|
|
112
|
+
} );
|
|
113
|
+
|
|
114
|
+
this.db.query( sql, sargs, function(err, res) {
|
|
115
|
+
if (err) {
|
|
116
|
+
self.logError('db', "Failed to execute SQL: " + err, { sql, err });
|
|
117
|
+
return callback(err);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
self.logDebug(9, "SQL query completed successfully", {
|
|
121
|
+
command: res.command || '',
|
|
122
|
+
rowCount: res.rowCount || 0
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
callback(null, res);
|
|
126
|
+
} ); // db.query
|
|
127
|
+
},
|
|
128
|
+
|
|
129
|
+
prepKey: function(key) {
|
|
130
|
+
// prepare key (no-op)
|
|
131
|
+
return key;
|
|
132
|
+
},
|
|
133
|
+
|
|
134
|
+
put: function(key, value, callback) {
|
|
135
|
+
// store key+value in Postgres
|
|
136
|
+
var self = this;
|
|
137
|
+
var now = Tools.timeNow(true);
|
|
138
|
+
key = this.prepKey(key);
|
|
139
|
+
|
|
140
|
+
var is_binary = this.storage.isBinaryKey(key);
|
|
141
|
+
|
|
142
|
+
if (is_binary) {
|
|
143
|
+
this.logDebug(9, "Storing Postgres Binary Object: " + key, '' + value.length + ' bytes');
|
|
144
|
+
}
|
|
145
|
+
else {
|
|
146
|
+
this.logDebug(9, "Storing Postgres JSON Object: " + key, this.debugLevel(10) ? value : null);
|
|
147
|
+
value = Buffer.from( JSON.stringify( value ) );
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
this.query( this.commands.put, { key: key, value: value, now: now }, function(err, res) {
|
|
151
|
+
if (err) {
|
|
152
|
+
err.message = "Failed to store object: " + key + ": " + err;
|
|
153
|
+
self.logError('pg', '' + err);
|
|
154
|
+
if (callback) callback(err);
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
self.logDebug(9, "Store complete: " + key);
|
|
159
|
+
// possibly cache in LRU
|
|
160
|
+
if (self.cache && !is_binary) {
|
|
161
|
+
self.cache.set( key, value, { date: now } );
|
|
162
|
+
}
|
|
163
|
+
if (callback) callback(null);
|
|
164
|
+
} ); // query
|
|
165
|
+
},
|
|
166
|
+
|
|
167
|
+
putStream: function(key, inp, callback) {
|
|
168
|
+
// store key+value in Postgres using read stream
|
|
169
|
+
var self = this;
|
|
170
|
+
|
|
171
|
+
// The Postgres API has no stream support.
|
|
172
|
+
// So, we have to do this the RAM-hard way...
|
|
173
|
+
|
|
174
|
+
var chunks = [];
|
|
175
|
+
var done = false;
|
|
176
|
+
var finish = function(err) {
|
|
177
|
+
if (done) return;
|
|
178
|
+
done = true;
|
|
179
|
+
callback(err);
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
inp.on('error', function(err) {
|
|
183
|
+
err.message = "Failed to store stream: " + key + ": " + err;
|
|
184
|
+
self.logError('pg', '' + err);
|
|
185
|
+
finish(err);
|
|
186
|
+
} );
|
|
187
|
+
inp.on('data', function(chunk) {
|
|
188
|
+
if (done) return;
|
|
189
|
+
chunks.push( chunk );
|
|
190
|
+
} );
|
|
191
|
+
inp.on('end', function() {
|
|
192
|
+
if (done) return;
|
|
193
|
+
var buf = Buffer.concat(chunks);
|
|
194
|
+
self.put( key, buf, finish );
|
|
195
|
+
} );
|
|
196
|
+
},
|
|
197
|
+
|
|
198
|
+
head: function(key, callback) {
|
|
199
|
+
// head pg item given key
|
|
200
|
+
var self = this;
|
|
201
|
+
key = this.prepKey(key);
|
|
202
|
+
|
|
203
|
+
// check cache first
|
|
204
|
+
if (this.cache && this.cache.has(key)) {
|
|
205
|
+
var item = this.cache.getMeta(key);
|
|
206
|
+
|
|
207
|
+
if (item.value.length == 0) {
|
|
208
|
+
process.nextTick( function() {
|
|
209
|
+
var err = new Error("Failed to head key: " + key + ": Not found");
|
|
210
|
+
err.code = "NoSuchKey";
|
|
211
|
+
callback( err );
|
|
212
|
+
} );
|
|
213
|
+
}
|
|
214
|
+
else {
|
|
215
|
+
process.nextTick( function() {
|
|
216
|
+
self.logDebug(9, "Cached head complete: " + key);
|
|
217
|
+
callback( null, {
|
|
218
|
+
mod: item.date,
|
|
219
|
+
len: item.value.length
|
|
220
|
+
} );
|
|
221
|
+
} );
|
|
222
|
+
}
|
|
223
|
+
return;
|
|
224
|
+
} // cache
|
|
225
|
+
|
|
226
|
+
this.query( this.commands.head, { key: key }, function(err, res) {
|
|
227
|
+
if (err) {
|
|
228
|
+
err.message = "Failed to head key: " + key + ": " + err;
|
|
229
|
+
self.logError('pg', '' + err);
|
|
230
|
+
callback(err);
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
var row = res.rows[0];
|
|
235
|
+
|
|
236
|
+
if (!row) {
|
|
237
|
+
var err = new Error("Failed to head key: " + key + ": Not found");
|
|
238
|
+
err.code = "NoSuchKey";
|
|
239
|
+
|
|
240
|
+
if (self.cache && !self.storage.isBinaryKey(key)) {
|
|
241
|
+
// store 'empty' stub in cache
|
|
242
|
+
self.cache.set( key, Buffer.alloc(0), { date: 0 } );
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
return callback(err, null);
|
|
246
|
+
}
|
|
247
|
+
callback(null, { mod: parseInt(row.modified, 10), len: row.len });
|
|
248
|
+
} ); // query
|
|
249
|
+
},
|
|
250
|
+
|
|
251
|
+
get: function(key, callback) {
|
|
252
|
+
// fetch Postgres value given key
|
|
253
|
+
var self = this;
|
|
254
|
+
key = this.prepKey(key);
|
|
255
|
+
|
|
256
|
+
var is_binary = this.storage.isBinaryKey(key);
|
|
257
|
+
|
|
258
|
+
// check cache first
|
|
259
|
+
if (this.cache && !is_binary && this.cache.has(key)) {
|
|
260
|
+
var item = this.cache.getMeta(key);
|
|
261
|
+
|
|
262
|
+
if (item.value.length == 0) {
|
|
263
|
+
process.nextTick( function() {
|
|
264
|
+
var err = new Error("Failed to fetch key: " + key + ": Not found");
|
|
265
|
+
err.code = "NoSuchKey";
|
|
266
|
+
callback( err );
|
|
267
|
+
} );
|
|
268
|
+
}
|
|
269
|
+
else {
|
|
270
|
+
process.nextTick( function() {
|
|
271
|
+
var data = null;
|
|
272
|
+
try { data = JSON.parse( item.value.toString() ); }
|
|
273
|
+
catch (e) {
|
|
274
|
+
self.logError('pg', "Failed to parse JSON record: " + key + ": " + e);
|
|
275
|
+
callback( e, null );
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
self.logDebug(9, "Cached JSON fetch complete: " + key, self.debugLevel(10) ? data : null);
|
|
279
|
+
|
|
280
|
+
callback( null, data );
|
|
281
|
+
} );
|
|
282
|
+
}
|
|
283
|
+
return;
|
|
284
|
+
} // cache
|
|
285
|
+
|
|
286
|
+
this.logDebug(9, "Fetching Postgres Object: " + key);
|
|
287
|
+
|
|
288
|
+
this.query( this.commands.get, { key: key }, function(err, res) {
|
|
289
|
+
if (err) {
|
|
290
|
+
err.message = "Failed to fetch key: " + key + ": " + err;
|
|
291
|
+
self.logError('pg', '' + err);
|
|
292
|
+
callback(err);
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
var row = res.rows[0];
|
|
297
|
+
|
|
298
|
+
if (!row) {
|
|
299
|
+
var err = new Error("Failed to fetch key: " + key + ": Not found");
|
|
300
|
+
err.code = "NoSuchKey";
|
|
301
|
+
|
|
302
|
+
if (self.cache && !is_binary) {
|
|
303
|
+
// store 'empty' stub in cache
|
|
304
|
+
self.cache.set( key, Buffer.alloc(0), { date: 0 } );
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
return callback(err, null);
|
|
308
|
+
}
|
|
309
|
+
if (is_binary) {
|
|
310
|
+
self.logDebug(9, "Binary fetch complete: " + key, '' + row.value.length + ' bytes');
|
|
311
|
+
return callback(null, row.value);
|
|
312
|
+
}
|
|
313
|
+
if (self.cache) {
|
|
314
|
+
self.cache.set( key, row.value, { date: parseInt(row.modified, 10) } );
|
|
315
|
+
}
|
|
316
|
+
var json = null;
|
|
317
|
+
try { json = JSON.parse( row.value.toString() ); }
|
|
318
|
+
catch (err) {
|
|
319
|
+
self.logError('pg', "Failed to parse JSON record: " + key + ": " + err);
|
|
320
|
+
return callback(err, null);
|
|
321
|
+
}
|
|
322
|
+
self.logDebug(9, "JSON fetch complete: " + key, self.debugLevel(10) ? json : null);
|
|
323
|
+
callback(null, json);
|
|
324
|
+
} ); // query
|
|
325
|
+
},
|
|
326
|
+
|
|
327
|
+
getBuffer: function(key, callback) {
|
|
328
|
+
// fetch Postgres buffer given key
|
|
329
|
+
var self = this;
|
|
330
|
+
key = this.prepKey(key);
|
|
331
|
+
|
|
332
|
+
this.logDebug(9, "Fetching Postgres Object: " + key);
|
|
333
|
+
|
|
334
|
+
this.query( this.commands.get, { key: key }, function(err, res) {
|
|
335
|
+
if (err) {
|
|
336
|
+
err.message = "Failed to fetch key: " + key + ": " + err;
|
|
337
|
+
self.logError('pg', '' + err);
|
|
338
|
+
callback(err);
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
var row = res.rows[0];
|
|
343
|
+
|
|
344
|
+
if (!row) {
|
|
345
|
+
var err = new Error("Failed to fetch key: " + key + ": Not found");
|
|
346
|
+
err.code = "NoSuchKey";
|
|
347
|
+
return callback(err, null);
|
|
348
|
+
}
|
|
349
|
+
self.logDebug(9, "Binary fetch complete: " + key, '' + row.value.length + ' bytes');
|
|
350
|
+
callback(null, row.value);
|
|
351
|
+
} ); // query
|
|
352
|
+
},
|
|
353
|
+
|
|
354
|
+
getStream: function(key, callback) {
|
|
355
|
+
// get readable stream to record value given key
|
|
356
|
+
var self = this;
|
|
357
|
+
|
|
358
|
+
// The Postgres API has no stream support.
|
|
359
|
+
// So, we have to do this the RAM-hard way...
|
|
360
|
+
|
|
361
|
+
this.query( this.commands.get, { key: this.prepKey(key) }, function(err, res) {
|
|
362
|
+
if (err) {
|
|
363
|
+
// an actual error
|
|
364
|
+
err.message = "Failed to fetch key: " + key + ": " + err;
|
|
365
|
+
self.logError('pg', '' + err);
|
|
366
|
+
return callback(err);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
var row = res.rows[0];
|
|
370
|
+
|
|
371
|
+
if (!row) {
|
|
372
|
+
// record not found
|
|
373
|
+
var err = new Error("Failed to fetch key: " + key + ": Not found");
|
|
374
|
+
err.code = "NoSuchKey";
|
|
375
|
+
return callback( err, null );
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
self.logDebug(9, "Binary fetch complete: " + key, '' + row.value.length + ' bytes');
|
|
379
|
+
var stream = new BufferStream(row.value);
|
|
380
|
+
callback(null, stream, { mod: parseInt(row.modified, 10), len: row.value.length });
|
|
381
|
+
} ); // query
|
|
382
|
+
},
|
|
383
|
+
|
|
384
|
+
getStreamRange: function(key, start, end, callback) {
|
|
385
|
+
// get readable stream to record value given key and range
|
|
386
|
+
var self = this;
|
|
387
|
+
|
|
388
|
+
// The Postgres API has no stream support.
|
|
389
|
+
// So, we have to do this the RAM-hard way...
|
|
390
|
+
|
|
391
|
+
this.query( this.commands.get, { key: this.prepKey(key) }, function(err, res) {
|
|
392
|
+
if (err) {
|
|
393
|
+
// an actual error
|
|
394
|
+
err.message = "Failed to fetch key: " + key + ": " + err;
|
|
395
|
+
self.logError('pg', '' + err);
|
|
396
|
+
return callback(err);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
var row = res.rows[0];
|
|
400
|
+
|
|
401
|
+
if (!row) {
|
|
402
|
+
// record not found
|
|
403
|
+
var err = new Error("Failed to fetch key: " + key + ": Not found");
|
|
404
|
+
err.code = "NoSuchKey";
|
|
405
|
+
return callback( err, null );
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
var buf = row.value;
|
|
409
|
+
|
|
410
|
+
// validate byte range, now that we have the head info
|
|
411
|
+
if (isNaN(start) && !isNaN(end)) {
|
|
412
|
+
start = buf.length - end;
|
|
413
|
+
end = buf.length ? buf.length - 1 : 0;
|
|
414
|
+
}
|
|
415
|
+
else if (!isNaN(start) && isNaN(end)) {
|
|
416
|
+
end = buf.length ? buf.length - 1 : 0;
|
|
417
|
+
}
|
|
418
|
+
if (isNaN(start) || isNaN(end) || (start < 0) || (start >= buf.length) || (end < start) || (end >= buf.length)) {
|
|
419
|
+
callback( new Error("Invalid byte range (" + start + '-' + end + ") for key: " + key + " (len: " + buf.length + ")"), null );
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
var range = buf.slice(start, end + 1);
|
|
424
|
+
var stream = new BufferStream(range);
|
|
425
|
+
callback(null, stream, { mod: parseInt(row.modified, 10), len: buf.length });
|
|
426
|
+
} ); // query
|
|
427
|
+
},
|
|
428
|
+
|
|
429
|
+
delete: function(key, callback) {
|
|
430
|
+
// delete Postgres key given key
|
|
431
|
+
var self = this;
|
|
432
|
+
key = this.prepKey(key);
|
|
433
|
+
|
|
434
|
+
this.logDebug(9, "Deleting Postgres Object: " + key);
|
|
435
|
+
|
|
436
|
+
this.query( this.commands.delete, { key: key }, function(err, res) {
|
|
437
|
+
if (err) {
|
|
438
|
+
self.logError('pg', "Failed to delete object: " + key + ": " + err);
|
|
439
|
+
callback(err);
|
|
440
|
+
return;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
if (!res.rowCount) {
|
|
444
|
+
var err = new Error("Failed to delete object: " + key + ": Not found");
|
|
445
|
+
err.code = "NoSuchKey";
|
|
446
|
+
|
|
447
|
+
if (self.cache && !self.storage.isBinaryKey(key)) {
|
|
448
|
+
// store 'empty' stub in cache
|
|
449
|
+
self.cache.set( key, Buffer.alloc(0), { date: 0 } );
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
return callback(err);
|
|
453
|
+
}
|
|
454
|
+
self.logDebug(9, "Delete complete: " + key);
|
|
455
|
+
|
|
456
|
+
// possibly "delete" from LRU cache as well
|
|
457
|
+
if (self.cache && !self.storage.isBinaryKey(key)) {
|
|
458
|
+
// store 'empty' stub in cache
|
|
459
|
+
self.cache.set( key, Buffer.alloc(0), { date: 0 } );
|
|
460
|
+
}
|
|
461
|
+
callback();
|
|
462
|
+
} ); // query
|
|
463
|
+
},
|
|
464
|
+
|
|
465
|
+
runMaintenance: function(callback) {
|
|
466
|
+
// run daily maintenance
|
|
467
|
+
callback();
|
|
468
|
+
},
|
|
469
|
+
|
|
470
|
+
shutdown: function(callback) {
|
|
471
|
+
// shutdown storage
|
|
472
|
+
var self = this;
|
|
473
|
+
this.logDebug(2, "Shutting down Postgres");
|
|
474
|
+
|
|
475
|
+
if (this.db) {
|
|
476
|
+
this.db.end( function(err) {
|
|
477
|
+
if (err) self.logError('pg', "Failed to shutdown database cleanly: " + err);
|
|
478
|
+
else self.logDebug(3, "Shutdown complete");
|
|
479
|
+
delete self.db;
|
|
480
|
+
callback();
|
|
481
|
+
} );
|
|
482
|
+
}
|
|
483
|
+
else {
|
|
484
|
+
this.logDebug(3, "Shutdown complete");
|
|
485
|
+
callback();
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
});
|
|
490
|
+
|
|
491
|
+
// Modified the following snippet from node-streamifier:
|
|
492
|
+
// Copyright (c) 2014 Gabriel Llamas, MIT Licensed
|
|
493
|
+
|
|
494
|
+
var util = require('util');
|
|
495
|
+
var stream = require('stream');
|
|
496
|
+
|
|
497
|
+
var BufferStream = function (object, options) {
|
|
498
|
+
if (object instanceof Buffer || typeof object === 'string') {
|
|
499
|
+
options = options || {};
|
|
500
|
+
stream.Readable.call(this, {
|
|
501
|
+
highWaterMark: options.highWaterMark,
|
|
502
|
+
encoding: options.encoding
|
|
503
|
+
});
|
|
504
|
+
} else {
|
|
505
|
+
stream.Readable.call(this, { objectMode: true });
|
|
506
|
+
}
|
|
507
|
+
this._object = object;
|
|
508
|
+
};
|
|
509
|
+
|
|
510
|
+
util.inherits(BufferStream, stream.Readable);
|
|
511
|
+
|
|
512
|
+
BufferStream.prototype._read = function () {
|
|
513
|
+
this.push(this._object);
|
|
514
|
+
this._object = null;
|
|
515
|
+
};
|