foxhound 2.0.18 → 2.0.20

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 (36) hide show
  1. package/README.md +3 -2
  2. package/docs/README.md +32 -11
  3. package/docs/_cover.md +3 -2
  4. package/docs/_sidebar.md +21 -1
  5. package/docs/_topbar.md +7 -0
  6. package/docs/api/README.md +73 -0
  7. package/docs/api/addFilter.md +170 -0
  8. package/docs/api/addJoin.md +128 -0
  9. package/docs/api/addRecord.md +108 -0
  10. package/docs/api/addSort.md +109 -0
  11. package/docs/api/behaviorFlags.md +123 -0
  12. package/docs/api/buildQuery.md +146 -0
  13. package/docs/api/clone.md +115 -0
  14. package/docs/api/queryOverrides.md +82 -0
  15. package/docs/api/setCap.md +115 -0
  16. package/docs/api/setDataElements.md +95 -0
  17. package/docs/api/setDialect.md +78 -0
  18. package/docs/api/setDistinct.md +76 -0
  19. package/docs/api/setIDUser.md +80 -0
  20. package/docs/api/setScope.md +70 -0
  21. package/docs/architecture.md +134 -42
  22. package/docs/dialects/README.md +2 -0
  23. package/docs/dialects/postgresql.md +126 -0
  24. package/docs/quickstart.md +196 -0
  25. package/docs/retold-catalog.json +40 -1
  26. package/docs/retold-keyword-index.json +7996 -2630
  27. package/package.json +2 -2
  28. package/source/Foxhound-Dialects.js +4 -0
  29. package/source/dialects/DGraph/FoxHound-Dialect-DGraph.js +954 -0
  30. package/source/dialects/MongoDB/FoxHound-Dialect-MongoDB.js +902 -0
  31. package/source/dialects/PostgreSQL/FoxHound-Dialect-PostgreSQL.js +865 -0
  32. package/source/dialects/Solr/FoxHound-Dialect-Solr.js +895 -0
  33. package/test/FoxHound-Dialect-DGraph_tests.js +547 -0
  34. package/test/FoxHound-Dialect-MongoDB_tests.js +485 -0
  35. package/test/FoxHound-Dialect-PostgreSQL_tests.js +342 -0
  36. package/test/FoxHound-Dialect-Solr_tests.js +551 -0
