foxhound 2.0.18 → 2.0.19

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,954 @@
1
+ /**
2
+ * FoxHound DGraph Dialect
3
+ *
4
+ * Generates DQL query strings and JSON mutation descriptors for DGraph.
5
+ * The query body is a JSON string; the parsed operation object is also
6
+ * stored in query.parameters.dgraphOperation for direct provider consumption.
7
+ *
8
+ * @license MIT
9
+ *
10
+ * @author Steven Velozo <steven@velozo.com>
11
+ * @class FoxHoundDialectDGraph
12
+ */
13
+
14
+ var FoxHoundDialectDGraph = function(pFable)
15
+ {
16
+ _Fable = pFable;
17
+
18
+ /**
19
+ * Strip any table-name prefix from a column name.
20
+ * DGraph uses plain predicate names without table qualification.
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 (tmpParts[tmpParts.length - 1] === '*')
39
+ {
40
+ return '*';
41
+ }
42
+ return tmpParts[tmpParts.length - 1];
43
+ }
44
+ return tmpColumn;
45
+ };
46
+
47
+ /**
48
+ * Find the schema entry for a given column name.
49
+ *
50
+ * @method findSchemaEntry
51
+ * @param {String} pColumn Column name
52
+ * @param {Array} pSchema Schema array
53
+ * @return {Object} Schema entry or default
54
+ */
55
+ var findSchemaEntry = function(pColumn, pSchema)
56
+ {
57
+ for (var i = 0; i < pSchema.length; i++)
58
+ {
59
+ if (pColumn == pSchema[i].Column)
60
+ {
61
+ return pSchema[i];
62
+ }
63
+ }
64
+ return { Column: pColumn, Type: 'Default' };
65
+ };
66
+
67
+ /**
68
+ * Format a value for inclusion in a DQL string.
69
+ * Strings are double-quoted, numbers stay bare, arrays become bracketed.
70
+ *
71
+ * @method formatDGraphValue
72
+ * @param {*} pValue The value to format
73
+ * @return {String} Formatted value string
74
+ */
75
+ var formatDGraphValue = function(pValue)
76
+ {
77
+ if (Array.isArray(pValue))
78
+ {
79
+ var tmpItems = [];
80
+ for (var i = 0; i < pValue.length; i++)
81
+ {
82
+ tmpItems.push(formatDGraphValue(pValue[i]));
83
+ }
84
+ return '[' + tmpItems.join(', ') + ']';
85
+ }
86
+ if (typeof pValue === 'number')
87
+ {
88
+ return String(pValue);
89
+ }
90
+ if (typeof pValue === 'boolean')
91
+ {
92
+ return pValue ? 'true' : 'false';
93
+ }
94
+ // Escape double quotes inside strings
95
+ return '"' + String(pValue).replace(/\\/g, '\\\\').replace(/"/g, '\\"') + '"';
96
+ };
97
+
98
+ /**
99
+ * Translate a single FoxHound filter entry into a DGraph DQL filter function call.
100
+ *
101
+ * @method translateOperator
102
+ * @param {Object} pFilterEntry A FoxHound filter object
103
+ * @return {String} DQL filter function string
104
+ */
105
+ var translateOperator = function(pFilterEntry)
106
+ {
107
+ var tmpColumn = stripTablePrefix(pFilterEntry.Column);
108
+ var tmpValue = pFilterEntry.Value;
109
+
110
+ switch (pFilterEntry.Operator)
111
+ {
112
+ case '=':
113
+ return 'eq(' + tmpColumn + ', ' + formatDGraphValue(tmpValue) + ')';
114
+ case '!=':
115
+ return 'NOT eq(' + tmpColumn + ', ' + formatDGraphValue(tmpValue) + ')';
116
+ case '>':
117
+ return 'gt(' + tmpColumn + ', ' + formatDGraphValue(tmpValue) + ')';
118
+ case '>=':
119
+ return 'ge(' + tmpColumn + ', ' + formatDGraphValue(tmpValue) + ')';
120
+ case '<':
121
+ return 'lt(' + tmpColumn + ', ' + formatDGraphValue(tmpValue) + ')';
122
+ case '<=':
123
+ return 'le(' + tmpColumn + ', ' + formatDGraphValue(tmpValue) + ')';
124
+ case 'LIKE':
125
+ // Convert SQL LIKE pattern to regex: % -> .*, _ -> .
126
+ var tmpPattern = String(tmpValue).replace(/%/g, '.*').replace(/_/g, '.');
127
+ return 'regexp(' + tmpColumn + ', /' + tmpPattern + '/i)';
128
+ case 'IN':
129
+ // DGraph eq() supports array values as IN
130
+ var tmpInValues = Array.isArray(tmpValue) ? tmpValue : [tmpValue];
131
+ return 'eq(' + tmpColumn + ', ' + formatDGraphValue(tmpInValues) + ')';
132
+ case 'NOT IN':
133
+ var tmpNinValues = Array.isArray(tmpValue) ? tmpValue : [tmpValue];
134
+ return 'NOT eq(' + tmpColumn + ', ' + formatDGraphValue(tmpNinValues) + ')';
135
+ case 'IS NULL':
136
+ return 'NOT has(' + tmpColumn + ')';
137
+ case 'IS NOT NULL':
138
+ return 'has(' + tmpColumn + ')';
139
+ default:
140
+ // Unknown operator, treat as equality
141
+ return 'eq(' + tmpColumn + ', ' + formatDGraphValue(tmpValue) + ')';
142
+ }
143
+ };
144
+
145
+ /**
146
+ * Generate the DGraph @filter(...) clause from the FoxHound filter array.
147
+ * Uses a stack-based approach for parenthetical groups.
148
+ *
149
+ * @method generateFilter
150
+ * @param {Object} pParameters Query Parameters
151
+ * @return {String} DQL @filter clause or empty string
152
+ */
153
+ var generateFilter = function(pParameters)
154
+ {
155
+ var tmpFilter = Array.isArray(pParameters.filter) ? pParameters.filter.slice() : [];
156
+
157
+ // Auto-add Deleted filter if applicable
158
+ if (!pParameters.query.disableDeleteTracking)
159
+ {
160
+ var tmpSchema = Array.isArray(pParameters.query.schema) ? pParameters.query.schema : [];
161
+ for (var i = 0; i < tmpSchema.length; i++)
162
+ {
163
+ if (tmpSchema[i].Type === 'Deleted')
164
+ {
165
+ var tmpHasDeletedParam = false;
166
+ for (var x = 0; x < tmpFilter.length; x++)
167
+ {
168
+ if (stripTablePrefix(tmpFilter[x].Column) === tmpSchema[i].Column)
169
+ {
170
+ tmpHasDeletedParam = true;
171
+ break;
172
+ }
173
+ }
174
+ if (!tmpHasDeletedParam)
175
+ {
176
+ tmpFilter.push(
177
+ {
178
+ Column: tmpSchema[i].Column,
179
+ Operator: '=',
180
+ Value: 0,
181
+ Connector: 'AND',
182
+ Parameter: 'Deleted'
183
+ });
184
+ }
185
+ break;
186
+ }
187
+ }
188
+ }
189
+
190
+ if (tmpFilter.length < 1)
191
+ {
192
+ return '';
193
+ }
194
+
195
+ // Stack-based processing for parenthetical groups
196
+ // Each stack level is an array of { text: 'eq(...)', connector: 'AND' }
197
+ var tmpStack = [[]];
198
+
199
+ for (var i = 0; i < tmpFilter.length; i++)
200
+ {
201
+ var tmpEntry = tmpFilter[i];
202
+
203
+ if (tmpEntry.Operator === '(')
204
+ {
205
+ tmpStack.push([]);
206
+ }
207
+ else if (tmpEntry.Operator === ')')
208
+ {
209
+ var tmpGroupConditions = tmpStack.pop();
210
+
211
+ // Check if any condition inside the group used OR
212
+ var tmpHasOR = false;
213
+ for (var g = 0; g < tmpGroupConditions.length; g++)
214
+ {
215
+ if (tmpGroupConditions[g].connector === 'OR')
216
+ {
217
+ tmpHasOR = true;
218
+ break;
219
+ }
220
+ }
221
+
222
+ // Join conditions in the group
223
+ var tmpGroupText = '';
224
+ for (var g2 = 0; g2 < tmpGroupConditions.length; g2++)
225
+ {
226
+ if (g2 > 0)
227
+ {
228
+ tmpGroupText += tmpHasOR ? ' OR ' : ' AND ';
229
+ }
230
+ tmpGroupText += tmpGroupConditions[g2].text;
231
+ }
232
+
233
+ tmpStack[tmpStack.length - 1].push({
234
+ text: '(' + tmpGroupText + ')',
235
+ connector: tmpEntry.Connector || 'AND'
236
+ });
237
+ }
238
+ else
239
+ {
240
+ tmpStack[tmpStack.length - 1].push({
241
+ text: translateOperator(tmpEntry),
242
+ connector: tmpEntry.Connector || 'AND'
243
+ });
244
+ }
245
+ }
246
+
247
+ // Collapse root level
248
+ var tmpRootConditions = tmpStack[0];
249
+ if (tmpRootConditions.length === 0)
250
+ {
251
+ return '';
252
+ }
253
+
254
+ var tmpFilterText = '';
255
+ for (var r = 0; r < tmpRootConditions.length; r++)
256
+ {
257
+ if (r > 0)
258
+ {
259
+ tmpFilterText += ' ' + tmpRootConditions[r].connector + ' ';
260
+ }
261
+ tmpFilterText += tmpRootConditions[r].text;
262
+ }
263
+
264
+ return ' @filter(' + tmpFilterText + ')';
265
+ };
266
+
267
+ /**
268
+ * Generate the field list for a DQL query from dataElements.
269
+ * Always includes uid. Falls back to all schema columns if no dataElements.
270
+ *
271
+ * @method generateFieldList
272
+ * @param {Object} pParameters Query Parameters
273
+ * @return {String} Space-separated field list
274
+ */
275
+ var generateFieldList = function(pParameters)
276
+ {
277
+ var tmpFields = ['uid'];
278
+ var tmpDataElements = pParameters.dataElements;
279
+
280
+ if (Array.isArray(tmpDataElements) && tmpDataElements.length > 0)
281
+ {
282
+ for (var i = 0; i < tmpDataElements.length; i++)
283
+ {
284
+ var tmpField = tmpDataElements[i];
285
+ if (Array.isArray(tmpField))
286
+ {
287
+ tmpField = tmpField[0];
288
+ }
289
+ tmpField = stripTablePrefix(tmpField);
290
+ if (tmpField !== '*' && tmpFields.indexOf(tmpField) < 0)
291
+ {
292
+ tmpFields.push(tmpField);
293
+ }
294
+ }
295
+ }
296
+ else
297
+ {
298
+ // Use all schema columns
299
+ var tmpSchema = Array.isArray(pParameters.query.schema) ? pParameters.query.schema : [];
300
+ for (var j = 0; j < tmpSchema.length; j++)
301
+ {
302
+ var tmpCol = tmpSchema[j].Column;
303
+ if (tmpFields.indexOf(tmpCol) < 0)
304
+ {
305
+ tmpFields.push(tmpCol);
306
+ }
307
+ }
308
+ }
309
+
310
+ // If we still only have uid, include dgraph.type for completeness
311
+ if (tmpFields.length === 1)
312
+ {
313
+ tmpFields.push('dgraph.type');
314
+ }
315
+
316
+ return tmpFields.join(' ');
317
+ };
318
+
319
+ /**
320
+ * Generate DQL sort parameters from sort array.
321
+ *
322
+ * @method generateSort
323
+ * @param {Object} pParameters Query Parameters
324
+ * @return {String} Sort parameters (e.g. "orderasc: Name") or empty string
325
+ */
326
+ var generateSort = function(pParameters)
327
+ {
328
+ var tmpSort = pParameters.sort;
329
+ if (!Array.isArray(tmpSort) || tmpSort.length < 1)
330
+ {
331
+ return '';
332
+ }
333
+
334
+ var tmpParts = [];
335
+ for (var i = 0; i < tmpSort.length; i++)
336
+ {
337
+ var tmpColumn = stripTablePrefix(tmpSort[i].Column);
338
+ var tmpDir = (tmpSort[i].Direction === 'Descending') ? 'orderdesc' : 'orderasc';
339
+ tmpParts.push(tmpDir + ': ' + tmpColumn);
340
+ }
341
+
342
+ return tmpParts.join(', ');
343
+ };
344
+
345
+ /**
346
+ * Generate DQL pagination parameters from begin/cap.
347
+ *
348
+ * @method generatePagination
349
+ * @param {Object} pParameters Query Parameters
350
+ * @return {String} Pagination parameters (e.g. "first: 10, offset: 5") or empty string
351
+ */
352
+ var generatePagination = function(pParameters)
353
+ {
354
+ var tmpParts = [];
355
+
356
+ if (pParameters.cap)
357
+ {
358
+ tmpParts.push('first: ' + pParameters.cap);
359
+ }
360
+
361
+ if (pParameters.begin !== false && pParameters.begin > 0)
362
+ {
363
+ tmpParts.push('offset: ' + pParameters.begin);
364
+ }
365
+
366
+ return tmpParts.join(', ');
367
+ };
368
+
369
+ /**
370
+ * Generate the document for a create (mutation set) operation.
371
+ * Walks the record through the schema to handle special column types.
372
+ *
373
+ * @method generateCreateDocument
374
+ * @param {Object} pParameters Query Parameters
375
+ * @return {Object|false} Document object or false if no record
376
+ */
377
+ var generateCreateDocument = function(pParameters)
378
+ {
379
+ var tmpRecords = pParameters.query.records;
380
+ if (!Array.isArray(tmpRecords) || tmpRecords.length < 1)
381
+ {
382
+ return false;
383
+ }
384
+
385
+ var tmpSchema = Array.isArray(pParameters.query.schema) ? pParameters.query.schema : [];
386
+ var tmpDocument = {};
387
+
388
+ for (var tmpColumn in tmpRecords[0])
389
+ {
390
+ var tmpSchemaEntry = findSchemaEntry(tmpColumn, tmpSchema);
391
+
392
+ if (!pParameters.query.disableDeleteTracking)
393
+ {
394
+ if (tmpSchemaEntry.Type === 'DeleteDate' ||
395
+ tmpSchemaEntry.Type === 'DeleteIDUser')
396
+ {
397
+ continue;
398
+ }
399
+ }
400
+
401
+ switch (tmpSchemaEntry.Type)
402
+ {
403
+ case 'AutoIdentity':
404
+ if (pParameters.query.disableAutoIdentity)
405
+ {
406
+ tmpDocument[tmpColumn] = tmpRecords[0][tmpColumn];
407
+ }
408
+ else
409
+ {
410
+ tmpDocument[tmpColumn] = '$$AUTOINCREMENT';
411
+ }
412
+ break;
413
+ case 'AutoGUID':
414
+ if (pParameters.query.disableAutoIdentity)
415
+ {
416
+ tmpDocument[tmpColumn] = tmpRecords[0][tmpColumn];
417
+ }
418
+ else if (tmpRecords[0][tmpColumn] &&
419
+ tmpRecords[0][tmpColumn].length >= 5 &&
420
+ tmpRecords[0][tmpColumn] !== '0x0000000000000000')
421
+ {
422
+ tmpDocument[tmpColumn] = tmpRecords[0][tmpColumn];
423
+ }
424
+ else
425
+ {
426
+ tmpDocument[tmpColumn] = pParameters.query.UUID;
427
+ }
428
+ break;
429
+ case 'UpdateDate':
430
+ case 'CreateDate':
431
+ if (pParameters.query.disableAutoDateStamp)
432
+ {
433
+ tmpDocument[tmpColumn] = tmpRecords[0][tmpColumn];
434
+ }
435
+ else
436
+ {
437
+ tmpDocument[tmpColumn] = '$$NOW';
438
+ }
439
+ break;
440
+ case 'DeleteIDUser':
441
+ case 'UpdateIDUser':
442
+ case 'CreateIDUser':
443
+ if (pParameters.query.disableAutoUserStamp)
444
+ {
445
+ tmpDocument[tmpColumn] = tmpRecords[0][tmpColumn];
446
+ }
447
+ else
448
+ {
449
+ tmpDocument[tmpColumn] = pParameters.query.IDUser;
450
+ }
451
+ break;
452
+ case 'Deleted':
453
+ tmpDocument[tmpColumn] = 0;
454
+ break;
455
+ default:
456
+ tmpDocument[tmpColumn] = tmpRecords[0][tmpColumn];
457
+ break;
458
+ }
459
+ }
460
+
461
+ if (Object.keys(tmpDocument).length === 0)
462
+ {
463
+ return false;
464
+ }
465
+
466
+ return tmpDocument;
467
+ };
468
+
469
+ /**
470
+ * Generate the update fields for a mutation operation.
471
+ * Walks the record through the schema, skipping identity/create/delete columns.
472
+ *
473
+ * @method generateUpdateDocument
474
+ * @param {Object} pParameters Query Parameters
475
+ * @return {Object|false} Update document or false if no record
476
+ */
477
+ var generateUpdateDocument = function(pParameters)
478
+ {
479
+ var tmpRecords = pParameters.query.records;
480
+ if (!Array.isArray(tmpRecords) || tmpRecords.length < 1)
481
+ {
482
+ return false;
483
+ }
484
+
485
+ var tmpSchema = Array.isArray(pParameters.query.schema) ? pParameters.query.schema : [];
486
+ var tmpUpdateDoc = {};
487
+
488
+ for (var tmpColumn in tmpRecords[0])
489
+ {
490
+ var tmpSchemaEntry = findSchemaEntry(tmpColumn, tmpSchema);
491
+
492
+ if (pParameters.query.disableAutoDateStamp &&
493
+ tmpSchemaEntry.Type === 'UpdateDate')
494
+ {
495
+ continue;
496
+ }
497
+ if (pParameters.query.disableAutoUserStamp &&
498
+ tmpSchemaEntry.Type === 'UpdateIDUser')
499
+ {
500
+ continue;
501
+ }
502
+
503
+ switch (tmpSchemaEntry.Type)
504
+ {
505
+ case 'AutoIdentity':
506
+ case 'CreateDate':
507
+ case 'CreateIDUser':
508
+ case 'DeleteDate':
509
+ case 'DeleteIDUser':
510
+ continue;
511
+ }
512
+
513
+ switch (tmpSchemaEntry.Type)
514
+ {
515
+ case 'UpdateDate':
516
+ tmpUpdateDoc[tmpColumn] = '$$NOW';
517
+ break;
518
+ case 'UpdateIDUser':
519
+ tmpUpdateDoc[tmpColumn] = pParameters.query.IDUser;
520
+ break;
521
+ default:
522
+ tmpUpdateDoc[tmpColumn] = tmpRecords[0][tmpColumn];
523
+ break;
524
+ }
525
+ }
526
+
527
+ if (Object.keys(tmpUpdateDoc).length === 0)
528
+ {
529
+ return false;
530
+ }
531
+
532
+ return tmpUpdateDoc;
533
+ };
534
+
535
+ /**
536
+ * Generate the soft-delete setters.
537
+ * Returns false if no Deleted column exists or delete tracking is disabled.
538
+ *
539
+ * @method generateDeleteSetters
540
+ * @param {Object} pParameters Query Parameters
541
+ * @return {Object|false} Delete setters or false
542
+ */
543
+ var generateDeleteSetters = function(pParameters)
544
+ {
545
+ if (pParameters.query.disableDeleteTracking)
546
+ {
547
+ return false;
548
+ }
549
+
550
+ var tmpSchema = Array.isArray(pParameters.query.schema) ? pParameters.query.schema : [];
551
+ var tmpHasDeletedField = false;
552
+ var tmpSetters = {};
553
+
554
+ for (var i = 0; i < tmpSchema.length; i++)
555
+ {
556
+ var tmpSchemaEntry = tmpSchema[i];
557
+ switch (tmpSchemaEntry.Type)
558
+ {
559
+ case 'Deleted':
560
+ tmpSetters[tmpSchemaEntry.Column] = 1;
561
+ tmpHasDeletedField = true;
562
+ break;
563
+ case 'DeleteDate':
564
+ tmpSetters[tmpSchemaEntry.Column] = '$$NOW';
565
+ break;
566
+ case 'UpdateDate':
567
+ tmpSetters[tmpSchemaEntry.Column] = '$$NOW';
568
+ break;
569
+ case 'DeleteIDUser':
570
+ tmpSetters[tmpSchemaEntry.Column] = pParameters.query.IDUser;
571
+ break;
572
+ default:
573
+ continue;
574
+ }
575
+ }
576
+
577
+ if (!tmpHasDeletedField || Object.keys(tmpSetters).length === 0)
578
+ {
579
+ return false;
580
+ }
581
+
582
+ return tmpSetters;
583
+ };
584
+
585
+ /**
586
+ * Generate the undelete setters.
587
+ * Returns false if no Deleted column exists.
588
+ *
589
+ * @method generateUndeleteSetters
590
+ * @param {Object} pParameters Query Parameters
591
+ * @return {Object|false} Undelete setters or false
592
+ */
593
+ var generateUndeleteSetters = function(pParameters)
594
+ {
595
+ var tmpSchema = Array.isArray(pParameters.query.schema) ? pParameters.query.schema : [];
596
+ var tmpHasDeletedField = false;
597
+ var tmpSetters = {};
598
+
599
+ for (var i = 0; i < tmpSchema.length; i++)
600
+ {
601
+ var tmpSchemaEntry = tmpSchema[i];
602
+ switch (tmpSchemaEntry.Type)
603
+ {
604
+ case 'Deleted':
605
+ tmpSetters[tmpSchemaEntry.Column] = 0;
606
+ tmpHasDeletedField = true;
607
+ break;
608
+ case 'UpdateDate':
609
+ tmpSetters[tmpSchemaEntry.Column] = '$$NOW';
610
+ break;
611
+ case 'UpdateIDUser':
612
+ tmpSetters[tmpSchemaEntry.Column] = pParameters.query.IDUser;
613
+ break;
614
+ default:
615
+ continue;
616
+ }
617
+ }
618
+
619
+ if (!tmpHasDeletedField || Object.keys(tmpSetters).length === 0)
620
+ {
621
+ return false;
622
+ }
623
+
624
+ return tmpSetters;
625
+ };
626
+
627
+ /**
628
+ * Build the func: arguments portion of a DQL query root.
629
+ * Combines type filter, pagination, and sort.
630
+ *
631
+ * @method buildFuncArgs
632
+ * @param {String} pType DGraph type name
633
+ * @param {String} pPagination Pagination string
634
+ * @param {String} pSort Sort string
635
+ * @return {String} Func arguments (e.g. "func: type(Animal), first: 10, orderasc: Name")
636
+ */
637
+ var buildFuncArgs = function(pType, pPagination, pSort)
638
+ {
639
+ var tmpParts = ['func: type(' + pType + ')'];
640
+
641
+ if (pPagination)
642
+ {
643
+ tmpParts.push(pPagination);
644
+ }
645
+
646
+ if (pSort)
647
+ {
648
+ tmpParts.push(pSort);
649
+ }
650
+
651
+ return tmpParts.join(', ');
652
+ };
653
+
654
+
655
+ /**
656
+ * Create a new record
657
+ *
658
+ * @method Create
659
+ * @param {Object} pParameters Query parameters
660
+ * @return {String} JSON operation descriptor or false
661
+ */
662
+ var Create = function(pParameters)
663
+ {
664
+ var tmpDocument = generateCreateDocument(pParameters);
665
+
666
+ if (!tmpDocument)
667
+ {
668
+ return false;
669
+ }
670
+
671
+ // Add DGraph type predicate
672
+ tmpDocument['dgraph.type'] = pParameters.scope;
673
+
674
+ // Determine if we need a counter scope for auto-increment
675
+ var tmpCounterScope = false;
676
+ var tmpSchema = Array.isArray(pParameters.query.schema) ? pParameters.query.schema : [];
677
+ for (var i = 0; i < tmpSchema.length; i++)
678
+ {
679
+ if (tmpSchema[i].Type === 'AutoIdentity' && !pParameters.query.disableAutoIdentity)
680
+ {
681
+ tmpCounterScope = pParameters.scope + '.' + tmpSchema[i].Column;
682
+ break;
683
+ }
684
+ }
685
+
686
+ var tmpResult = {
687
+ type: pParameters.scope,
688
+ operation: 'mutate',
689
+ mutationType: 'set',
690
+ document: tmpDocument
691
+ };
692
+
693
+ if (tmpCounterScope)
694
+ {
695
+ tmpResult.counterScope = tmpCounterScope;
696
+ }
697
+
698
+ pParameters.query.parameters.dgraphOperation = tmpResult;
699
+
700
+ return JSON.stringify(tmpResult);
701
+ };
702
+
703
+
704
+ /**
705
+ * Read one or many records
706
+ *
707
+ * @method Read
708
+ * @param {Object} pParameters Query parameters
709
+ * @return {String} JSON operation descriptor
710
+ */
711
+ var Read = function(pParameters)
712
+ {
713
+ if (pParameters.join && Array.isArray(pParameters.join) && pParameters.join.length > 0)
714
+ {
715
+ _Fable.log.warn('DGraph dialect does not support JOINs; join parameter will be ignored.');
716
+ }
717
+
718
+ var tmpFilterClause = generateFilter(pParameters);
719
+ var tmpFieldList = generateFieldList(pParameters);
720
+ var tmpSort = generateSort(pParameters);
721
+ var tmpPagination = generatePagination(pParameters);
722
+
723
+ var tmpFuncArgs = buildFuncArgs(pParameters.scope, tmpPagination, tmpSort);
724
+
725
+ var tmpDQL = '{ results(' + tmpFuncArgs + ')' + tmpFilterClause + ' { ' + tmpFieldList + ' } }';
726
+
727
+ var tmpResult = {
728
+ type: pParameters.scope,
729
+ operation: 'query',
730
+ query: tmpDQL,
731
+ queryName: 'results'
732
+ };
733
+
734
+ if (pParameters.distinct)
735
+ {
736
+ tmpResult.distinct = true;
737
+ }
738
+
739
+ pParameters.query.parameters.dgraphOperation = tmpResult;
740
+
741
+ return JSON.stringify(tmpResult);
742
+ };
743
+
744
+ /**
745
+ * Update one or many records
746
+ *
747
+ * @method Update
748
+ * @param {Object} pParameters Query parameters
749
+ * @return {String} JSON operation descriptor or false
750
+ */
751
+ var Update = function(pParameters)
752
+ {
753
+ var tmpFilterClause = generateFilter(pParameters);
754
+ var tmpUpdateDoc = generateUpdateDocument(pParameters);
755
+
756
+ if (!tmpUpdateDoc)
757
+ {
758
+ return false;
759
+ }
760
+
761
+ // Build a DQL query to find the UIDs of matching nodes
762
+ var tmpFuncArgs = buildFuncArgs(pParameters.scope, '', '');
763
+ var tmpQueryForUIDs = '{ updateTargets(' + tmpFuncArgs + ')' + tmpFilterClause + ' { uid } }';
764
+
765
+ var tmpResult = {
766
+ type: pParameters.scope,
767
+ operation: 'upsert',
768
+ queryForUIDs: tmpQueryForUIDs,
769
+ queryName: 'updateTargets',
770
+ update: tmpUpdateDoc
771
+ };
772
+
773
+ pParameters.query.parameters.dgraphOperation = tmpResult;
774
+
775
+ return JSON.stringify(tmpResult);
776
+ };
777
+
778
+ /**
779
+ * Delete one or many records (soft or hard depending on schema)
780
+ *
781
+ * @method Delete
782
+ * @param {Object} pParameters Query parameters
783
+ * @return {String} JSON operation descriptor
784
+ */
785
+ var Delete = function(pParameters)
786
+ {
787
+ var tmpDeleteSetters = generateDeleteSetters(pParameters);
788
+ var tmpFilterClause = generateFilter(pParameters);
789
+
790
+ // Build a DQL query to find the UIDs of matching nodes
791
+ var tmpFuncArgs = buildFuncArgs(pParameters.scope, '', '');
792
+ var tmpQueryForUIDs = '{ deleteTargets(' + tmpFuncArgs + ')' + tmpFilterClause + ' { uid } }';
793
+
794
+ if (tmpDeleteSetters)
795
+ {
796
+ // Soft delete via mutation
797
+ var tmpResult = {
798
+ type: pParameters.scope,
799
+ operation: 'upsert',
800
+ queryForUIDs: tmpQueryForUIDs,
801
+ queryName: 'deleteTargets',
802
+ update: tmpDeleteSetters
803
+ };
804
+ pParameters.query.parameters.dgraphOperation = tmpResult;
805
+ return JSON.stringify(tmpResult);
806
+ }
807
+ else
808
+ {
809
+ // Hard delete
810
+ var tmpHardResult = {
811
+ type: pParameters.scope,
812
+ operation: 'delete',
813
+ queryForUIDs: tmpQueryForUIDs,
814
+ queryName: 'deleteTargets'
815
+ };
816
+ pParameters.query.parameters.dgraphOperation = tmpHardResult;
817
+ return JSON.stringify(tmpHardResult);
818
+ }
819
+ };
820
+
821
+ /**
822
+ * Undelete (restore) a soft-deleted record
823
+ *
824
+ * @method Undelete
825
+ * @param {Object} pParameters Query parameters
826
+ * @return {String} JSON operation descriptor
827
+ */
828
+ var Undelete = function(pParameters)
829
+ {
830
+ var tmpUndeleteSetters = generateUndeleteSetters(pParameters);
831
+
832
+ // Temporarily disable delete tracking for filter generation
833
+ // so we can find records where Deleted=1
834
+ var tmpDeleteTrackingState = pParameters.query.disableDeleteTracking;
835
+ pParameters.query.disableDeleteTracking = true;
836
+ var tmpFilterClause = generateFilter(pParameters);
837
+ pParameters.query.disableDeleteTracking = tmpDeleteTrackingState;
838
+
839
+ if (tmpUndeleteSetters)
840
+ {
841
+ var tmpFuncArgs = buildFuncArgs(pParameters.scope, '', '');
842
+ var tmpQueryForUIDs = '{ undeleteTargets(' + tmpFuncArgs + ')' + tmpFilterClause + ' { uid } }';
843
+
844
+ var tmpResult = {
845
+ type: pParameters.scope,
846
+ operation: 'upsert',
847
+ queryForUIDs: tmpQueryForUIDs,
848
+ queryName: 'undeleteTargets',
849
+ update: tmpUndeleteSetters
850
+ };
851
+ pParameters.query.parameters.dgraphOperation = tmpResult;
852
+ return JSON.stringify(tmpResult);
853
+ }
854
+ else
855
+ {
856
+ // No-op -- can't undelete without a Deleted column
857
+ var tmpNoopResult = {
858
+ type: pParameters.scope,
859
+ operation: 'noop'
860
+ };
861
+ pParameters.query.parameters.dgraphOperation = tmpNoopResult;
862
+ return JSON.stringify(tmpNoopResult);
863
+ }
864
+ };
865
+
866
+ /**
867
+ * Count records
868
+ *
869
+ * @method Count
870
+ * @param {Object} pParameters Query parameters
871
+ * @return {String} JSON operation descriptor
872
+ */
873
+ var Count = function(pParameters)
874
+ {
875
+ var tmpFilterClause = generateFilter(pParameters);
876
+
877
+ var tmpFuncArgs = buildFuncArgs(pParameters.scope, '', '');
878
+
879
+ var tmpDQL = '{ results(' + tmpFuncArgs + ')' + tmpFilterClause + ' { total: count(uid) } }';
880
+
881
+ var tmpResult = {
882
+ type: pParameters.scope,
883
+ operation: 'query',
884
+ query: tmpDQL,
885
+ queryName: 'results',
886
+ isCount: true
887
+ };
888
+
889
+ if (pParameters.distinct)
890
+ {
891
+ tmpResult.distinct = true;
892
+ var tmpDataElements = pParameters.dataElements;
893
+ if (Array.isArray(tmpDataElements) && tmpDataElements.length > 0)
894
+ {
895
+ var tmpFields = [];
896
+ for (var i = 0; i < tmpDataElements.length; i++)
897
+ {
898
+ var tmpField = Array.isArray(tmpDataElements[i]) ? tmpDataElements[i][0] : tmpDataElements[i];
899
+ tmpField = stripTablePrefix(tmpField);
900
+ if (tmpField !== '*')
901
+ {
902
+ tmpFields.push(tmpField);
903
+ }
904
+ }
905
+ if (tmpFields.length > 0)
906
+ {
907
+ tmpResult.distinctFields = tmpFields;
908
+ }
909
+ }
910
+ else
911
+ {
912
+ // Fall back to AutoIdentity column from schema
913
+ var tmpSchema = Array.isArray(pParameters.query.schema) ? pParameters.query.schema : [];
914
+ for (var j = 0; j < tmpSchema.length; j++)
915
+ {
916
+ if (tmpSchema[j].Type === 'AutoIdentity')
917
+ {
918
+ tmpResult.distinctFields = [tmpSchema[j].Column];
919
+ break;
920
+ }
921
+ }
922
+ }
923
+ }
924
+
925
+ pParameters.query.parameters.dgraphOperation = tmpResult;
926
+
927
+ return JSON.stringify(tmpResult);
928
+ };
929
+
930
+ var tmpDialect = ({
931
+ Create: Create,
932
+ Read: Read,
933
+ Update: Update,
934
+ Delete: Delete,
935
+ Undelete: Undelete,
936
+ Count: Count
937
+ });
938
+
939
+ /**
940
+ * Dialect Name
941
+ *
942
+ * @property name
943
+ * @type string
944
+ */
945
+ Object.defineProperty(tmpDialect, 'name',
946
+ {
947
+ get: function() { return 'DGraph'; },
948
+ enumerable: true
949
+ });
950
+
951
+ return tmpDialect;
952
+ };
953
+
954
+ module.exports = FoxHoundDialectDGraph;