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