foxhound 2.0.9 → 2.0.11

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,908 @@
1
+ /**
2
+ * FoxHound SQLite Dialect
3
+ *
4
+ * @license MIT
5
+ *
6
+ * For an SQLite query override:
7
+ // An underscore template with the following values:
8
+ // <%= DataElements %> = Field1, Field2, Field3, Field4
9
+ // <%= Begin %> = 0
10
+ // <%= Cap %> = 10
11
+ // <%= Filter %> = WHERE StartDate > :MyStartDate
12
+ // <%= Sort %> = ORDER BY Field1
13
+ // The values are empty strings if they aren't set.
14
+
15
+ // SQLite 3 also supports named parameters in 3 syntaxes: $name, @name, and :name.
16
+ // To keep things simple and consistent with other providers, we'll use the :name syntax.
17
+ *
18
+ * @author Steven Velozo <steven@velozo.com>
19
+ * @class FoxHoundDialectSQLite
20
+ */
21
+
22
+ var FoxHoundDialectSQLite = function(pFable)
23
+ {
24
+ //Request time from SQL server with microseconds resolution
25
+ const SQL_NOW = "NOW(3)";
26
+
27
+ _Fable = pFable;
28
+
29
+ /**
30
+ * Generate a table name from the scope.
31
+ *
32
+ * Because SQLite is all in-memory, and can be run in two modes (anonymous
33
+ * working on arrays or table-based) we are going to make this a programmable
34
+ * value. Then we can share the code across both providers.
35
+ *
36
+ * @method: generateTableName
37
+ * @param: {Object} pParameters SQL Query Parameters
38
+ * @return: {String} Returns the table name clause
39
+ */
40
+ var generateTableName = function(pParameters)
41
+ {
42
+ return ' '+pParameters.scope;
43
+ };
44
+
45
+ /**
46
+ * Escape columns, because SQLite has more reserved KWs than most SQL dialects
47
+ */
48
+ var escapeColumn = (pColumn, pParameters) =>
49
+ {
50
+ if (pColumn.indexOf('.') < 0)
51
+ {
52
+ return '`'+pColumn+'`';
53
+ }
54
+ else
55
+ {
56
+ // This could suck if the scope is not the same
57
+ var tmpTableName = pParameters.scope;
58
+ if (pColumn.indexOf(tmpTableName+'.') > -1)
59
+ {
60
+ return '`'+pColumn.replace(tmpTableName+'.', '')+'`';
61
+ }
62
+ else
63
+ {
64
+ // This doesn't work well but we'll try it.
65
+ return '`'+pColumn+'`';
66
+ }
67
+ }
68
+ };
69
+
70
+ /**
71
+ * Generate a field list from the array of dataElements
72
+ *
73
+ * Each entry in the dataElements is a simple string
74
+ *
75
+ * @method: generateFieldList
76
+ * @param: {Object} pParameters SQL Query Parameters
77
+ * @param {Boolean} pIsForCountClause (optional) If true, generate fields for use within a count clause.
78
+ * @return: {String} Returns the field list clause, or empty string if explicit fields are requested but cannot be fulfilled
79
+ * due to missing schema.
80
+ */
81
+ var generateFieldList = function(pParameters, pIsForCountClause)
82
+ {
83
+ var tmpDataElements = pParameters.dataElements;
84
+ if (!Array.isArray(tmpDataElements) || tmpDataElements.length < 1)
85
+ {
86
+ if (!pIsForCountClause)
87
+ {
88
+ return ' *';
89
+ }
90
+ // we need to list all of the table fields explicitly; get them from the schema
91
+ const tmpSchema = Array.isArray(pParameters.query.schema) ? pParameters.query.schema : [];
92
+ if (tmpSchema.length < 1)
93
+ {
94
+ // this means we have no schema; returning an empty string here signals the calling code to handle this case
95
+ return '';
96
+ }
97
+ const idColumn = tmpSchema.find((entry) => entry.Type === 'AutoIdentity');
98
+ if (!idColumn)
99
+ {
100
+ // this means there is no autoincrementing unique ID column; treat as above
101
+ return '';
102
+ }
103
+ return ` ${idColumn.Column}`;
104
+ }
105
+
106
+ var tmpFieldList = ' ';
107
+ for (var i = 0; i < tmpDataElements.length; i++)
108
+ {
109
+ if (i > 0)
110
+ {
111
+ tmpFieldList += ', ';
112
+ }
113
+ tmpFieldList += escapeColumn(tmpDataElements[i], pParameters);
114
+ }
115
+ return tmpFieldList;
116
+ };
117
+
118
+ /**
119
+ * Generate a query from the array of where clauses
120
+ *
121
+ * Each clause is an object like:
122
+ {
123
+ Column:'Name',
124
+ Operator:'EQ',
125
+ Value:'John',
126
+ Connector:'And',
127
+ Parameter:'Name'
128
+ }
129
+ *
130
+ * @method: generateWhere
131
+ * @param: {Object} pParameters SQL Query Parameters
132
+ * @return: {String} Returns the WHERE clause prefixed with WHERE, or an empty string if unnecessary
133
+ */
134
+ var generateWhere = function(pParameters)
135
+ {
136
+ var tmpFilter = Array.isArray(pParameters.filter) ? pParameters.filter : [];
137
+ var tmpTableName = generateTableName(pParameters).trim();
138
+
139
+ if (!pParameters.query.disableDeleteTracking)
140
+ {
141
+ // Check if there is a Deleted column on the Schema. If so, we add this to the filters automatically (if not already present)
142
+ var tmpSchema = Array.isArray(pParameters.query.schema) ? pParameters.query.schema : [];
143
+ for (var i = 0; i < tmpSchema.length; i++)
144
+ {
145
+ // There is a schema entry for it. Process it accordingly.
146
+ var tmpSchemaEntry = tmpSchema[i];
147
+
148
+ if (tmpSchemaEntry.Type === 'Deleted')
149
+ {
150
+ var tmpHasDeletedParameter = false;
151
+
152
+ //first, check to see if filters are already looking for Deleted column
153
+ if (tmpFilter.length > 0)
154
+ {
155
+ for (var x = 0; x < tmpFilter.length; x++)
156
+ {
157
+ if (tmpFilter[x].Column === tmpSchemaEntry.Column)
158
+ {
159
+ tmpHasDeletedParameter = true;
160
+ break;
161
+ }
162
+ }
163
+ }
164
+ if (!tmpHasDeletedParameter)
165
+ {
166
+ //if not, we need to add it
167
+ tmpFilter.push(
168
+ {
169
+ Column: tmpTableName + '.' + tmpSchemaEntry.Column,
170
+ Operator: '=',
171
+ Value: 0,
172
+ Connector: 'AND',
173
+ Parameter: 'Deleted'
174
+ });
175
+ }
176
+ break;
177
+ }
178
+ }
179
+ }
180
+
181
+ if (tmpFilter.length < 1)
182
+ {
183
+ return '';
184
+ }
185
+
186
+ var tmpWhere = ' WHERE';
187
+
188
+ // This is used to disable the connectors for subsequent queries.
189
+ // Only the open parenthesis operator uses this, currently.
190
+ var tmpLastOperatorNoConnector = false;
191
+
192
+ for (var i = 0; i < tmpFilter.length; i++)
193
+ {
194
+ if ((tmpFilter[i].Connector != 'NONE') && (tmpFilter[i].Operator != ')') && (tmpWhere != ' WHERE') && (tmpLastOperatorNoConnector == false))
195
+ {
196
+ tmpWhere += ' '+tmpFilter[i].Connector;
197
+ }
198
+
199
+ tmpLastOperatorNoConnector = false;
200
+
201
+ var tmpColumnParameter;
202
+
203
+ if (tmpFilter[i].Operator === '(')
204
+ {
205
+ // Open a logical grouping
206
+ tmpWhere += ' (';
207
+ tmpLastOperatorNoConnector = true;
208
+ }
209
+ else if (tmpFilter[i].Operator === ')')
210
+ {
211
+ // Close a logical grouping
212
+ tmpWhere += ' )';
213
+ }
214
+ else if (tmpFilter[i].Operator === 'IN')
215
+ {
216
+ tmpColumnParameter = tmpFilter[i].Parameter+'_w'+i;
217
+ // Add the column name, operator and parameter name to the list of where value parenthetical
218
+ tmpWhere += ' '+escapeColumn(tmpFilter[i].Column, pParameters)+' '+tmpFilter[i].Operator+' ( :'+tmpColumnParameter+' )';
219
+ pParameters.query.parameters[tmpColumnParameter] = tmpFilter[i].Value;
220
+ }
221
+ else if (tmpFilter[i].Operator === 'IS NOT NULL')
222
+ {
223
+ // IS NOT NULL is a special operator which doesn't require a value, or parameter
224
+ tmpWhere += ' '+escapeColumn(tmpFilter[i].Column, pParameters)+' '+tmpFilter[i].Operator;
225
+ }
226
+ else
227
+ {
228
+ tmpColumnParameter = tmpFilter[i].Parameter+'_w'+i;
229
+ // Add the column name, operator and parameter name to the list of where value parenthetical
230
+ tmpWhere += ' '+escapeColumn(tmpFilter[i].Column, pParameters)+' '+tmpFilter[i].Operator+' :'+tmpColumnParameter;
231
+ pParameters.query.parameters[tmpColumnParameter] = tmpFilter[i].Value;
232
+ }
233
+ }
234
+
235
+ return tmpWhere;
236
+ };
237
+
238
+ /**
239
+ * Generate an ORDER BY clause from the sort array
240
+ *
241
+ * Each entry in the sort is an object like:
242
+ * {Column:'Color',Direction:'Descending'}
243
+ *
244
+ * @method: generateOrderBy
245
+ * @param: {Object} pParameters SQL Query Parameters
246
+ * @return: {String} Returns the field list clause
247
+ */
248
+ var generateOrderBy = function(pParameters)
249
+ {
250
+ var tmpOrderBy = pParameters.sort;
251
+ if (!Array.isArray(tmpOrderBy) || tmpOrderBy.length < 1)
252
+ {
253
+ return '';
254
+ }
255
+
256
+ var tmpOrderClause = ' ORDER BY';
257
+ for (var i = 0; i < tmpOrderBy.length; i++)
258
+ {
259
+ if (i > 0)
260
+ {
261
+ tmpOrderClause += ',';
262
+ }
263
+ tmpOrderClause += ' '+escapeColumn(tmpOrderBy[i].Column, pParameters);
264
+
265
+ if (tmpOrderBy[i].Direction == 'Descending')
266
+ {
267
+ tmpOrderClause += ' DESC';
268
+ }
269
+ }
270
+ return tmpOrderClause;
271
+ };
272
+
273
+ /**
274
+ * Generate the limit clause
275
+ *
276
+ * @method: generateLimit
277
+ * @param: {Object} pParameters SQL Query Parameters
278
+ * @return: {String} Returns the table name clause
279
+ */
280
+ var generateLimit = function(pParameters)
281
+ {
282
+ if (!pParameters.cap)
283
+ {
284
+ return '';
285
+ }
286
+
287
+ var tmpLimit = ' LIMIT';
288
+ // Cap is required for a limit clause.
289
+ tmpLimit += ' ' + pParameters.cap;
290
+
291
+ // If there is a begin record, we'll pass that in as well.
292
+ if (pParameters.begin !== false)
293
+ {
294
+ tmpLimit += ' FETCH ' + pParameters.begin;
295
+ }
296
+
297
+ return tmpLimit;
298
+ };
299
+
300
+ /**
301
+ * Generate the update SET clause
302
+ *
303
+ * @method: generateUpdateSetters
304
+ * @param: {Object} pParameters SQL Query Parameters
305
+ * @return: {String} Returns the table name clause
306
+ */
307
+ var generateUpdateSetters = function(pParameters)
308
+ {
309
+ var tmpRecords = pParameters.query.records;
310
+ // We need to tell the query not to generate improperly if there are no values to set.
311
+ if (!Array.isArray(tmpRecords) || tmpRecords.length < 1)
312
+ {
313
+ return false;
314
+ }
315
+
316
+ // Check if there is a schema. If so, we will use it to decide if these are parameterized or not.
317
+ var tmpSchema = Array.isArray(pParameters.query.schema) ? pParameters.query.schema : [];
318
+
319
+ var tmpUpdate = '';
320
+ // If there is more than one record in records, we are going to ignore them for now.
321
+ var tmpCurrentColumn = 0;
322
+ for(var tmpColumn in tmpRecords[0])
323
+ {
324
+ // No hash table yet, so, we will just linear search it for now.
325
+ // This uses the schema to decide if we want to treat a column differently on insert
326
+ var tmpSchemaEntry = {Column:tmpColumn, Type:'Default'};
327
+ for (var i = 0; i < tmpSchema.length; i++)
328
+ {
329
+ if (tmpColumn == tmpSchema[i].Column)
330
+ {
331
+ // There is a schema entry for it. Process it accordingly.
332
+ tmpSchemaEntry = tmpSchema[i];
333
+ break;
334
+ }
335
+ }
336
+
337
+ if (pParameters.query.disableAutoDateStamp &&
338
+ tmpSchemaEntry.Type === 'UpdateDate')
339
+ {
340
+ // This is ignored if flag is set
341
+ continue;
342
+ }
343
+ if (pParameters.query.disableAutoUserStamp &&
344
+ tmpSchemaEntry.Type === 'UpdateIDUser')
345
+ {
346
+ // This is ignored if flag is set
347
+ continue;
348
+ }
349
+
350
+ switch (tmpSchemaEntry.Type)
351
+ {
352
+ case 'AutoIdentity':
353
+ case 'CreateDate':
354
+ case 'CreateIDUser':
355
+ case 'DeleteDate':
356
+ case 'DeleteIDUser':
357
+ // These are all ignored on update
358
+ continue;
359
+ }
360
+ if (tmpCurrentColumn > 0)
361
+ {
362
+ tmpUpdate += ',';
363
+ }
364
+ switch (tmpSchemaEntry.Type)
365
+ {
366
+ case 'UpdateDate':
367
+ // This is an autoidentity, so we don't parameterize it and just pass in NULL
368
+ tmpUpdate += ' '+escapeColumn(tmpColumn, pParameters)+' = NOW()';
369
+ break;
370
+ case 'UpdateIDUser':
371
+ // This is the user ID, which we hope is in the query.
372
+ // This is how to deal with a normal column
373
+ var tmpColumnParameter = tmpColumn+'_'+tmpCurrentColumn;
374
+ tmpUpdate += ' '+escapeColumn(tmpColumn, pParameters)+' = :'+tmpColumnParameter;
375
+ // Set the query parameter
376
+ pParameters.query.parameters[tmpColumnParameter] = pParameters.query.IDUser;
377
+ break;
378
+ default:
379
+ var tmpColumnDefaultParameter = tmpColumn+'_'+tmpCurrentColumn;
380
+ tmpUpdate += ' '+escapeColumn(tmpColumn, pParameters)+' = :'+tmpColumnDefaultParameter;
381
+
382
+ // Set the query parameter
383
+ pParameters.query.parameters[tmpColumnDefaultParameter] = tmpRecords[0][tmpColumn];
384
+ break;
385
+ }
386
+
387
+ // We use a number to make sure parameters are unique.
388
+ tmpCurrentColumn++;
389
+ }
390
+
391
+ // We need to tell the query not to generate improperly if there are no values set.
392
+ if (tmpUpdate === '')
393
+ {
394
+ return false;
395
+ }
396
+
397
+ return tmpUpdate;
398
+ };
399
+
400
+ /**
401
+ * Generate the update-delete SET clause
402
+ *
403
+ * @method: generateUpdateDeleteSetters
404
+ * @param: {Object} pParameters SQL Query Parameters
405
+ * @return: {String} Returns the table name clause
406
+ */
407
+ var generateUpdateDeleteSetters = function(pParameters)
408
+ {
409
+ if (pParameters.query.disableDeleteTracking)
410
+ {
411
+ //Don't generate an UPDATE query if Delete tracking is disabled
412
+ return false;
413
+ }
414
+ // Check if there is a schema. If so, we will use it to decide if these are parameterized or not.
415
+ var tmpSchema = Array.isArray(pParameters.query.schema) ? pParameters.query.schema : [];
416
+
417
+ var tmpCurrentColumn = 0;
418
+ var tmpHasDeletedField = false;
419
+ var tmpUpdate = '';
420
+ // No hash table yet, so, we will just linear search it for now.
421
+ // This uses the schema to decide if we want to treat a column differently on insert
422
+ var tmpSchemaEntry = {Type:'Default'};
423
+ for (var i = 0; i < tmpSchema.length; i++)
424
+ {
425
+ // There is a schema entry for it. Process it accordingly.
426
+ tmpSchemaEntry = tmpSchema[i];
427
+
428
+ var tmpUpdateSql = null;
429
+
430
+ switch (tmpSchemaEntry.Type)
431
+ {
432
+ case 'Deleted':
433
+ tmpUpdateSql = ' '+escapeColumn(tmpSchemaEntry.Column, pParameters)+' = 1';
434
+ tmpHasDeletedField = true; //this field is required in order for query to be built
435
+ break;
436
+ case 'DeleteDate':
437
+ tmpUpdateSql = ' '+escapeColumn(tmpSchemaEntry.Column, pParameters)+' = NOW()';
438
+ break;
439
+ case 'UpdateDate':
440
+ // Delete operation is an Update, so we should stamp the update time
441
+ tmpUpdateSql = ' '+escapeColumn(tmpSchemaEntry.Column, pParameters)+' = NOW()';
442
+ break;
443
+ case 'DeleteIDUser':
444
+ // This is the user ID, which we hope is in the query.
445
+ // This is how to deal with a normal column
446
+ var tmpColumnParameter = tmpSchemaEntry.Column+'_'+tmpCurrentColumn;
447
+ tmpUpdateSql = ' '+escapeColumn(tmpSchemaEntry.Column, pParameters)+' = :'+tmpColumnParameter;
448
+ // Set the query parameter
449
+ pParameters.query.parameters[tmpColumnParameter] = pParameters.query.IDUser;
450
+ break;
451
+ default:
452
+ //DON'T allow update of other fields in this query
453
+ continue;
454
+ }
455
+
456
+ if (tmpCurrentColumn > 0)
457
+ {
458
+ tmpUpdate += ',';
459
+ }
460
+
461
+ tmpUpdate += tmpUpdateSql;
462
+
463
+ // We use a number to make sure parameters are unique.
464
+ tmpCurrentColumn++;
465
+ }
466
+
467
+ // We need to tell the query not to generate improperly if there are no values set.
468
+ if (!tmpHasDeletedField ||
469
+ tmpUpdate === '')
470
+ {
471
+ return false;
472
+ }
473
+
474
+ return tmpUpdate;
475
+ };
476
+
477
+ /**
478
+ * Generate the update-delete SET clause
479
+ *
480
+ * @method: generateUpdateDeleteSetters
481
+ * @param: {Object} pParameters SQL Query Parameters
482
+ * @return: {String} Returns the table name clause
483
+ */
484
+ var generateUpdateUndeleteSetters = function(pParameters)
485
+ {
486
+ // Check if there is a schema. If so, we will use it to decide if these are parameterized or not.
487
+ var tmpSchema = Array.isArray(pParameters.query.schema) ? pParameters.query.schema : [];
488
+
489
+ var tmpCurrentColumn = 0;
490
+ var tmpHasDeletedField = false;
491
+ var tmpUpdate = '';
492
+ // No hash table yet, so, we will just linear search it for now.
493
+ // This uses the schema to decide if we want to treat a column differently on insert
494
+ var tmpSchemaEntry = {Type:'Default'};
495
+ for (var i = 0; i < tmpSchema.length; i++)
496
+ {
497
+ // There is a schema entry for it. Process it accordingly.
498
+ tmpSchemaEntry = tmpSchema[i];
499
+
500
+ var tmpUpdateSql = null;
501
+
502
+ switch (tmpSchemaEntry.Type)
503
+ {
504
+ case 'Deleted':
505
+ tmpUpdateSql = ' '+escapeColumn(tmpSchemaEntry.Column, pParameters)+' = 0';
506
+ tmpHasDeletedField = true; //this field is required in order for query to be built
507
+ break;
508
+ case 'UpdateDate':
509
+ // Delete operation is an Update, so we should stamp the update time
510
+ tmpUpdateSql = ' '+escapeColumn(tmpSchemaEntry.Column, pParameters)+' = NOW()';
511
+ break;
512
+ case 'UpdateIDUser':
513
+ // This is the user ID, which we hope is in the query.
514
+ // This is how to deal with a normal column
515
+ var tmpColumnParameter = tmpSchemaEntry.Column+'_'+tmpCurrentColumn;
516
+ tmpUpdateSql = ' '+escapeColumn(tmpSchemaEntry.Column, pParameters)+' = :'+tmpColumnParameter;
517
+ // Set the query parameter
518
+ pParameters.query.parameters[tmpColumnParameter] = pParameters.query.IDUser;
519
+ break;
520
+ default:
521
+ //DON'T allow update of other fields in this query
522
+ continue;
523
+ }
524
+
525
+ if (tmpCurrentColumn > 0)
526
+ {
527
+ tmpUpdate += ',';
528
+ }
529
+
530
+ tmpUpdate += tmpUpdateSql;
531
+
532
+ // We use a number to make sure parameters are unique.
533
+ tmpCurrentColumn++;
534
+ }
535
+
536
+ // We need to tell the query not to generate improperly if there are no values set.
537
+ if (!tmpHasDeletedField ||
538
+ tmpUpdate === '')
539
+ {
540
+ return false;
541
+ }
542
+
543
+ return tmpUpdate;
544
+ };
545
+
546
+ /**
547
+ * Generate the create SET clause
548
+ *
549
+ * @method: generateCreateSetList
550
+ * @param: {Object} pParameters SQL Query Parameters
551
+ * @return: {String} Returns the table name clause
552
+ */
553
+ var generateCreateSetValues = function(pParameters)
554
+ {
555
+ var tmpRecords = pParameters.query.records;
556
+ // We need to tell the query not to generate improperly if there are no values to set.
557
+ if (!Array.isArray(tmpRecords) || tmpRecords.length < 1)
558
+ {
559
+ return false;
560
+ }
561
+
562
+ // Check if there is a schema. If so, we will use it to decide if these are parameterized or not.
563
+ var tmpSchema = Array.isArray(pParameters.query.schema) ? pParameters.query.schema : [];
564
+
565
+
566
+ var tmpCreateSet = '';
567
+ // If there is more than one record in records, we are going to ignore them for now.
568
+ var tmpCurrentColumn = 0;
569
+ for(var tmpColumn in tmpRecords[0])
570
+ {
571
+ // No hash table yet, so, we will just linear search it for now.
572
+ // This uses the schema to decide if we want to treat a column differently on insert
573
+ var tmpSchemaEntry = {Column:tmpColumn, Type:'Default'};
574
+ for (var i = 0; i < tmpSchema.length; i++)
575
+ {
576
+ if (tmpColumn == tmpSchema[i].Column)
577
+ {
578
+ // There is a schema entry for it. Process it accordingly.
579
+ tmpSchemaEntry = tmpSchema[i];
580
+ break;
581
+ }
582
+ }
583
+
584
+ if (!pParameters.query.disableDeleteTracking)
585
+ {
586
+ if (tmpSchemaEntry.Type === 'DeleteDate' ||
587
+ tmpSchemaEntry.Type === 'DeleteIDUser')
588
+ {
589
+ // These are all ignored on insert (if delete tracking is enabled as normal)
590
+ continue;
591
+ }
592
+ }
593
+
594
+ if (tmpCurrentColumn > 0)
595
+ {
596
+ tmpCreateSet += ',';
597
+ }
598
+
599
+ //define a re-usable method for setting up field definitions in a default pattern
600
+ var buildDefaultDefinition = function()
601
+ {
602
+ var tmpColumnParameter = tmpColumn+'_'+tmpCurrentColumn;
603
+ tmpCreateSet += ' :'+tmpColumnParameter;
604
+ // Set the query parameter
605
+ pParameters.query.parameters[tmpColumnParameter] = tmpRecords[0][tmpColumn];
606
+ };
607
+
608
+ var tmpColumnParameter;
609
+ switch (tmpSchemaEntry.Type)
610
+ {
611
+ case 'AutoIdentity':
612
+ if (pParameters.query.disableAutoIdentity)
613
+ {
614
+ buildDefaultDefinition();
615
+ }
616
+ else
617
+ {
618
+ // This is an autoidentity, so we don't parameterize it and just pass in NULL
619
+ tmpCreateSet += ' NULL';
620
+ }
621
+ break;
622
+ case 'AutoGUID':
623
+ if (pParameters.query.disableAutoIdentity)
624
+ {
625
+ buildDefaultDefinition();
626
+ }
627
+ else if (tmpRecords[0][tmpColumn] &&
628
+ tmpRecords[0][tmpColumn].length >= 5 &&
629
+ tmpRecords[0][tmpColumn] !== '0x0000000000000000') //stricture default
630
+ {
631
+ // Allow consumer to override AutoGUID
632
+ buildDefaultDefinition();
633
+ }
634
+ else
635
+ {
636
+ // This is an autoidentity, so we don't parameterize it and just pass in NULL
637
+ tmpColumnParameter = tmpColumn+'_'+tmpCurrentColumn;
638
+ tmpCreateSet += ' :'+tmpColumnParameter;
639
+ // Set the query parameter
640
+ pParameters.query.parameters[tmpColumnParameter] = pParameters.query.UUID;
641
+ }
642
+ break;
643
+ case 'UpdateDate':
644
+ case 'CreateDate':
645
+ case 'DeleteDate':
646
+ if (pParameters.query.disableAutoDateStamp)
647
+ {
648
+ buildDefaultDefinition();
649
+ }
650
+ else
651
+ {
652
+ // This is an autoidentity, so we don't parameterize it and just pass in NULL
653
+ tmpCreateSet += ' NOW()';
654
+ }
655
+ break;
656
+ case 'UpdateIDUser':
657
+ case 'CreateIDUser':
658
+ case 'DeleteIDUser':
659
+ if (pParameters.query.disableAutoUserStamp)
660
+ {
661
+ buildDefaultDefinition();
662
+ }
663
+ else
664
+ {
665
+ // This is the user ID, which we hope is in the query.
666
+ // This is how to deal with a normal column
667
+ tmpColumnParameter = tmpColumn+'_'+tmpCurrentColumn;
668
+ tmpCreateSet += ' :'+tmpColumnParameter;
669
+ // Set the query parameter
670
+ pParameters.query.parameters[tmpColumnParameter] = pParameters.query.IDUser;
671
+ }
672
+ break;
673
+ default:
674
+ buildDefaultDefinition();
675
+ break;
676
+ }
677
+
678
+ // We use an appended number to make sure parameters are unique.
679
+ tmpCurrentColumn++;
680
+ }
681
+
682
+ // We need to tell the query not to generate improperly if there are no values set.
683
+ if (tmpCreateSet === '')
684
+ {
685
+ return false;
686
+ }
687
+
688
+ return tmpCreateSet;
689
+ };
690
+
691
+ /**
692
+ * Generate the create SET clause
693
+ *
694
+ * @method: generateCreateSetList
695
+ * @param: {Object} pParameters SQL Query Parameters
696
+ * @return: {String} Returns the table name clause
697
+ */
698
+ var generateCreateSetList = function(pParameters)
699
+ {
700
+ // The records were already validated by generateCreateSetValues
701
+ var tmpRecords = pParameters.query.records;
702
+
703
+ // Check if there is a schema. If so, we will use it to decide if these are parameterized or not.
704
+ var tmpSchema = Array.isArray(pParameters.query.schema) ? pParameters.query.schema : [];
705
+
706
+ var tmpCreateSet = '';
707
+ // If there is more than one record in records, we are going to ignore them for now.
708
+ for(var tmpColumn in tmpRecords[0])
709
+ {
710
+ // No hash table yet, so, we will just linear search it for now.
711
+ // This uses the schema to decide if we want to treat a column differently on insert
712
+ var tmpSchemaEntry = {Column:tmpColumn, Type:'Default'};
713
+ for (var i = 0; i < tmpSchema.length; i++)
714
+ {
715
+ if (tmpColumn == tmpSchema[i].Column)
716
+ {
717
+ // There is a schema entry for it. Process it accordingly.
718
+ tmpSchemaEntry = tmpSchema[i];
719
+ break;
720
+ }
721
+ }
722
+ if (!pParameters.query.disableDeleteTracking)
723
+ {
724
+ if (tmpSchemaEntry.Type === 'DeleteDate' ||
725
+ tmpSchemaEntry.Type === 'DeleteIDUser')
726
+ {
727
+ // These are all ignored on insert (if delete tracking is enabled as normal)
728
+ continue;
729
+ }
730
+ }
731
+ switch (tmpSchemaEntry.Type)
732
+ {
733
+ default:
734
+ if (tmpCreateSet != '')
735
+ {
736
+ tmpCreateSet += ',';
737
+ }
738
+ tmpCreateSet += ' '+escapeColumn(tmpColumn, pParameters);
739
+ break;
740
+ }
741
+ }
742
+
743
+ return tmpCreateSet;
744
+ };
745
+
746
+
747
+ var Create = function(pParameters)
748
+ {
749
+ var tmpTableName = generateTableName(pParameters);
750
+ var tmpCreateSetList = generateCreateSetList(pParameters);
751
+ var tmpCreateSetValues = generateCreateSetValues(pParameters);
752
+
753
+ if (!tmpCreateSetValues)
754
+ {
755
+ return false;
756
+ }
757
+
758
+ return 'INSERT INTO'+tmpTableName+' ('+tmpCreateSetList+') VALUES ('+tmpCreateSetValues+');';
759
+ };
760
+
761
+
762
+ /**
763
+ * Read one or many records
764
+ *
765
+ * Some examples:
766
+ * SELECT * FROM WIDGETS;
767
+ * SELECT * FROM WIDGETS LIMIT 0, 20;
768
+ * SELECT * FROM WIDGETS LIMIT 5, 20;
769
+ * SELECT ID, Name, Cost FROM WIDGETS LIMIT 5, 20;
770
+ * SELECT ID, Name, Cost FROM WIDGETS LIMIT 5, 20 WHERE LastName = 'Smith';
771
+ *
772
+ * @method Read
773
+ * @param {Object} pParameters SQL Query parameters
774
+ * @return {String} Returns the current Query for chaining.
775
+ */
776
+ var Read = function(pParameters)
777
+ {
778
+ var tmpFieldList = generateFieldList(pParameters);
779
+ var tmpTableName = generateTableName(pParameters);
780
+ var tmpWhere = generateWhere(pParameters);
781
+ var tmpOrderBy = generateOrderBy(pParameters);
782
+ var tmpLimit = generateLimit(pParameters);
783
+ const tmpOptDistinct = pParameters.distinct ? ' DISTINCT' : '';
784
+
785
+ if (pParameters.queryOverride)
786
+ {
787
+ try
788
+ {
789
+ var tmpQueryTemplate = _Fable.Utility.template(pParameters.queryOverride);
790
+ return tmpQueryTemplate({FieldList:tmpFieldList, TableName:tmpTableName, Where:tmpWhere, OrderBy:tmpOrderBy, Limit:tmpLimit, Distinct: tmpOptDistinct, _Params: pParameters});
791
+ }
792
+ catch (pError)
793
+ {
794
+ // This pokemon is here to give us a convenient way of not throwing up totally if the query fails.
795
+ console.log('Error with custom Read Query ['+pParameters.queryOverride+']: '+pError);
796
+ return false;
797
+ }
798
+ }
799
+
800
+ return `SELECT${tmpOptDistinct}${tmpFieldList} FROM${tmpTableName}${tmpWhere}${tmpOrderBy}${tmpLimit};`;
801
+ };
802
+
803
+ var Update = function(pParameters)
804
+ {
805
+ var tmpTableName = generateTableName(pParameters);
806
+ var tmpWhere = generateWhere(pParameters);
807
+ var tmpUpdateSetters = generateUpdateSetters(pParameters);
808
+
809
+ if (!tmpUpdateSetters)
810
+ {
811
+ return false;
812
+ }
813
+
814
+ return 'UPDATE'+tmpTableName+' SET'+tmpUpdateSetters+tmpWhere+';';
815
+ };
816
+
817
+ var Delete = function(pParameters)
818
+ {
819
+ var tmpTableName = generateTableName(pParameters);
820
+ var tmpWhere = generateWhere(pParameters);
821
+ var tmpUpdateDeleteSetters = generateUpdateDeleteSetters(pParameters);
822
+
823
+ if (tmpUpdateDeleteSetters)
824
+ {
825
+ //If it has a deleted bit, update it instead of actually deleting the record
826
+ return 'UPDATE'+tmpTableName+' SET'+tmpUpdateDeleteSetters+tmpWhere+';';
827
+ }
828
+ else
829
+ {
830
+ return 'DELETE FROM'+tmpTableName+tmpWhere+';';
831
+ }
832
+ };
833
+
834
+ var Undelete = function(pParameters)
835
+ {
836
+ var tmpTableName = generateTableName(pParameters);
837
+ let tmpDeleteTrackingState = pParameters.query.disableDeleteTracking;
838
+ pParameters.query.disableDeleteTracking = true;
839
+ var tmpWhere = generateWhere(pParameters);
840
+ var tmpUpdateUndeleteSetters = generateUpdateUndeleteSetters(pParameters);
841
+ pParameters.query.disableDeleteTracking = tmpDeleteTrackingState;
842
+
843
+ if (tmpUpdateUndeleteSetters)
844
+ {
845
+ //If it has a deleted bit, update it instead of actually deleting the record
846
+ return 'UPDATE'+tmpTableName+' SET'+tmpUpdateUndeleteSetters+tmpWhere+';';
847
+ }
848
+ else
849
+ {
850
+ return 'SELECT NULL;';
851
+ }
852
+ };
853
+
854
+ var Count = function(pParameters)
855
+ {
856
+ var tmpTableName = generateTableName(pParameters);
857
+ var tmpWhere = generateWhere(pParameters);
858
+ const tmpFieldList = pParameters.distinct ? generateFieldList(pParameters, true) : '*';
859
+
860
+ // here, we ignore the distinct keyword if no fields have been specified and
861
+ if (pParameters.distinct && tmpFieldList.length < 1)
862
+ {
863
+ console.warn('Distinct requested but no field list or schema are available, so not honoring distinct for count query.');
864
+ }
865
+ const tmpOptDistinct = pParameters.distinct && tmpFieldList.length > 0 ? 'DISTINCT' : '';
866
+ if (pParameters.queryOverride)
867
+ {
868
+ try
869
+ {
870
+ var tmpQueryTemplate = _Fable.Utility.template(pParameters.queryOverride);
871
+ return tmpQueryTemplate({FieldList:[], TableName:tmpTableName, Where:tmpWhere, OrderBy:'', Limit:'', Distinct: tmpOptDistinct, _Params: pParameters});
872
+ }
873
+ catch (pError)
874
+ {
875
+ // This pokemon is here to give us a convenient way of not throwing up totally if the query fails.
876
+ console.log('Error with custom Count Query ['+pParameters.queryOverride+']: '+pError);
877
+ return false;
878
+ }
879
+ }
880
+
881
+ return `SELECT COUNT(${tmpOptDistinct}${tmpFieldList || '*'}) AS RowCount FROM${tmpTableName}${tmpWhere};`;
882
+ };
883
+
884
+ var tmpDialect = ({
885
+ Create: Create,
886
+ Read: Read,
887
+ Update: Update,
888
+ Delete: Delete,
889
+ Undelete: Undelete,
890
+ Count: Count
891
+ });
892
+
893
+ /**
894
+ * Dialect Name
895
+ *
896
+ * @property name
897
+ * @type string
898
+ */
899
+ Object.defineProperty(tmpDialect, 'name',
900
+ {
901
+ get: function() { return 'SQLite'; },
902
+ enumerable: true
903
+ });
904
+
905
+ return tmpDialect;
906
+ };
907
+
908
+ module.exports = FoxHoundDialectSQLite;