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.
- package/README.md +3 -2
- package/docs/README.md +32 -11
- package/docs/_cover.md +3 -2
- package/docs/_sidebar.md +21 -1
- package/docs/_topbar.md +7 -0
- package/docs/api/README.md +73 -0
- package/docs/api/addFilter.md +170 -0
- package/docs/api/addJoin.md +128 -0
- package/docs/api/addRecord.md +108 -0
- package/docs/api/addSort.md +109 -0
- package/docs/api/behaviorFlags.md +123 -0
- package/docs/api/buildQuery.md +146 -0
- package/docs/api/clone.md +115 -0
- package/docs/api/queryOverrides.md +82 -0
- package/docs/api/setCap.md +115 -0
- package/docs/api/setDataElements.md +95 -0
- package/docs/api/setDialect.md +78 -0
- package/docs/api/setDistinct.md +76 -0
- package/docs/api/setIDUser.md +80 -0
- package/docs/api/setScope.md +70 -0
- package/docs/architecture.md +134 -42
- package/docs/dialects/README.md +2 -0
- package/docs/dialects/postgresql.md +126 -0
- package/docs/quickstart.md +196 -0
- package/docs/retold-catalog.json +40 -1
- package/docs/retold-keyword-index.json +7996 -2630
- package/package.json +2 -2
- package/source/Foxhound-Dialects.js +4 -0
- package/source/dialects/DGraph/FoxHound-Dialect-DGraph.js +954 -0
- package/source/dialects/MongoDB/FoxHound-Dialect-MongoDB.js +902 -0
- package/source/dialects/PostgreSQL/FoxHound-Dialect-PostgreSQL.js +865 -0
- package/source/dialects/Solr/FoxHound-Dialect-Solr.js +895 -0
- package/test/FoxHound-Dialect-DGraph_tests.js +547 -0
- package/test/FoxHound-Dialect-MongoDB_tests.js +485 -0
- package/test/FoxHound-Dialect-PostgreSQL_tests.js +342 -0
- 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;
|