@depup/pg-promise 12.6.2-depup.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.
Files changed (51) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +25 -0
  3. package/lib/assert.js +10 -0
  4. package/lib/connect.js +178 -0
  5. package/lib/context.js +93 -0
  6. package/lib/database-pool.js +115 -0
  7. package/lib/database.js +1643 -0
  8. package/lib/errors/README.md +13 -0
  9. package/lib/errors/index.js +51 -0
  10. package/lib/errors/parameterized-query-error.js +71 -0
  11. package/lib/errors/prepared-statement-error.js +71 -0
  12. package/lib/errors/query-file-error.js +75 -0
  13. package/lib/errors/query-result-error.js +157 -0
  14. package/lib/events.js +543 -0
  15. package/lib/formatting.js +932 -0
  16. package/lib/helpers/README.md +10 -0
  17. package/lib/helpers/column-set.js +614 -0
  18. package/lib/helpers/column.js +406 -0
  19. package/lib/helpers/index.js +75 -0
  20. package/lib/helpers/methods/concat.js +103 -0
  21. package/lib/helpers/methods/index.js +13 -0
  22. package/lib/helpers/methods/insert.js +151 -0
  23. package/lib/helpers/methods/sets.js +81 -0
  24. package/lib/helpers/methods/update.js +248 -0
  25. package/lib/helpers/methods/values.js +116 -0
  26. package/lib/helpers/table-name.js +175 -0
  27. package/lib/index.js +29 -0
  28. package/lib/inner-state.js +39 -0
  29. package/lib/main.js +394 -0
  30. package/lib/patterns.js +43 -0
  31. package/lib/query-file.js +379 -0
  32. package/lib/query-result.js +39 -0
  33. package/lib/query.js +273 -0
  34. package/lib/special-query.js +30 -0
  35. package/lib/stream.js +125 -0
  36. package/lib/task.js +404 -0
  37. package/lib/text.js +40 -0
  38. package/lib/tx-mode.js +194 -0
  39. package/lib/types/index.js +18 -0
  40. package/lib/types/parameterized-query.js +247 -0
  41. package/lib/types/prepared-statement.js +298 -0
  42. package/lib/types/server-formatting.js +92 -0
  43. package/lib/utils/README.md +13 -0
  44. package/lib/utils/color.js +68 -0
  45. package/lib/utils/index.js +199 -0
  46. package/lib/utils/public.js +312 -0
  47. package/package.json +77 -0
  48. package/typescript/README.md +63 -0
  49. package/typescript/pg-promise.d.ts +728 -0
  50. package/typescript/pg-subset.d.ts +359 -0
  51. package/typescript/tslint.json +21 -0