@@ -0,0 +1,902 @@
1
+ /**
2
+ * FoxHound MongoDB Dialect
3
+ *
4
+ * Generates JSON operation descriptors for MongoDB queries.
5
+ * The query body is a JSON string; the parsed operation object is also
6
+ * stored in query.parameters.mongoOperation for direct provider consumption.
7
+ *
8
+ * @license MIT
9
+ *
10
+ * @author Steven Velozo <steven@velozo.com>
11
+ * @class FoxHoundDialectMongoDB
12
+ */
13
+
14
+ var FoxHoundDialectMongoDB = function(pFable)
15
+ {
16
+ _Fable = pFable;
17
+
18
+ /**
19
+ * Strip any table-name prefix from a column name.
20
+ * MongoDB has no table-qualified field names.
21
+ *
22
+ * @method stripTablePrefix
23
+ * @param {String} pColumn Column name, possibly table-qualified
24
+ * @return {String} Plain column name
25
+ */
26
+ var stripTablePrefix = function(pColumn)
27
+ {
28
+ if (typeof pColumn !== 'string')
29
+ {
30
+ return pColumn;
31
+ }
32
+ // Remove backtick and double-quote quoting
33
+ var tmpColumn = pColumn.replace(/[`"]/g, '');
34
+ // Strip table prefix (e.g. "Animal.Name" -> "Name")
35
+ if (tmpColumn.indexOf('.') >= 0)
36
+ {
37
+ var tmpParts = tmpColumn.split('.');
38
+ // If it ends with *, just ignore it (wildcard in MongoDB = all fields)
39
+ if (tmpParts[tmpParts.length - 1] === '*')
40
+ {
41
+ return '*';
42
+ }
43
+ return tmpParts[tmpParts.length - 1];
44
+ }
45
+ return tmpColumn;
46
+ };
47
+
48
+ /**
49
+ * Find the schema entry for a given column name.
50
+ *
51
+ * @method findSchemaEntry
52
+ * @param {String} pColumn Column name
53
+ * @param {Array} pSchema Schema array
54
+ * @return {Object} Schema entry or default
55
+ */
56
+ var findSchemaEntry = function(pColumn, pSchema)
57
+ {
58
+ for (var i = 0; i < pSchema.length; i++)
59
+ {
60
+ if (pColumn == pSchema[i].Column)
61
+ {
62
+ return pSchema[i];
63
+ }
64
+ }
65
+ return { Column: pColumn, Type: 'Default' };
66
+ };
67
+
68
+ /**
69
+ * Translate a single FoxHound filter operator into a MongoDB condition object.
70
+ *
71
+ * @method translateOperator
72
+ * @param {Object} pFilterEntry A FoxHound filter object
73
+ * @return {Object} MongoDB condition object
74
+ */
75
+ var translateOperator = function(pFilterEntry)
76
+ {
77
+ var tmpColumn = stripTablePrefix(pFilterEntry.Column);
78
+ var tmpValue = pFilterEntry.Value;
79
+
80
+ switch (pFilterEntry.Operator)
81
+ {
82
+ case '=':
83
+ var tmpObj = {};
84
+ tmpObj[tmpColumn] = tmpValue;
85
+ return tmpObj;
86
+ case '!=':
87
+ var tmpNeObj = {};
88
+ tmpNeObj[tmpColumn] = { $ne: tmpValue };
89
+ return tmpNeObj;
90
+ case '>':
91
+ var tmpGtObj = {};
92
+ tmpGtObj[tmpColumn] = { $gt: tmpValue };
93
+ return tmpGtObj;
94
+ case '>=':
95
+ var tmpGteObj = {};
96
+ tmpGteObj[tmpColumn] = { $gte: tmpValue };
97
+ return tmpGteObj;
98
+ case '<':
99
+ var tmpLtObj = {};
100
+ tmpLtObj[tmpColumn] = { $lt: tmpValue };
101
+ return tmpLtObj;
102
+ case '<=':
103
+ var tmpLteObj = {};
104
+ tmpLteObj[tmpColumn] = { $lte: tmpValue };
105
+ return tmpLteObj;
106
+ case 'LIKE':
107
+ // Convert SQL LIKE pattern to regex: % -> .*, _ -> .
108
+ var tmpPattern = String(tmpValue).replace(/%/g, '.*').replace(/_/g, '.');
109
+ var tmpLikeObj = {};
110
+ tmpLikeObj[tmpColumn] = { $regex: tmpPattern, $options: 'i' };
111
+ return tmpLikeObj;
112
+ case 'IN':
113
+ var tmpInObj = {};
114
+ tmpInObj[tmpColumn] = { $in: Array.isArray(tmpValue) ? tmpValue : [tmpValue] };
115
+ return tmpInObj;
116
+ case 'NOT IN':
117
+ var tmpNinObj = {};
118
+ tmpNinObj[tmpColumn] = { $nin: Array.isArray(tmpValue) ? tmpValue : [tmpValue] };
119
+ return tmpNinObj;
120
+ case 'IS NULL':
121
+ var tmpNullObj = {};
122
+ tmpNullObj[tmpColumn] = null;
123
+ return tmpNullObj;
124
+ case 'IS NOT NULL':
125
+ var tmpNotNullObj = {};
126
+ tmpNotNullObj[tmpColumn] = { $ne: null };
127
+ return tmpNotNullObj;
128
+ default:
129
+ // Unknown operator, treat as equality
130
+ var tmpDefaultObj = {};
131
+ tmpDefaultObj[tmpColumn] = tmpValue;
132
+ return tmpDefaultObj;
133
+ }
134
+ };
135
+
136
+ /**
137
+ * Safely merge an array of condition objects into a single MongoDB filter.
138
+ * If there are key collisions, wrap in $and.
139
+ *
140
+ * @method mergeConditions
141
+ * @param {Array} pConditions Array of condition objects
142
+ * @return {Object} Merged MongoDB filter
143
+ */
144
+ var mergeConditions = function(pConditions)
145
+ {
146
+ if (!pConditions || pConditions.length === 0)
147
+ {
148
+ return {};
149
+ }
150
+
151
+ if (pConditions.length === 1)
152
+ {
153
+ var tmpSingle = pConditions[0];
154
+ delete tmpSingle._connector;
155
+ return tmpSingle;
156
+ }
157
+
158
+ // Check for key collisions
159
+ var tmpKeysSeen = {};
160
+ var tmpHasCollision = false;
161
+ for (var i = 0; i < pConditions.length; i++)
162
+ {
163
+ var tmpKeys = Object.keys(pConditions[i]);
164
+ for (var j = 0; j < tmpKeys.length; j++)
165
+ {
166
+ var tmpKey = tmpKeys[j];
167
+ if (tmpKey === '_connector') continue;
168
+ if (tmpKey.charAt(0) === '$') { tmpHasCollision = true; break; }
169
+ if (tmpKeysSeen[tmpKey]) { tmpHasCollision = true; break; }
170
+ tmpKeysSeen[tmpKey] = true;
171
+ }
172
+ if (tmpHasCollision) break;
173
+ }
174
+
175
+ if (tmpHasCollision)
176
+ {
177
+ // Must use $and
178
+ var tmpAndArray = [];
179
+ for (var k = 0; k < pConditions.length; k++)
180
+ {
181
+ var tmpCond = pConditions[k];
182
+ delete tmpCond._connector;
183
+ tmpAndArray.push(tmpCond);
184
+ }
185
+ return { $and: tmpAndArray };
186
+ }
187
+ else
188
+ {
189
+ // Safe to merge into a single object
190
+ var tmpMerged = {};
191
+ for (var m = 0; m < pConditions.length; m++)
192
+ {
193
+ var tmpCondM = pConditions[m];
194
+ var tmpKeysM = Object.keys(tmpCondM);
195
+ for (var n = 0; n < tmpKeysM.length; n++)
196
+ {
197
+ if (tmpKeysM[n] !== '_connector')
198
+ {
199
+ tmpMerged[tmpKeysM[n]] = tmpCondM[tmpKeysM[n]];
200
+ }
201
+ }
202
+ }
203
+ return tmpMerged;
204
+ }
205
+ };
206
+
207
+ /**
208
+ * Generate the MongoDB filter object from the FoxHound filter array.
209
+ * Uses a stack-based approach for parenthetical groups.
210
+ *
211
+ * @method generateFilter
212
+ * @param {Object} pParameters SQL Query Parameters
213
+ * @return {Object} MongoDB filter object
214
+ */
215
+ var generateFilter = function(pParameters)
216
+ {
217
+ var tmpFilter = Array.isArray(pParameters.filter) ? pParameters.filter.slice() : [];
218
+
219
+ // Auto-add Deleted filter if applicable
220
+ if (!pParameters.query.disableDeleteTracking)
221
+ {
222
+ var tmpSchema = Array.isArray(pParameters.query.schema) ? pParameters.query.schema : [];
223
+ for (var i = 0; i < tmpSchema.length; i++)
224
+ {
225
+ if (tmpSchema[i].Type === 'Deleted')
226
+ {
227
+ var tmpHasDeletedParam = false;
228
+ for (var x = 0; x < tmpFilter.length; x++)
229
+ {
230
+ if (stripTablePrefix(tmpFilter[x].Column) === tmpSchema[i].Column)
231
+ {
232
+ tmpHasDeletedParam = true;
233
+ break;
234
+ }
235
+ }
236
+ if (!tmpHasDeletedParam)
237
+ {
238
+ tmpFilter.push(
239
+ {
240
+ Column: tmpSchema[i].Column,
241
+ Operator: '=',
242
+ Value: 0,
243
+ Connector: 'AND',
244
+ Parameter: 'Deleted'
245
+ });
246
+ }
247
+ break;
248
+ }
249
+ }
250
+ }
251
+
252
+ if (tmpFilter.length < 1)
253
+ {
254
+ return {};
255
+ }
256
+
257
+ // Stack-based processing for parenthetical groups
258
+ var tmpStack = [[]]; // Stack of condition arrays
259
+
260
+ for (var i = 0; i < tmpFilter.length; i++)
261
+ {
262
+ var tmpEntry = tmpFilter[i];
263
+
264
+ if (tmpEntry.Operator === '(')
265
+ {
266
+ tmpStack.push([]);
267
+ }
268
+ else if (tmpEntry.Operator === ')')
269
+ {
270
+ var tmpGroupConditions = tmpStack.pop();
271
+
272
+ // Check if any condition inside the group used OR
273
+ var tmpHasOR = false;
274
+ for (var g = 0; g < tmpGroupConditions.length; g++)
275
+ {
276
+ if (tmpGroupConditions[g]._connector === 'OR')
277
+ {
278
+ tmpHasOR = true;
279
+ break;
280
+ }
281
+ }
282
+
283
+ var tmpWrapped;
284
+ if (tmpHasOR)
285
+ {
286
+ var tmpOrArray = [];
287
+ for (var g2 = 0; g2 < tmpGroupConditions.length; g2++)
288
+ {
289
+ delete tmpGroupConditions[g2]._connector;
290
+ tmpOrArray.push(tmpGroupConditions[g2]);
291
+ }
292
+ tmpWrapped = { $or: tmpOrArray };
293
+ }
294
+ else
295
+ {
296
+ tmpWrapped = mergeConditions(tmpGroupConditions);
297
+ }
298
+
299
+ tmpWrapped._connector = tmpEntry.Connector || 'AND';
300
+ tmpStack[tmpStack.length - 1].push(tmpWrapped);
301
+ }
302
+ else
303
+ {
304
+ var tmpCondition = translateOperator(tmpEntry);
305
+ tmpCondition._connector = tmpEntry.Connector || 'AND';
306
+ tmpStack[tmpStack.length - 1].push(tmpCondition);
307
+ }
308
+ }
309
+
310
+ // Collapse root level
311
+ var tmpRootConditions = tmpStack[0];
312
+ return mergeConditions(tmpRootConditions);
313
+ };
314
+
315
+ /**
316
+ * Generate the MongoDB projection object from dataElements.
317
+ *
318
+ * @method generateProjection
319
+ * @param {Object} pParameters SQL Query Parameters
320
+ * @return {Object} MongoDB projection object (empty = all fields)
321
+ */
322
+ var generateProjection = function(pParameters)
323
+ {
324
+ var tmpDataElements = pParameters.dataElements;
325
+ if (!Array.isArray(tmpDataElements) || tmpDataElements.length < 1)
326
+ {
327
+ return {};
328
+ }
329
+
330
+ var tmpProjection = {};
331
+ for (var i = 0; i < tmpDataElements.length; i++)
332
+ {
333
+ var tmpField = tmpDataElements[i];
334
+ // Handle array format [FieldName, Alias]
335
+ if (Array.isArray(tmpField))
336
+ {
337
+ tmpField = tmpField[0];
338
+ }
339
+ tmpField = stripTablePrefix(tmpField);
340
+ if (tmpField !== '*')
341
+ {
342
+ tmpProjection[tmpField] = 1;
343
+ }
344
+ }
345
+ return tmpProjection;
346
+ };
347
+
348
+ /**
349
+ * Generate the MongoDB sort object from sort array.
350
+ *
351
+ * @method generateSort
352
+ * @param {Object} pParameters SQL Query Parameters
353
+ * @return {Object} MongoDB sort object (empty = no sort)
354
+ */
355
+ var generateSort = function(pParameters)
356
+ {
357
+ var tmpSort = pParameters.sort;
358
+ if (!Array.isArray(tmpSort) || tmpSort.length < 1)
359
+ {
360
+ return {};
361
+ }
362
+
363
+ var tmpSortObj = {};
364
+ for (var i = 0; i < tmpSort.length; i++)
365
+ {
366
+ var tmpColumn = stripTablePrefix(tmpSort[i].Column);
367
+ tmpSortObj[tmpColumn] = (tmpSort[i].Direction === 'Descending') ? -1 : 1;
368
+ }
369
+ return tmpSortObj;
370
+ };
371
+
372
+ /**
373
+ * Generate the document for a create (insertOne) operation.
374
+ * Walks the record through the schema to handle special column types.
375
+ *
376
+ * @method generateCreateDocument
377
+ * @param {Object} pParameters SQL Query Parameters
378
+ * @return {Object|false} Document object or false if no record
379
+ */
380
+ var generateCreateDocument = function(pParameters)
381
+ {
382
+ var tmpRecords = pParameters.query.records;
383
+ if (!Array.isArray(tmpRecords) || tmpRecords.length < 1)
384
+ {
385
+ return false;
386
+ }
387
+
388
+ var tmpSchema = Array.isArray(pParameters.query.schema) ? pParameters.query.schema : [];
389
+ var tmpDocument = {};
390
+
391
+ for (var tmpColumn in tmpRecords[0])
392
+ {
393
+ var tmpSchemaEntry = findSchemaEntry(tmpColumn, tmpSchema);
394
+
395
+ if (!pParameters.query.disableDeleteTracking)
396
+ {
397
+ if (tmpSchemaEntry.Type === 'DeleteDate' ||
398
+ tmpSchemaEntry.Type === 'DeleteIDUser')
399
+ {
400
+ continue;
401
+ }
402
+ }
403
+
404
+ switch (tmpSchemaEntry.Type)
405
+ {
406
+ case 'AutoIdentity':
407
+ if (pParameters.query.disableAutoIdentity)
408
+ {
409
+ tmpDocument[tmpColumn] = tmpRecords[0][tmpColumn];
410
+ }
411
+ else
412
+ {
413
+ tmpDocument[tmpColumn] = '$$AUTOINCREMENT';
414
+ }
415
+ break;
416
+ case 'AutoGUID':
417
+ if (pParameters.query.disableAutoIdentity)
418
+ {
419
+ tmpDocument[tmpColumn] = tmpRecords[0][tmpColumn];
420
+ }
421
+ else if (tmpRecords[0][tmpColumn] &&
422
+ tmpRecords[0][tmpColumn].length >= 5 &&
423
+ tmpRecords[0][tmpColumn] !== '0x0000000000000000')
424
+ {
425
+ tmpDocument[tmpColumn] = tmpRecords[0][tmpColumn];
426
+ }
427
+ else
428
+ {
429
+ tmpDocument[tmpColumn] = pParameters.query.UUID;
430
+ }
431
+ break;
432
+ case 'UpdateDate':
433
+ case 'CreateDate':
434
+ if (pParameters.query.disableAutoDateStamp)
435
+ {
436
+ tmpDocument[tmpColumn] = tmpRecords[0][tmpColumn];
437
+ }
438
+ else
439
+ {
440
+ tmpDocument[tmpColumn] = '$$NOW';
441
+ }
442
+ break;
443
+ case 'DeleteIDUser':
444
+ case 'UpdateIDUser':
445
+ case 'CreateIDUser':
446
+ if (pParameters.query.disableAutoUserStamp)
447
+ {
448
+ tmpDocument[tmpColumn] = tmpRecords[0][tmpColumn];
449
+ }
450
+ else
451
+ {
452
+ tmpDocument[tmpColumn] = pParameters.query.IDUser;
453
+ }
454
+ break;
455
+ case 'Deleted':
456
+ tmpDocument[tmpColumn] = 0;
457
+ break;
458
+ default:
459
+ tmpDocument[tmpColumn] = tmpRecords[0][tmpColumn];
460
+ break;
461
+ }
462
+ }
463
+
464
+ if (Object.keys(tmpDocument).length === 0)
465
+ {
466
+ return false;
467
+ }
468
+
469
+ return tmpDocument;
470
+ };
471
+
472
+ /**
473
+ * Generate the $set document for an update operation.
474
+ * Walks the record through the schema, skipping identity/create/delete columns.
475
+ *
476
+ * @method generateUpdateDocument
477
+ * @param {Object} pParameters SQL Query Parameters
478
+ * @return {Object|false} Update document or false if no record
479
+ */
480
+ var generateUpdateDocument = function(pParameters)
481
+ {
482
+ var tmpRecords = pParameters.query.records;
483
+ if (!Array.isArray(tmpRecords) || tmpRecords.length < 1)
484
+ {
485
+ return false;
486
+ }
487
+
488
+ var tmpSchema = Array.isArray(pParameters.query.schema) ? pParameters.query.schema : [];
489
+ var tmpUpdateDoc = {};
490
+
491
+ for (var tmpColumn in tmpRecords[0])
492
+ {
493
+ var tmpSchemaEntry = findSchemaEntry(tmpColumn, tmpSchema);
494
+
495
+ if (pParameters.query.disableAutoDateStamp &&
496
+ tmpSchemaEntry.Type === 'UpdateDate')
497
+ {
498
+ continue;
499
+ }
500
+ if (pParameters.query.disableAutoUserStamp &&
501
+ tmpSchemaEntry.Type === 'UpdateIDUser')
502
+ {
503
+ continue;
504
+ }
505
+
506
+ switch (tmpSchemaEntry.Type)
507
+ {
508
+ case 'AutoIdentity':
509
+ case 'CreateDate':
510
+ case 'CreateIDUser':
511
+ case 'DeleteDate':
512
+ case 'DeleteIDUser':
513
+ continue;
514
+ }
515
+
516
+ switch (tmpSchemaEntry.Type)
517
+ {
518
+ case 'UpdateDate':
519
+ tmpUpdateDoc[tmpColumn] = '$$NOW';
520
+ break;
521
+ case 'UpdateIDUser':
522
+ tmpUpdateDoc[tmpColumn] = pParameters.query.IDUser;
523
+ break;
524
+ default:
525
+ tmpUpdateDoc[tmpColumn] = tmpRecords[0][tmpColumn];
526
+ break;
527
+ }
528
+ }
529
+
530
+ if (Object.keys(tmpUpdateDoc).length === 0)
531
+ {
532
+ return false;
533
+ }
534
+
535
+ return tmpUpdateDoc;
536
+ };
537
+
538
+ /**
539
+ * Generate the soft-delete $set document.
540
+ * Returns false if no Deleted column exists or delete tracking is disabled.
541
+ *
542
+ * @method generateDeleteSetters
543
+ * @param {Object} pParameters SQL Query Parameters
544
+ * @return {Object|false} Delete setters or false
545
+ */
546
+ var generateDeleteSetters = function(pParameters)
547
+ {
548
+ if (pParameters.query.disableDeleteTracking)
549
+ {
550
+ return false;
551
+ }
552
+
553
+ var tmpSchema = Array.isArray(pParameters.query.schema) ? pParameters.query.schema : [];
554
+ var tmpHasDeletedField = false;
555
+ var tmpSetters = {};
556
+
557
+ for (var i = 0; i < tmpSchema.length; i++)
558
+ {
559
+ var tmpSchemaEntry = tmpSchema[i];
560
+ switch (tmpSchemaEntry.Type)
561
+ {
562
+ case 'Deleted':
563
+ tmpSetters[tmpSchemaEntry.Column] = 1;
564
+ tmpHasDeletedField = true;
565
+ break;
566
+ case 'DeleteDate':
567
+ tmpSetters[tmpSchemaEntry.Column] = '$$NOW';
568
+ break;
569
+ case 'UpdateDate':
570
+ tmpSetters[tmpSchemaEntry.Column] = '$$NOW';
571
+ break;
572
+ case 'DeleteIDUser':
573
+ tmpSetters[tmpSchemaEntry.Column] = pParameters.query.IDUser;
574
+ break;
575
+ default:
576
+ continue;
577
+ }
578
+ }
579
+
580
+ if (!tmpHasDeletedField || Object.keys(tmpSetters).length === 0)
581
+ {
582
+ return false;
583
+ }
584
+
585
+ return tmpSetters;
586
+ };
587
+
588
+ /**
589
+ * Generate the undelete $set document.
590
+ * Returns false if no Deleted column exists.
591
+ *
592
+ * @method generateUndeleteSetters
593
+ * @param {Object} pParameters SQL Query Parameters
594
+ * @return {Object|false} Undelete setters or false
595
+ */
596
+ var generateUndeleteSetters = function(pParameters)
597
+ {
598
+ var tmpSchema = Array.isArray(pParameters.query.schema) ? pParameters.query.schema : [];
599
+ var tmpHasDeletedField = false;
600
+ var tmpSetters = {};
601
+
602
+ for (var i = 0; i < tmpSchema.length; i++)
603
+ {
604
+ var tmpSchemaEntry = tmpSchema[i];
605
+ switch (tmpSchemaEntry.Type)
606
+ {
607
+ case 'Deleted':
608
+ tmpSetters[tmpSchemaEntry.Column] = 0;
609
+ tmpHasDeletedField = true;
610
+ break;
611
+ case 'UpdateDate':
612
+ tmpSetters[tmpSchemaEntry.Column] = '$$NOW';
613
+ break;
614
+ case 'UpdateIDUser':
615
+ tmpSetters[tmpSchemaEntry.Column] = pParameters.query.IDUser;
616
+ break;
617
+ default:
618
+ continue;
619
+ }
620
+ }
621
+
622
+ if (!tmpHasDeletedField || Object.keys(tmpSetters).length === 0)
623
+ {
624
+ return false;
625
+ }
626
+
627
+ return tmpSetters;
628
+ };
629
+
630
+
631
+ /**
632
+ * Create a new record
633
+ *
634
+ * @method Create
635
+ * @param {Object} pParameters Query parameters
636
+ * @return {String} JSON operation descriptor or false
637
+ */
638
+ var Create = function(pParameters)
639
+ {
640
+ var tmpDocument = generateCreateDocument(pParameters);
641
+
642
+ if (!tmpDocument)
643
+ {
644
+ return false;
645
+ }
646
+
647
+ var tmpResult = {
648
+ collection: pParameters.scope,
649
+ operation: 'insertOne',
650
+ document: tmpDocument
651
+ };
652
+
653
+ pParameters.query.parameters.mongoOperation = tmpResult;
654
+
655
+ return JSON.stringify(tmpResult);
656
+ };
657
+
658
+
659
+ /**
660
+ * Read one or many records
661
+ *
662
+ * @method Read
663
+ * @param {Object} pParameters Query parameters
664
+ * @return {String} JSON operation descriptor
665
+ */
666
+ var Read = function(pParameters)
667
+ {
668
+ if (pParameters.join && Array.isArray(pParameters.join) && pParameters.join.length > 0)
669
+ {
670
+ _Fable.log.warn('MongoDB dialect does not support JOINs; join parameter will be ignored.');
671
+ }
672
+
673
+ var tmpFilter = generateFilter(pParameters);
674
+ var tmpProjection = generateProjection(pParameters);
675
+ var tmpSort = generateSort(pParameters);
676
+
677
+ var tmpResult = {
678
+ collection: pParameters.scope,
679
+ operation: 'find',
680
+ filter: tmpFilter
681
+ };
682
+
683
+ if (Object.keys(tmpProjection).length > 0)
684
+ {
685
+ tmpResult.projection = tmpProjection;
686
+ }
687
+
688
+ if (Object.keys(tmpSort).length > 0)
689
+ {
690
+ tmpResult.sort = tmpSort;
691
+ }
692
+
693
+ if (pParameters.cap)
694
+ {
695
+ tmpResult.limit = pParameters.cap;
696
+ }
697
+
698
+ if (pParameters.begin !== false && pParameters.begin > 0)
699
+ {
700
+ tmpResult.skip = pParameters.begin;
701
+ }
702
+
703
+ if (pParameters.distinct)
704
+ {
705
+ tmpResult.distinct = true;
706
+ }
707
+
708
+ pParameters.query.parameters.mongoOperation = tmpResult;
709
+
710
+ return JSON.stringify(tmpResult);
711
+ };
712
+
713
+ /**
714
+ * Update one or many records
715
+ *
716
+ * @method Update
717
+ * @param {Object} pParameters Query parameters
718
+ * @return {String} JSON operation descriptor or false
719
+ */
720
+ var Update = function(pParameters)
721
+ {
722
+ var tmpFilter = generateFilter(pParameters);
723
+ var tmpUpdateDoc = generateUpdateDocument(pParameters);
724
+
725
+ if (!tmpUpdateDoc)
726
+ {
727
+ return false;
728
+ }
729
+
730
+ var tmpResult = {
731
+ collection: pParameters.scope,
732
+ operation: 'updateMany',
733
+ filter: tmpFilter,
734
+ update: { $set: tmpUpdateDoc }
735
+ };
736
+
737
+ pParameters.query.parameters.mongoOperation = tmpResult;
738
+
739
+ return JSON.stringify(tmpResult);
740
+ };
741
+
742
+ /**
743
+ * Delete one or many records (soft or hard depending on schema)
744
+ *
745
+ * @method Delete
746
+ * @param {Object} pParameters Query parameters
747
+ * @return {String} JSON operation descriptor
748
+ */
749
+ var Delete = function(pParameters)
750
+ {
751
+ var tmpDeleteSetters = generateDeleteSetters(pParameters);
752
+ var tmpFilter = generateFilter(pParameters);
753
+
754
+ if (tmpDeleteSetters)
755
+ {
756
+ // Soft delete via update
757
+ var tmpResult = {
758
+ collection: pParameters.scope,
759
+ operation: 'updateMany',
760
+ filter: tmpFilter,
761
+ update: { $set: tmpDeleteSetters }
762
+ };
763
+ pParameters.query.parameters.mongoOperation = tmpResult;
764
+ return JSON.stringify(tmpResult);
765
+ }
766
+ else
767
+ {
768
+ // Hard delete
769
+ var tmpHardResult = {
770
+ collection: pParameters.scope,
771
+ operation: 'deleteMany',
772
+ filter: tmpFilter
773
+ };
774
+ pParameters.query.parameters.mongoOperation = tmpHardResult;
775
+ return JSON.stringify(tmpHardResult);
776
+ }
777
+ };
778
+
779
+ /**
780
+ * Undelete (restore) a soft-deleted record
781
+ *
782
+ * @method Undelete
783
+ * @param {Object} pParameters Query parameters
784
+ * @return {String} JSON operation descriptor
785
+ */
786
+ var Undelete = function(pParameters)
787
+ {
788
+ var tmpUndeleteSetters = generateUndeleteSetters(pParameters);
789
+
790
+ // Temporarily disable delete tracking for filter generation
791
+ // so we can find records where Deleted=1
792
+ var tmpDeleteTrackingState = pParameters.query.disableDeleteTracking;
793
+ pParameters.query.disableDeleteTracking = true;
794
+ var tmpFilter = generateFilter(pParameters);
795
+ pParameters.query.disableDeleteTracking = tmpDeleteTrackingState;
796
+
797
+ if (tmpUndeleteSetters)
798
+ {
799
+ var tmpResult = {
800
+ collection: pParameters.scope,
801
+ operation: 'updateMany',
802
+ filter: tmpFilter,
803
+ update: { $set: tmpUndeleteSetters }
804
+ };
805
+ pParameters.query.parameters.mongoOperation = tmpResult;
806
+ return JSON.stringify(tmpResult);
807
+ }
808
+ else
809
+ {
810
+ // No-op -- can't undelete without a Deleted column
811
+ var tmpNoopResult = {
812
+ collection: pParameters.scope,
813
+ operation: 'noop'
814
+ };
815
+ pParameters.query.parameters.mongoOperation = tmpNoopResult;
816
+ return JSON.stringify(tmpNoopResult);
817
+ }
818
+ };
819
+
820
+ /**
821
+ * Count records
822
+ *
823
+ * @method Count
824
+ * @param {Object} pParameters Query parameters
825
+ * @return {String} JSON operation descriptor
826
+ */
827
+ var Count = function(pParameters)
828
+ {
829
+ var tmpFilter = generateFilter(pParameters);
830
+
831
+ var tmpResult = {
832
+ collection: pParameters.scope,
833
+ operation: 'countDocuments',
834
+ filter: tmpFilter
835
+ };
836
+
837
+ if (pParameters.distinct)
838
+ {
839
+ tmpResult.distinct = true;
840
+ var tmpDataElements = pParameters.dataElements;
841
+ if (Array.isArray(tmpDataElements) && tmpDataElements.length > 0)
842
+ {
843
+ var tmpFields = [];
844
+ for (var i = 0; i < tmpDataElements.length; i++)
845
+ {
846
+ var tmpField = Array.isArray(tmpDataElements[i]) ? tmpDataElements[i][0] : tmpDataElements[i];
847
+ tmpField = stripTablePrefix(tmpField);
848
+ if (tmpField !== '*')
849
+ {
850
+ tmpFields.push(tmpField);
851
+ }
852
+ }
853
+ if (tmpFields.length > 0)
854
+ {
855
+ tmpResult.distinctFields = tmpFields;
856
+ }
857
+ }
858
+ else
859
+ {
860
+ // Fall back to AutoIdentity column from schema
861
+ var tmpSchema = Array.isArray(pParameters.query.schema) ? pParameters.query.schema : [];
862
+ for (var j = 0; j < tmpSchema.length; j++)
863
+ {
864
+ if (tmpSchema[j].Type === 'AutoIdentity')
865
+ {
866
+ tmpResult.distinctFields = [tmpSchema[j].Column];
867
+ break;
868
+ }
869
+ }
870
+ }
871
+ }
872
+
873
+ pParameters.query.parameters.mongoOperation = tmpResult;
874
+
875
+ return JSON.stringify(tmpResult);
876
+ };
877
+
878
+ var tmpDialect = ({
879
+ Create: Create,
880
+ Read: Read,
881
+ Update: Update,
882
+ Delete: Delete,
883
+ Undelete: Undelete,
884
+ Count: Count
885
+ });
886
+
887
+ /**
888
+ * Dialect Name
889
+ *
890
+ * @property name
891
+ * @type string
892
+ */
893
+ Object.defineProperty(tmpDialect, 'name',
894
+ {
895
+ get: function() { return 'MongoDB'; },
896
+ enumerable: true
897
+ });
898
+
899
+ return tmpDialect;
900
+ };
901
+
902
+ module.exports = FoxHoundDialectMongoDB;