mythix-orm-sql-base 1.0.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.
@@ -0,0 +1,763 @@
1
+ 'use strict';
2
+
3
+ const Nife = require('nife');
4
+ const UUID = require('uuid');
5
+ const {
6
+ ConnectionBase,
7
+ Utils,
8
+ QueryEngine,
9
+ Literals,
10
+ Model: ModelBase,
11
+ } = require('mythix-orm');
12
+
13
+ const SAVE_POINT_NAME_CHARS = [ 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P' ];
14
+ const MODEL_RELATIONS = Symbol.for('_mythixModelRelations');
15
+
16
+ class SQLConnectionBase extends ConnectionBase {
17
+ static Literals = Literals;
18
+
19
+ static getLiteralClassByName(_name) {
20
+ if (!_name)
21
+ return;
22
+
23
+ let name = Nife.capitalize(_name.toLowerCase());
24
+
25
+ if (name === 'Literal')
26
+ return Literals.Literal;
27
+ else if (name === 'Base')
28
+ return Literals.LiteralBase;
29
+
30
+ return Literals[`${name}Literal`];
31
+ }
32
+
33
+ static Literal(name, ...args) {
34
+ const LiteralClass = this.getLiteralClassByName(name);
35
+ if (!LiteralClass)
36
+ throw new Error(`${this.constructor.name}::Literal: Unable to locate literal class for literal name "${name}".`);
37
+
38
+ let literal = new LiteralClass(...args);
39
+ return literal;
40
+ }
41
+
42
+ prepareArrayValuesForSQL(_array) {
43
+ let array = Nife.arrayFlatten(_array);
44
+
45
+ array = array.filter((item) => {
46
+ if (item === null)
47
+ return true;
48
+
49
+ if (item instanceof Literals.LiteralBase)
50
+ return true;
51
+
52
+ if (!Nife.instanceOf(item, 'string', 'number', 'bigint', 'boolean'))
53
+ return false;
54
+
55
+ return true;
56
+ });
57
+
58
+ return Nife.uniq(array);
59
+ }
60
+
61
+ getDefaultOrder(Model, options) {
62
+ let order = Nife.toArray(Model.getDefaultOrder(options));
63
+
64
+ order = Nife.arrayFlatten(order).filter(Boolean);
65
+
66
+ if (Nife.isEmpty(order))
67
+ return;
68
+
69
+ return order;
70
+ }
71
+
72
+ generateSavePointName() {
73
+ let id = UUID.v4();
74
+
75
+ id = id.toUpperCase().replace(/\d/g, (m) => {
76
+ let index = parseInt(m, 10);
77
+ return SAVE_POINT_NAME_CHARS[index];
78
+ }).replace(/-/g, '');
79
+
80
+ return `SP${id}`;
81
+ }
82
+
83
+ findAllFieldsFromFieldProjectionMap(projectionFieldMap) {
84
+ let fullFieldNames = (Array.isArray(projectionFieldMap)) ? projectionFieldMap : Array.from(projectionFieldMap.keys());
85
+
86
+ return fullFieldNames.map((fullFieldName) => {
87
+ let def = Utils.parseQualifiedName(fullFieldName);
88
+ if (!def.modelName || def.fieldNames.length === 0)
89
+ return fullFieldName;
90
+
91
+ let field = this.getField(def.fieldNames[0], def.modelName);
92
+ if (!field)
93
+ return fullFieldName;
94
+
95
+ return field;
96
+ }).filter(Boolean);
97
+ }
98
+
99
+ buildModelDataMapFromSelectResults(queryEngine, result) {
100
+ if (!result)
101
+ return {};
102
+
103
+ let rows = result.rows;
104
+ if (Nife.isEmpty(rows))
105
+ return {};
106
+
107
+ let context = queryEngine._getRawQueryContext();
108
+ let fields = this.findAllFieldsFromFieldProjectionMap(result.columns);
109
+ let rootModelName = context.rootModelName;
110
+ let modelData = {};
111
+ let alreadyVisited = {};
112
+
113
+ let fieldInfo = fields.map((field) => {
114
+ let Model = field.Model;
115
+ let modelName = Model.getModelName();
116
+ let pkFieldName = Model.getPrimaryKeyFieldName();
117
+
118
+ return {
119
+ pkFieldName,
120
+ field,
121
+ Model,
122
+ modelName,
123
+ };
124
+ });
125
+
126
+ let modelInfo = fieldInfo.reduce((obj, info) => {
127
+ obj[info.modelName] = info;
128
+ return obj;
129
+ }, {});
130
+
131
+ // Remap row
132
+ let modelNames = Object.keys(modelInfo).sort((a, b) => {
133
+ if (a === rootModelName)
134
+ return -1;
135
+
136
+ if (b === rootModelName)
137
+ return 1;
138
+
139
+ if (a === b)
140
+ return 0;
141
+
142
+ return (a < b) ? -1 : 1;
143
+ });
144
+
145
+ for (let i = 0, il = rows.length; i < il; i++) {
146
+ let row = rows[i];
147
+ let data = {};
148
+
149
+ // Collect row
150
+ for (let j = 0, jl = fieldInfo.length; j < jl; j++) {
151
+ let {
152
+ field,
153
+ modelName,
154
+ } = fieldInfo[j];
155
+
156
+ let dataContext = data[modelName];
157
+ let remoteValue = row[j];
158
+
159
+ if (!dataContext)
160
+ dataContext = data[modelName] = {};
161
+
162
+ dataContext[field.fieldName] = remoteValue;
163
+ }
164
+
165
+ let rootModel;
166
+ for (let j = 0, jl = modelNames.length; j < jl; j++) {
167
+ let modelName = modelNames[j];
168
+ let info = modelInfo[modelName];
169
+ let models = modelData[modelName];
170
+ let model = data[modelName];
171
+ let pkFieldName = info.pkFieldName;
172
+ let index;
173
+
174
+ if (!models)
175
+ models = modelData[modelName] = [];
176
+
177
+ if (pkFieldName) {
178
+ let id = model[pkFieldName];
179
+
180
+ if (id != null) {
181
+ let idKey = `${modelName}:${pkFieldName}:${id}`;
182
+
183
+ if (alreadyVisited[idKey] != null) {
184
+ index = alreadyVisited[idKey];
185
+ } else {
186
+ index = alreadyVisited[idKey] = models.length;
187
+ models.push(model);
188
+ }
189
+ }
190
+ } else {
191
+ models.push(model);
192
+ continue;
193
+ }
194
+
195
+ if (j === 0) {
196
+ rootModel = model;
197
+ } else {
198
+ if (!rootModel[MODEL_RELATIONS]) {
199
+ Object.defineProperties(rootModel, {
200
+ [MODEL_RELATIONS]: {
201
+ writable: true,
202
+ enumberable: false,
203
+ configurable: true,
204
+ value: {},
205
+ },
206
+ });
207
+ }
208
+
209
+ if (!rootModel[MODEL_RELATIONS][modelName])
210
+ rootModel[MODEL_RELATIONS][modelName] = [];
211
+
212
+ rootModel[MODEL_RELATIONS][modelName].push(index);
213
+ }
214
+ }
215
+ }
216
+
217
+ return modelData;
218
+ }
219
+
220
+ buildModelsFromModelDataMap(queryEngine, modelDataMap, callback) {
221
+ if (Nife.isEmpty(modelDataMap))
222
+ return [];
223
+
224
+ let queryContext = queryEngine._getRawQueryContext();
225
+ let rootModelName = queryContext.rootModelName;
226
+ let RootModel = queryContext.rootModel;
227
+ if (!rootModelName || !RootModel)
228
+ throw new Error(`${this.constructor.name}::buildModelsFromModelDataMap: Root model not found.`);
229
+
230
+ let rootModelData = modelDataMap[rootModelName];
231
+ if (Nife.isEmpty(rootModelData))
232
+ return [];
233
+
234
+ let callbackIsValid = (typeof callback === 'function');
235
+ let rootModels = rootModelData.map((data) => {
236
+ let model = new RootModel(data);
237
+
238
+ if (callbackIsValid)
239
+ model = callback(RootModel, model);
240
+
241
+ if (data[MODEL_RELATIONS]) {
242
+ let relationships = data[MODEL_RELATIONS];
243
+ let modelNames = Object.keys(relationships);
244
+
245
+ for (let i = 0, il = modelNames.length; i < il; i++) {
246
+ let modelName = modelNames[i];
247
+ let Model = this.getModel(modelName);
248
+ let modelIndexes = relationships[modelName];
249
+ let models = modelDataMap[modelName];
250
+
251
+ Utils.assignRelatedModels(model, modelIndexes.map((modelIndex) => {
252
+ let modelData = models[modelIndex];
253
+ let thisModel = new Model(modelData);
254
+
255
+ if (callbackIsValid)
256
+ thisModel = callback(Model, thisModel);
257
+
258
+ return thisModel;
259
+ }));
260
+ }
261
+ }
262
+
263
+ model.clearDirty();
264
+
265
+ return model;
266
+ });
267
+
268
+ return rootModels;
269
+ }
270
+
271
+ updateModelsFromResults(Model, storedModels, results) {
272
+ let {
273
+ rows,
274
+ columns,
275
+ } = results;
276
+
277
+ for (let i = 0, il = rows.length; i < il; i++) {
278
+ let row = rows[i];
279
+ let storedModel = storedModels[i];
280
+
281
+ for (let j = 0, jl = columns.length; j < jl; j++) {
282
+ let columnName = columns[j];
283
+ let value = row[j];
284
+
285
+ storedModel[columnName] = value;
286
+ }
287
+
288
+ this.setPersisted([ storedModel ], true);
289
+ }
290
+
291
+ return storedModels;
292
+ }
293
+
294
+ async runSaveHooks(Model, models, operationHookName, saveHookName, options) {
295
+ const throwError = (error) => {
296
+ throw error;
297
+ };
298
+
299
+ let promises = [];
300
+ let context = { connection: this, Model, options };
301
+
302
+ for (let i = 0, il = models.length; i < il; i++) {
303
+ let model = models[i];
304
+ let promise = model[operationHookName](context);
305
+
306
+ if (!(promise instanceof Promise))
307
+ promise = Promise.resolve();
308
+
309
+ promise = promise.then(async () => await model[saveHookName](context), throwError);
310
+ if (!(promise instanceof Promise))
311
+ promise = Promise.resolve();
312
+
313
+ promises.push(promise);
314
+ }
315
+
316
+ await Promise.all(promises);
317
+ }
318
+
319
+ async dropTable(Model, options) {
320
+ let queryGenerator = this.getQueryGenerator();
321
+ let createTableSQL = queryGenerator.generateDropTableStatement(Model, options);
322
+
323
+ // Drop table
324
+ return await this.query(createTableSQL, options);
325
+ }
326
+
327
+ async createTable(Model, options) {
328
+ let queryGenerator = this.getQueryGenerator();
329
+ let createTableSQL = queryGenerator.generateCreateTableStatement(Model, options);
330
+
331
+ // Create table
332
+ let result = await this.query(createTableSQL, options);
333
+
334
+ // Create indexes and constraints
335
+ let trailingStatements = Nife.toArray(queryGenerator.generateCreateTableStatementOuterTail(Model, options)).filter(Boolean);
336
+ if (Nife.isNotEmpty(trailingStatements)) {
337
+ for (let i = 0, il = trailingStatements.length; i < il; i++) {
338
+ let trailingStatement = trailingStatements[i];
339
+ await this.query(trailingStatement, options);
340
+ }
341
+ }
342
+
343
+ return result;
344
+ }
345
+
346
+ async insert(Model, models, _options) {
347
+ return await this.bulkModelOperation(
348
+ Model,
349
+ models,
350
+ Object.assign({}, _options || {}, { skipPersisted: true, isInsertOperation: true }),
351
+ // Before model operation handler
352
+ async (Model, models, options) => {
353
+ await this.runSaveHooks(Model, models, 'onBeforeCreate', 'onBeforeSave', true, options);
354
+ },
355
+ // Operation handler
356
+ async (Model, preparedModels, options, queryGenerator) => {
357
+ let sqlStr = queryGenerator.generateInsertStatement(Model, preparedModels, options);
358
+ let results = await this.query(sqlStr, { logger: options.logger });
359
+
360
+ this.updateModelsFromResults(Model, preparedModels.models, results);
361
+ },
362
+ // After model operation handler
363
+ async (Model, models, options) => {
364
+ await this.runSaveHooks(Model, models, 'onAfterCreate', 'onAfterSave', false, options);
365
+ },
366
+ // After all operations handler
367
+ async (PrimaryModel, dirtyModels, options, queryGenerator) => {
368
+ for (let dirtyModel of dirtyModels) {
369
+ let Model = dirtyModel.getModel();
370
+ let sqlStr = queryGenerator.generateUpdateStatement(Model, dirtyModel, null, options);
371
+ let results = await this.query(sqlStr, { logger: options.logger });
372
+
373
+ this.updateModelsFromResults(Model, [ dirtyModel ], results);
374
+ }
375
+ },
376
+ );
377
+ }
378
+
379
+ // eslint-disable-next-line no-unused-vars
380
+ async upsert(Model, models, _options) {
381
+ throw new Error(`${this.constructor.name}::upsert: This connection type does not support "upsert" operations.`);
382
+ }
383
+
384
+ async update(Model, models, _options) {
385
+ let options = _options || {};
386
+
387
+ let primaryKeyFieldName = Model.getPrimaryKeyFieldName();
388
+ if (Nife.isEmpty(primaryKeyFieldName))
389
+ throw new Error(`${this.constructor.name}::update: Model has no primary key field.`);
390
+
391
+ return await this.bulkModelOperation(
392
+ Model,
393
+ models,
394
+ Object.assign({}, options || {}, { isUpdateOperation: true }),
395
+ // Before model operation handler
396
+ async (Model, models, options) => {
397
+ await this.runSaveHooks(Model, models, 'onBeforeUpdate', 'onBeforeSave', true, options);
398
+ },
399
+ // Operation handler
400
+ async (Model, preparedModels, options, queryGenerator) => {
401
+ let models = preparedModels.models;
402
+ for (let i = 0, il = models.length; i < il; i++) {
403
+ let model = models[i];
404
+ let query = Model.where;
405
+
406
+ let pkFieldValue = model[primaryKeyFieldName];
407
+ if (!pkFieldValue)
408
+ throw new Error(`${this.constructor.name}::update: Model's primary key is empty. Models being updated must have a valid primary key.`);
409
+
410
+ query = query[primaryKeyFieldName].EQ(pkFieldValue);
411
+
412
+ let sqlStr = queryGenerator.generateUpdateStatement(Model, model, query, options);
413
+ if (!sqlStr)
414
+ continue;
415
+
416
+ let results = await this.query(sqlStr, { logger: options.logger });
417
+ this.updateModelsFromResults(Model, [ model ], results);
418
+ }
419
+ },
420
+ // After model operation handler
421
+ async (Model, models, options) => {
422
+ await this.runSaveHooks(Model, models, 'onAfterUpdate', 'onAfterSave', false, options);
423
+ },
424
+ );
425
+ }
426
+
427
+ async updateAll(_queryEngine, model, _options) {
428
+ let queryEngine = this.toQueryEngine(_queryEngine);
429
+ if (!queryEngine)
430
+ throw new Error(`${this.constructor.name}::updateAll: Model class or query is required to update.`);
431
+
432
+ let rootModel = queryEngine._getRawQueryContext().rootModel;
433
+ if (!rootModel)
434
+ throw new Error(`${this.constructor.name}::updateAll: Root model not found, and is required to update.`);
435
+
436
+ let options = Object.assign({}, _options || {}, { isUpdateOperation: true });
437
+ let queryGenerator = this.getQueryGenerator();
438
+ let sqlStr = queryGenerator.generateUpdateStatement(rootModel, model, queryEngine, options);
439
+
440
+ // TODO: Use "RETURNING" to return pks of of updated rows
441
+
442
+ return await this.query(sqlStr, { logger: options.logger });
443
+ }
444
+
445
+ async destroyModels(Model, _models, _options) {
446
+ if (!ModelBase.isModelClass(Model))
447
+ throw new Error(`${this.constructor.name}::_destroyModels: You must provide a model class as the first argument.`);
448
+
449
+ let options = _options || {};
450
+ if (_models == null) {
451
+ let queryGenerator = this.getQueryGenerator();
452
+ let sqlStr = queryGenerator.generateDeleteStatement(Model, Model.where.unscoped(), options);
453
+
454
+ return await this.query(sqlStr, { logger: options.logger });
455
+ }
456
+
457
+ let models = Nife.toArray(_models).filter(Boolean);
458
+ if (Nife.isEmpty(models))
459
+ return;
460
+
461
+ let primaryKeyFieldName = Model.getPrimaryKeyFieldName();
462
+ if (Nife.isEmpty(primaryKeyFieldName))
463
+ throw new Error(`${this.constructor.name}::destroyModels: Model has no primary key field. You must supply a query to delete models with no primary key.`);
464
+
465
+ return await this.bulkModelOperation(
466
+ Model,
467
+ models,
468
+ Object.assign({}, options, { isDeleteOperation: true }),
469
+ // Before model operation handler
470
+ null,
471
+ // Operation handler
472
+ async (Model, preparedModels, options, queryGenerator) => {
473
+ let models = preparedModels.models;
474
+ let pkIDs = [];
475
+
476
+ for (let i = 0, il = models.length; i < il; i++) {
477
+ let model = models[i];
478
+ let pkFieldValue = model[primaryKeyFieldName];
479
+ if (pkFieldValue != null && Nife.isEmpty(pkFieldValue))
480
+ continue;
481
+
482
+ pkIDs.push(pkFieldValue);
483
+ }
484
+
485
+ if (Nife.isEmpty(pkIDs))
486
+ return;
487
+
488
+ let sqlStr = queryGenerator.generateDeleteStatement(Model, Model.where.id.EQ(pkIDs));
489
+ if (!sqlStr)
490
+ return;
491
+
492
+ await this.query(sqlStr, { logger: options.logger });
493
+ },
494
+ );
495
+ }
496
+
497
+ async destroy(_queryEngineOrModel, modelsOrOptions, _options) {
498
+ // TODO: Have destroy use "RETURNING" to return deleted PKs
499
+
500
+ let queryEngineOrModel = _queryEngineOrModel;
501
+
502
+ if (QueryEngine.isQuery(modelsOrOptions))
503
+ queryEngineOrModel = modelsOrOptions;
504
+ else if (queryEngineOrModel && ModelBase.isModelClass(queryEngineOrModel))
505
+ return await this.destroyModels(queryEngineOrModel, modelsOrOptions, _options);
506
+ else if (!QueryEngine.isQuery(queryEngineOrModel))
507
+ throw new Error(`${this.constructor.name}::destroy: Please provide a query, or a model class and a list of models to destroy.`);
508
+
509
+ let queryEngine = this.toQueryEngine(queryEngineOrModel);
510
+ if (!queryEngine)
511
+ throw new Error(`${this.constructor.name}::destroy: Model class or query is required to destroy.`);
512
+
513
+ let rootModel = queryEngine._getRawQueryContext().rootModel;
514
+ if (!rootModel)
515
+ throw new Error(`${this.constructor.name}::destroy: Root model not found, and is required to destroy.`);
516
+
517
+ let options = modelsOrOptions || {};
518
+ let queryGenerator = this.getQueryGenerator();
519
+ let sqlStr = queryGenerator.generateDeleteStatement(rootModel, queryEngine, options);
520
+ return await this.query(sqlStr, { logger: options.logger });
521
+ }
522
+
523
+ async *select(_queryEngine, _options) {
524
+ let queryEngine = _queryEngine;
525
+ if (!queryEngine)
526
+ throw new TypeError(`${this.constructor.name}::select: First argument must be a model class or a query.`);
527
+
528
+ if (!QueryEngine.isQuery(queryEngine)) {
529
+ if (Object.prototype.hasOwnProperty.call(queryEngine, 'where'))
530
+ queryEngine = queryEngine.where(this);
531
+ else
532
+ throw new TypeError(`${this.constructor.name}::select: First argument must be a model class or a query.`);
533
+ }
534
+
535
+ let queryContext = queryEngine._getRawQueryContext();
536
+ let options = _options || {};
537
+ let batchSize = options.batchSize || 500;
538
+ let startIndex = queryContext.offset || 0;
539
+ let queryGenerator = this.getQueryGenerator();
540
+
541
+ while (true) {
542
+ let query = queryEngine.clone().LIMIT(batchSize).OFFSET(startIndex);
543
+ let sqlStatement = queryGenerator.generateSelectStatement(query, options);
544
+ let result = await this.query(sqlStatement, { logger: options.logger });
545
+
546
+ if (!result.rows || result.rows.length === 0)
547
+ break;
548
+
549
+ startIndex += result.rows.length;
550
+
551
+ if (options.raw === true) {
552
+ yield result;
553
+ } else {
554
+ let modelDataMap = this.buildModelDataMapFromSelectResults(queryEngine, result);
555
+ let models = this.buildModelsFromModelDataMap(queryEngine, modelDataMap, (_, model) => {
556
+ model._persisted = true;
557
+ return model;
558
+ });
559
+
560
+ for (let i = 0, il = models.length; i < il; i++) {
561
+ let model = models[i];
562
+
563
+ model.__order = i;
564
+
565
+ yield model;
566
+ }
567
+ }
568
+
569
+ if (result.rows.length < batchSize)
570
+ break;
571
+ }
572
+ }
573
+
574
+ async aggregate(_queryEngine, _literal, options) {
575
+ let literal = _literal;
576
+ if (!(literal instanceof Literals.LiteralBase))
577
+ throw new Error(`${this.constructor.name}::aggregate: Second argument must be a Literal instance.`);
578
+
579
+ let queryEngine = this.toQueryEngine(_queryEngine);
580
+ if (!queryEngine)
581
+ throw new TypeError(`${this.constructor.name}::aggregate: First argument must be a model class or a query.`);
582
+
583
+ queryEngine = queryEngine.clone();
584
+
585
+ let queryContext = queryEngine._getRawQueryContext();
586
+ let distinct = queryContext.distinct;
587
+ if (distinct) {
588
+ let fullyQualifiedFieldName = distinct.getFullyQualifiedFieldName();
589
+
590
+ if (fullyQualifiedFieldName) {
591
+ let LiteralConstructor = literal.constructor;
592
+ literal = new LiteralConstructor(fullyQualifiedFieldName);
593
+
594
+ queryEngine = queryEngine.DISTINCT(false).PROJECT(literal);
595
+ }
596
+ }
597
+
598
+ let literalStr = literal.toString(this);
599
+ let queryGenerator = this.getQueryGenerator();
600
+ let query = queryEngine.PROJECT(literal);
601
+ let sqlStr = queryGenerator.generateSelectStatement(query, options);
602
+
603
+ let result = await this.query(sqlStr, options);
604
+ let columnIndex = result.columns.indexOf(literalStr);
605
+ if (columnIndex < 0) {
606
+ if (result.columns.length === 1)
607
+ columnIndex = 0;
608
+ else
609
+ throw new Error(`${this.constructor.name}::aggregate: Can not find specified column in results.`);
610
+ }
611
+
612
+ return result.rows[0][columnIndex];
613
+ }
614
+
615
+ async average(_queryEngine, _field, options) {
616
+ let queryEngine = this.toQueryEngine(_queryEngine);
617
+ if (!queryEngine)
618
+ throw new TypeError(`${this.constructor.name}::average: First argument must be a model class or a query.`);
619
+
620
+ let rootModel = queryEngine._getRawQueryContext().rootModel;
621
+ let field = Utils.fieldToFullyQualifiedName(_field, rootModel);
622
+
623
+ return await this.aggregate(queryEngine, new Literals.AverageLiteral(field), options);
624
+ }
625
+
626
+ async count(_queryEngine, _field, options) {
627
+ let queryEngine = this.toQueryEngine(_queryEngine);
628
+ if (!queryEngine)
629
+ throw new TypeError(`${this.constructor.name}::count: First argument must be a model class or a query.`);
630
+
631
+ let rootModel = queryEngine._getRawQueryContext().rootModel;
632
+ let field = (_field) ? Utils.fieldToFullyQualifiedName(_field, rootModel) : null;
633
+
634
+ return await this.aggregate(queryEngine, new Literals.CountLiteral(field), options);
635
+ }
636
+
637
+ async min(_queryEngine, _field, options) {
638
+ let queryEngine = this.toQueryEngine(_queryEngine);
639
+ if (!queryEngine)
640
+ throw new TypeError(`${this.constructor.name}::min: First argument must be a model class or a query.`);
641
+
642
+ let rootModel = queryEngine._getRawQueryContext().rootModel;
643
+ let field = Utils.fieldToFullyQualifiedName(_field, rootModel);
644
+
645
+ return await this.aggregate(queryEngine, new Literals.MinLiteral(field), options);
646
+ }
647
+
648
+ async max(_queryEngine, _field, options) {
649
+ let queryEngine = this.toQueryEngine(_queryEngine);
650
+ if (!queryEngine)
651
+ throw new TypeError(`${this.constructor.name}::max: First argument must be a model class or a query.`);
652
+
653
+ let rootModel = queryEngine._getRawQueryContext().rootModel;
654
+ let field = Utils.fieldToFullyQualifiedName(_field, rootModel);
655
+
656
+ return await this.aggregate(queryEngine, new Literals.MaxLiteral(field), options);
657
+ }
658
+
659
+ async sum(_queryEngine, _field, options) {
660
+ let queryEngine = this.toQueryEngine(_queryEngine);
661
+ if (!queryEngine)
662
+ throw new TypeError(`${this.constructor.name}::sum: First argument must be a model class or a query.`);
663
+
664
+ let rootModel = queryEngine._getRawQueryContext().rootModel;
665
+ let field = Utils.fieldToFullyQualifiedName(_field, rootModel);
666
+
667
+ return await this.aggregate(queryEngine, new Literals.SumLiteral(field), options);
668
+ }
669
+
670
+ async pluck(_queryEngine, _fields, _options) {
671
+ if (_options && !Nife.instanceOf(_options, 'object'))
672
+ throw new TypeError(`${this.constructor.name}::pluck: "options" isn't an object. Did you pass a field by accident?`);
673
+
674
+ let options = _options || {};
675
+ let providedArrayOfFields = Array.isArray(_fields);
676
+ let fields = Nife.arrayFlatten(Nife.toArray(_fields)).filter(Boolean);
677
+
678
+ if (Nife.isEmpty(fields))
679
+ throw new Error(`${this.constructor.name}::pluck: You must supply "fields" to pluck.`);
680
+
681
+ let queryEngine = _queryEngine;
682
+ if (!queryEngine)
683
+ throw new TypeError(`${this.constructor.name}::pluck: First argument must be a model class or a query.`);
684
+
685
+ if (!QueryEngine.isQuery(queryEngine)) {
686
+ if (Object.prototype.hasOwnProperty.call(queryEngine, 'where'))
687
+ queryEngine = queryEngine.where(this);
688
+ else
689
+ throw new TypeError(`${this.constructor.name}::pluck: First argument must be a model class or a query.`);
690
+ }
691
+
692
+ let queryContext = queryEngine._getRawQueryContext();
693
+ let rootModel = queryContext.rootModel;
694
+ let rootModelName = rootModel.getModelName();
695
+
696
+ // remap fields so they have fully qualified names
697
+ fields = fields.map((field) => {
698
+ let def = this.parseQualifiedName(field);
699
+ if (!def.modelName)
700
+ def.modelName = rootModelName;
701
+
702
+ if (Nife.isEmpty(def.fieldNames))
703
+ throw new Error(`${this.constructor.name}::pluck: Do not know how to map to field "${field}".`);
704
+
705
+ return `${def.modelName}:${def.fieldNames[0]}`;
706
+ });
707
+
708
+ let queryGenerator = this.getQueryGenerator();
709
+ let query = queryEngine.clone().PROJECT(fields);
710
+ let sqlStr = queryGenerator.generateSelectStatement(query, options);
711
+ let result = await this.query(sqlStr, options);
712
+ let finalResults = [];
713
+ let { columns, rows } = result;
714
+ let columnIndexMap = fields.reduce((obj, field) => {
715
+ obj[field] = columns.indexOf(field);
716
+ return obj;
717
+ }, {});
718
+
719
+ if (options.mapToObjects) {
720
+ finalResults = rows.map((row) => {
721
+ let obj = {};
722
+ for (let i = 0, il = fields.length; i < il; i++) {
723
+ let field = fields[i];
724
+ let columnIndex = columnIndexMap[field];
725
+ if (columnIndex < 0) {
726
+ obj[fields] = undefined;
727
+ continue;
728
+ }
729
+
730
+ obj[field] = row[columnIndex];
731
+ }
732
+
733
+ return obj;
734
+ });
735
+ } else {
736
+ finalResults = rows.map((row) => {
737
+ return fields.map((field) => {
738
+ let columnIndex = columnIndexMap[field];
739
+ if (columnIndex < 0)
740
+ return;
741
+
742
+ return row[columnIndex];
743
+ });
744
+ });
745
+ }
746
+
747
+ if (!providedArrayOfFields)
748
+ finalResults = finalResults.map((row) => row[0]);
749
+
750
+ return finalResults;
751
+ }
752
+
753
+ async exists(queryEngine, options) {
754
+ let count = await this.count(queryEngine, null, options);
755
+ return (count > 0);
756
+ }
757
+
758
+ async enableForeignKeyConstraints(enable) {
759
+ throw new Error(`${this.constructor.name}::enableForeignKeyConstraints: This operation is not supported for this connection type.`);
760
+ }
761
+ }
762
+
763
+ module.exports = SQLConnectionBase;