@@ -0,0 +1,10 @@
1
+ ### `helpers` namespace
2
+
3
+ This folder contains everything that's available via the [helpers] namespace, after initializing the library:
4
+
5
+ ```js
6
+ const pgp = require('pg-promise')(/*initialization options*/);
7
+ const helpers = pgp.helpers; // `helpers` namespace
8
+ ```
9
+
10
+ [helpers]:http://vitaly-t.github.io/pg-promise/helpers.html
@@ -0,0 +1,614 @@
1
+ /*
2
+ * Copyright (c) 2015-present, Vitaly Tomilov
3
+ *
4
+ * See the LICENSE file at the top-level directory of this distribution
5
+ * for licensing information.
6
+ *
7
+ * Removal or modification of this copyright notice is prohibited.
8
+ */
9
+
10
+ const {InnerState} = require('../inner-state');
11
+ const {assert} = require('../assert');
12
+ const {TableName} = require('./table-name');
13
+ const {Column} = require('./column');
14
+
15
+ const npm = {
16
+ os: require('os'),
17
+ utils: require('../utils'),
18
+ formatting: require('../formatting')
19
+ };
20
+
21
+ /**
22
+ * @class helpers.ColumnSet
23
+ * @description
24
+ * Performance-optimized, read-only structure with query-formatting columns.
25
+ *
26
+ * In order to avail from performance optimization provided by this class, it should be created
27
+ * only once, statically, and then reused.
28
+ *
29
+ * @param {object|helpers.Column|array} columns
30
+ * Columns information object, depending on the type:
31
+ *
32
+ * - When it is a simple object, its properties are enumerated to represent both column names and property names
33
+ * within the source objects. See also option `inherit` that's applicable in this case.
34
+ *
35
+ * - When it is a single {@link helpers.Column Column} object, property {@link helpers.ColumnSet#columns columns} is initialized with
36
+ * just a single column. It is not a unique situation when only a single column is required for an update operation.
37
+ *
38
+ * - When it is an array, each element is assumed to represent details for a column. If the element is already of type {@link helpers.Column Column},
39
+ * it is used directly; otherwise the element is passed into {@link helpers.Column Column} constructor for initialization.
40
+ * On any duplicate column name (case-sensitive) it will throw {@link external:Error Error} = `Duplicate column name "name".`
41
+ *
42
+ * - When it is none of the above, it will throw {@link external:TypeError TypeError} = `Invalid parameter 'columns' specified.`
43
+ *
44
+ * @param {object} [options]
45
+ *
46
+ * @param {helpers.TableName|Table|string} [options.table]
47
+ * Table details.
48
+ *
49
+ * When it is a non-null value, and not a {@link helpers.TableName TableName} object, a new {@link helpers.TableName TableName} is constructed from the value.
50
+ *
51
+ * It can be used as the default for methods {@link helpers.insert insert} and {@link helpers.update update} when their parameter
52
+ * `table` is omitted, and for logging purposes.
53
+ *
54
+ * @param {boolean} [options.inherit = false]
55
+ * Use inherited properties in addition to the object's own properties.
56
+ *
57
+ * By default, only the object's own properties are enumerated for column names.
58
+ *
59
+ * @param {'error' | 'skip' | 'replace'} [options.duplicate = 'error']
60
+ * Strategy for processing duplicate columns:
61
+ * - `'error'` - throws {@link external:Error Error} = `Duplicate column name "name".` (Default)
62
+ * - `'skip'` - skips repeated columns (only the first will be used)
63
+ * - `'replace'` - replaces repeated columns (the last one will be used)
64
+ *
65
+ * @returns {helpers.ColumnSet}
66
+ *
67
+ * @see
68
+ *
69
+ * {@link helpers.ColumnSet#columns columns},
70
+ * {@link helpers.ColumnSet#names names},
71
+ * {@link helpers.ColumnSet#table table},
72
+ * {@link helpers.ColumnSet#variables variables} |
73
+ * {@link helpers.ColumnSet#assign assign},
74
+ * {@link helpers.ColumnSet#assignColumns assignColumns},
75
+ * {@link helpers.ColumnSet#extend extend},
76
+ * {@link helpers.ColumnSet#merge merge},
77
+ * {@link helpers.ColumnSet#prepare prepare}
78
+ *
79
+ * @example
80
+ *
81
+ * // A complex insert/update object scenario for table 'purchases' in schema 'fiscal'.
82
+ * // For a good performance, you should declare such objects once and then reuse them.
83
+ * //
84
+ * // Column Requirements:
85
+ * //
86
+ * // 1. Property 'id' is only to be used for a WHERE condition in updates
87
+ * // 2. Property 'list' needs to be formatted as a csv
88
+ * // 3. Property 'code' is to be used as raw text, and to be defaulted to 0 when the
89
+ * // property is missing in the source object
90
+ * // 4. Property 'log' is a JSON object with 'log-entry' for the column name
91
+ * // 5. Property 'data' requires SQL type casting '::int[]'
92
+ * // 6. Property 'amount' needs to be set to 100, if it is 0
93
+ * // 7. Property 'total' must be skipped during updates, if 'amount' was 0, plus its
94
+ * // column name is 'total-val'
95
+ * const {ColumnSet} = pgp.helpers;
96
+ *
97
+ * const cs = new ColumnSet([
98
+ * '?id', // ColumnConfig equivalent: {name: 'id', cnd: true}
99
+ * 'list:csv', // ColumnConfig equivalent: {name: 'list', mod: ':csv'}
100
+ * {
101
+ * name: 'code',
102
+ * mod: '^', // format as raw text
103
+ * def: 0 // default to 0 when the property doesn't exist
104
+ * },
105
+ * {
106
+ * name: 'log-entry',
107
+ * prop: 'log',
108
+ * mod: ':json' // format as JSON
109
+ * },
110
+ * {
111
+ * name: 'data',
112
+ * cast: 'int[]' // use SQL type casting '::int[]'
113
+ * },
114
+ * {
115
+ * name: 'amount',
116
+ * init(col) {
117
+ * // set to 100, if the value is 0:
118
+ * return col.value === 0 ? 100 : col.value;
119
+ * }
120
+ * },
121
+ * {
122
+ * name: 'total-val',
123
+ * prop: 'total',
124
+ * skip(col) {
125
+ * // skip from updates, if 'amount' is 0:
126
+ * return col.source.amount === 0;
127
+ * }
128
+ * }
129
+ * ], {table: {table: 'purchases', schema: 'fiscal'}});
130
+ *
131
+ * // Alternatively, you could take the table declaration out:
132
+ * // const table = new pgp.helpers.TableName('purchases', 'fiscal');
133
+ *
134
+ * console.log(cs); // console output for the object:
135
+ * //=>
136
+ * // ColumnSet {
137
+ * // table: "fiscal"."purchases"
138
+ * // columns: [
139
+ * // Column {
140
+ * // name: "id"
141
+ * // cnd: true
142
+ * // }
143
+ * // Column {
144
+ * // name: "list"
145
+ * // mod: ":csv"
146
+ * // }
147
+ * // Column {
148
+ * // name: "code"
149
+ * // mod: "^"
150
+ * // def: 0
151
+ * // }
152
+ * // Column {
153
+ * // name: "log-entry"
154
+ * // prop: "log"
155
+ * // mod: ":json"
156
+ * // }
157
+ * // Column {
158
+ * // name: "data"
159
+ * // cast: "int[]"
160
+ * // }
161
+ * // Column {
162
+ * // name: "amount"
163
+ * // init: [Function]
164
+ * // }
165
+ * // Column {
166
+ * // name: "total-val"
167
+ * // prop: "total"
168
+ * // skip: [Function]
169
+ * // }
170
+ * // ]
171
+ * // }
172
+ */
173
+ class ColumnSet extends InnerState {
174
+
175
+ constructor(columns, options) {
176
+ super();
177
+
178
+ if (!columns || typeof columns !== 'object') {
179
+ throw new TypeError('Invalid parameter \'columns\' specified.');
180
+ }
181
+
182
+ options = assert(options, ['table', 'inherit', 'duplicate']);
183
+
184
+ if (!npm.utils.isNull(options.table)) {
185
+ this.table = (options.table instanceof TableName) ? options.table : new TableName(options.table);
186
+ }
187
+
188
+ /**
189
+ * @name helpers.ColumnSet#table
190
+ * @type {helpers.TableName}
191
+ * @readonly
192
+ * @description
193
+ * Destination table. It can be specified for two purposes:
194
+ *
195
+ * - **primary:** to be used as the default table when it is omitted during a call into methods {@link helpers.insert insert} and {@link helpers.update update}
196
+ * - **secondary:** to be automatically written into the console (for logging purposes).
197
+ */
198
+
199
+ this.columns = [];
200
+
201
+ /**
202
+ * @name helpers.ColumnSet#columns
203
+ * @type helpers.Column[]
204
+ * @readonly
205
+ * @description
206
+ * Array of {@link helpers.Column Column} objects.
207
+ */
208
+ if (Array.isArray(columns)) {
209
+ const duplicate = options.duplicate || 'error';
210
+ columns.forEach(c => {
211
+ const col = (c instanceof Column) ? c : new Column(c);
212
+ const idx = this.columns.findIndex(a => a.name === col.name);
213
+ if (idx === -1) {
214
+ this.columns.push(col);
215
+ } else {
216
+ if (duplicate === 'replace') {
217
+ this.columns[idx] = col;
218
+ return;
219
+ }
220
+ if (duplicate !== 'skip') {
221
+ throw new Error(`Duplicate column name "${col.name}".`);
222
+ }
223
+ }
224
+ });
225
+ } else {
226
+ if (columns instanceof Column) {
227
+ this.columns = [columns];
228
+ } else {
229
+ for (const name in columns) {
230
+ if (options.inherit || Object.prototype.hasOwnProperty.call(columns, name)) {
231
+ this.columns.push(new Column(name));
232
+ }
233
+ }
234
+ }
235
+ }
236
+
237
+ Object.freeze(this.columns);
238
+ Object.freeze(this);
239
+
240
+ this.extendState({
241
+ names: undefined,
242
+ variables: undefined,
243
+ updates: undefined,
244
+ isSimple: true
245
+ });
246
+
247
+ for (let i = 0; i < this.columns.length; i++) {
248
+ const c = this.columns[i];
249
+ // ColumnSet is simple when the source objects require no preparation,
250
+ // and should be used directly:
251
+ if (c.prop || c.init || 'def' in c) {
252
+ this._inner.isSimple = false;
253
+ break;
254
+ }
255
+ }
256
+ }
257
+
258
+ /**
259
+ * @name helpers.ColumnSet#names
260
+ * @type string
261
+ * @readonly
262
+ * @description
263
+ * Returns a string - comma-separated list of all column names, properly escaped.
264
+ *
265
+ * @example
266
+ * const cs = new ColumnSet(['id^', {name: 'cells', cast: 'int[]'}, 'doc:json']);
267
+ * console.log(cs.names);
268
+ * //=> "id","cells","doc"
269
+ */
270
+ get names() {
271
+ const _i = this._inner;
272
+ if (!_i.names) {
273
+ _i.names = this.columns.map(c => c.escapedName).join();
274
+ }
275
+ return _i.names;
276
+ }
277
+
278
+ /**
279
+ * @name helpers.ColumnSet#variables
280
+ * @type string
281
+ * @readonly
282
+ * @description
283
+ * Returns a string - formatting template for all column values.
284
+ *
285
+ * @see {@link helpers.ColumnSet#assign assign}
286
+ *
287
+ * @example
288
+ * const cs = new ColumnSet(['id^', {name: 'cells', cast: 'int[]'}, 'doc:json']);
289
+ * console.log(cs.variables);
290
+ * //=> ${id^},${cells}::int[],${doc:json}
291
+ */
292
+ get variables() {
293
+ const _i = this._inner;
294
+ if (!_i.variables) {
295
+ _i.variables = this.columns.map(c => c.variable + c.castText).join();
296
+ }
297
+ return _i.variables;
298
+ }
299
+
300
+ }
301
+
302
+ /**
303
+ * @method helpers.ColumnSet#assign
304
+ * @description
305
+ * Returns a formatting template of SET assignments, either generic or for a single object.
306
+ *
307
+ * The method is optimized to cache the output string when there are no columns that can be skipped dynamically.
308
+ *
309
+ * This method is primarily for internal use, that's why it does not validate the input.
310
+ *
311
+ * @param {object} [options]
312
+ * Assignment/formatting options.
313
+ *
314
+ * @param {object} [options.source]
315
+ * Source - a single object that contains values for columns.
316
+ *
317
+ * The object is only necessary to correctly apply the logic of skipping columns dynamically, based on the source data
318
+ * and the rules defined in the {@link helpers.ColumnSet ColumnSet}. If, however, you do not care about that, then you do not need to specify any object.
319
+ *
320
+ * Note that even if you do not specify the object, the columns marked as conditional (`cnd: true`) will always be skipped.
321
+ *
322
+ * @param {string} [options.prefix]
323
+ * In cases where needed, an alias prefix to be added before each column.
324
+ *
325
+ * @returns {string}
326
+ * Comma-separated list of variable-to-column assignments.
327
+ *
328
+ * @see {@link helpers.ColumnSet#variables variables}
329
+ *
330
+ * @example
331
+ *
332
+ * const cs = new pgp.helpers.ColumnSet([
333
+ * '?first', // = {name: 'first', cnd: true}
334
+ * 'second:json',
335
+ * {name: 'third', mod: ':raw', cast: 'text'}
336
+ * ]);
337
+ *
338
+ * cs.assign();
339
+ * //=> "second"=${second:json},"third"=${third:raw}::text
340
+ *
341
+ * cs.assign({prefix: 'a b c'});
342
+ * //=> "a b c"."second"=${second:json},"a b c"."third"=${third:raw}::text
343
+ */
344
+ ColumnSet.prototype.assign = function (options) {
345
+ const _i = this._inner;
346
+ const hasPrefix = options && options.prefix && typeof options.prefix === 'string';
347
+ if (_i.updates && !hasPrefix) {
348
+ return _i.updates;
349
+ }
350
+ let dynamic = hasPrefix;
351
+ const hasSource = options && options.source && typeof options.source === 'object';
352
+ let list = this.columns.filter(c => {
353
+ if (c.cnd) {
354
+ return false;
355
+ }
356
+ if (c.skip) {
357
+ dynamic = true;
358
+ if (hasSource) {
359
+ const a = colDesc(c, options.source);
360
+ if (c.skip.call(options.source, a)) {
361
+ return false;
362
+ }
363
+ }
364
+ }
365
+ return true;
366
+ });
367
+
368
+ const prefix = hasPrefix ? npm.formatting.as.alias(options.prefix) + '.' : '';
369
+ list = list.map(c => prefix + c.escapedName + '=' + c.variable + c.castText).join();
370
+
371
+ if (!dynamic) {
372
+ _i.updates = list;
373
+ }
374
+ return list;
375
+ };
376
+
377
+ /**
378
+ * @method helpers.ColumnSet#assignColumns
379
+ * @description
380
+ * Generates assignments for all columns in the set, with support for aliases and column-skipping logic.
381
+ * Aliases are set by using method {@link formatting.alias as.alias}.
382
+ *
383
+ * @param {{}} [options]
384
+ * Optional Parameters.
385
+ *
386
+ * @param {string} [options.from]
387
+ * Alias for the source columns.
388
+ *
389
+ * @param {string} [options.to]
390
+ * Alias for the destination columns.
391
+ *
392
+ * @param {string | Array<string> | function} [options.skip]
393
+ * Name(s) of the column(s) to be skipped (case-sensitive). It can be either a single string or an array of strings.
394
+ *
395
+ * It can also be a function - iterator, to be called for every column, passing in {@link helpers.Column Column} as
396
+ * `this` context, and plus as a single parameter. The function would return a truthy value for every column that needs to be skipped.
397
+ *
398
+ * @returns {string}
399
+ * A string of comma-separated column assignments.
400
+ *
401
+ * @example
402
+ *
403
+ * const cs = new ColumnSet(['id', 'city', 'street']);
404
+ *
405
+ * cs.assignColumns({from: 'EXCLUDED', skip: 'id'})
406
+ * //=> "city"=EXCLUDED."city","street"=EXCLUDED."street"
407
+ *
408
+ * @example
409
+ *
410
+ * const cs = new ColumnSet(['?id', 'city', 'street']);
411
+ *
412
+ * cs.assignColumns({from: 'source', to: 'target', skip: c => c.cnd})
413
+ * //=> target."city"=source."city",target."street"=source."street"
414
+ *
415
+ */
416
+ ColumnSet.prototype.assignColumns = function (options) {
417
+ options = assert(options, ['from', 'to', 'skip']);
418
+ const skip = (typeof options.skip === 'string' && [options.skip]) || ((Array.isArray(options.skip) || typeof options.skip === 'function') && options.skip);
419
+ const from = (typeof options.from === 'string' && options.from && (npm.formatting.as.alias(options.from) + '.')) || '';
420
+ const to = (typeof options.to === 'string' && options.to && (npm.formatting.as.alias(options.to) + '.')) || '';
421
+ const iterator = typeof skip === 'function' ? c => !skip.call(c, c) : c => skip.indexOf(c.name) === -1;
422
+ const cols = skip ? this.columns.filter(iterator) : this.columns;
423
+ return cols.map(c => to + c.escapedName + '=' + from + c.escapedName).join();
424
+ };
425
+
426
+ /**
427
+ * @method helpers.ColumnSet#extend
428
+ * @description
429
+ * Creates a new {@link helpers.ColumnSet ColumnSet}, by joining the two sets of columns.
430
+ *
431
+ * If the two sets contain a column with the same `name` (case-sensitive), an error is thrown (unless `options.skip` is set to `true`).
432
+ *
433
+ * @param {helpers.Column|helpers.ColumnSet|array} columns
434
+ * Columns to be appended, of the same type as parameter `columns` during {@link helpers.ColumnSet ColumnSet} construction, except:
435
+ * - it can also be of type {@link helpers.ColumnSet ColumnSet}
436
+ * - it cannot be a simple object (properties enumeration is not supported here)
437
+ *
438
+ * @param {object} [options]
439
+ *
440
+ * @param {boolean} [options.skip = false]
441
+ * When set to `true`, it skips duplicate columns (by default, it throws an error).
442
+ *
443
+ * @returns {helpers.ColumnSet}
444
+ * New {@link helpers.ColumnSet ColumnSet} object with the extended/concatenated list of columns.
445
+ *
446
+ * @see
447
+ * {@link helpers.Column Column},
448
+ * {@link helpers.ColumnSet#merge merge}
449
+ *
450
+ * @example
451
+ *
452
+ * const pgp = require('pg-promise')();
453
+ *
454
+ * const cs = new pgp.helpers.ColumnSet(['one', 'two'], {table: 'my-table'});
455
+ * console.log(cs);
456
+ * //=>
457
+ * // ColumnSet {
458
+ * // table: "my-table"
459
+ * // columns: [
460
+ * // Column {
461
+ * // name: "one"
462
+ * // }
463
+ * // Column {
464
+ * // name: "two"
465
+ * // }
466
+ * // ]
467
+ * // }
468
+ * const csExtended = cs.extend(['three']);
469
+ * console.log(csExtended);
470
+ * //=>
471
+ * // ColumnSet {
472
+ * // table: "my-table"
473
+ * // columns: [
474
+ * // Column {
475
+ * // name: "one"
476
+ * // }
477
+ * // Column {
478
+ * // name: "two"
479
+ * // }
480
+ * // Column {
481
+ * // name: "three"
482
+ * // }
483
+ * // ]
484
+ * // }
485
+ */
486
+ ColumnSet.prototype.extend = function (columns, options) {
487
+ options = assert(options, ['skip']);
488
+ const duplicate = options.skip ? 'skip' : 'error';
489
+ const cs = columns instanceof ColumnSet ? columns : new ColumnSet(columns, {duplicate});
490
+ const joinedCols = this.columns.concat(...cs.columns);
491
+ return new ColumnSet(joinedCols, {table: this.table, duplicate});
492
+ };
493
+
494
+ /**
495
+ * @method helpers.ColumnSet#merge
496
+ * @description
497
+ * Creates a new {@link helpers.ColumnSet ColumnSet}, by joining the two sets of columns.
498
+ *
499
+ * Items in `columns` with the same `name` (case-sensitive) override the original columns.
500
+ *
501
+ * @param {helpers.Column|helpers.ColumnSet|array} columns
502
+ * Columns to be appended, of the same type as parameter `columns` during {@link helpers.ColumnSet ColumnSet} construction, except:
503
+ * - it can also be of type {@link helpers.ColumnSet ColumnSet}
504
+ * - it cannot be a simple object (properties enumeration is not supported here)
505
+ *
506
+ * @see
507
+ * {@link helpers.Column Column},
508
+ * {@link helpers.ColumnSet#extend extend}
509
+ *
510
+ * @returns {helpers.ColumnSet}
511
+ * New {@link helpers.ColumnSet ColumnSet} object with the merged list of columns.
512
+ *
513
+ * @example
514
+ *
515
+ * const pgp = require('pg-promise')();
516
+ * const {ColumnSet} = pgp.helpers;
517
+ *
518
+ * const cs = new ColumnSet(['?one', 'two:json'], {table: 'my-table'});
519
+ * console.log(cs);
520
+ * //=>
521
+ * // ColumnSet {
522
+ * // table: "my-table"
523
+ * // columns: [
524
+ * // Column {
525
+ * // name: "one"
526
+ * // cnd: true
527
+ * // }
528
+ * // Column {
529
+ * // name: "two"
530
+ * // mod: ":json"
531
+ * // }
532
+ * // ]
533
+ * // }
534
+ * const csMerged = cs.merge(['two', 'three^']);
535
+ * console.log(csMerged);
536
+ * //=>
537
+ * // ColumnSet {
538
+ * // table: "my-table"
539
+ * // columns: [
540
+ * // Column {
541
+ * // name: "one"
542
+ * // cnd: true
543
+ * // }
544
+ * // Column {
545
+ * // name: "two"
546
+ * // }
547
+ * // Column {
548
+ * // name: "three"
549
+ * // mod: "^"
550
+ * // }
551
+ * // ]
552
+ * // }
553
+ *
554
+ */
555
+ ColumnSet.prototype.merge = function (columns) {
556
+ const duplicate = 'replace';
557
+ const cs = columns instanceof ColumnSet ? columns : new ColumnSet(columns, {duplicate});
558
+ const joinedCols = this.columns.concat(...cs.columns);
559
+ return new ColumnSet(joinedCols, {table: this.table, duplicate});
560
+ };
561
+
562
+ /**
563
+ * @method helpers.ColumnSet#prepare
564
+ * @description
565
+ * Prepares a source object to be formatted, by cloning it and applying the rules as set by the
566
+ * columns configuration.
567
+ *
568
+ * This method is primarily for internal use, that's why it does not validate the input parameters.
569
+ *
570
+ * @param {object} source
571
+ * The source object to be prepared, if required.
572
+ *
573
+ * It must be a non-`null` object, which the method does not validate, as it is
574
+ * intended primarily for internal use by the library.
575
+ *
576
+ * @returns {object}
577
+ * When the object needs to be prepared, the method returns a clone of the source object,
578
+ * with all properties and values set according to the columns configuration.
579
+ *
580
+ * When the object does not need to be prepared, the original object is returned.
581
+ */
582
+ ColumnSet.prototype.prepare = function (source) {
583
+ if (this._inner.isSimple) {
584
+ return source; // a simple ColumnSet requires no object preparation;
585
+ }
586
+ const target = {};
587
+ this.columns.forEach(c => {
588
+ const a = colDesc(c, source);
589
+ if (c.init) {
590
+ target[a.name] = c.init.call(source, a);
591
+ } else {
592
+ if (a.exists || 'def' in c) {
593
+ target[a.name] = a.value;
594
+ }
595
+ }
596
+ });
597
+ return target;
598
+ };
599
+
600
+ function colDesc(column, source) {
601
+ const a = {
602
+ source,
603
+ name: column.prop || column.name
604
+ };
605
+ a.exists = a.name in source;
606
+ if (a.exists) {
607
+ a.value = source[a.name];
608
+ } else {
609
+ a.value = 'def' in column ? column.def : undefined;
610
+ }
611
+ return a;
612
+ }
613
+
614
+ module.exports = {ColumnSet};