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,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;
|