foxhound 2.0.27 → 2.0.28
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 +5 -3
- package/docs/README.md +4 -4
- package/docs/_brand.json +18 -0
- package/docs/_cover.md +1 -1
- package/docs/_topbar.md +1 -1
- package/docs/_version.json +3 -3
- package/docs/api/README.md +14 -14
- package/docs/dialects/README.md +5 -5
- package/docs/index.html +6 -7
- package/docs/quickstart.md +1 -1
- package/docs/retold-catalog.json +245 -161
- package/docs/retold-keyword-index.json +11916 -6383
- package/docs/schema.md +1 -1
- package/package.json +7 -7
- package/source/Foxhound-Dialects.js +1 -0
- package/source/dialects/Oracle/FoxHound-Dialect-Oracle.js +1218 -0
- package/test/Foxhound-Dialect-Oracle_tests.js +463 -0
- package/docs/css/docuserve.css +0 -327
|
@@ -0,0 +1,1218 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FoxHound Oracle Dialect
|
|
3
|
+
*
|
|
4
|
+
* @license MIT
|
|
5
|
+
*
|
|
6
|
+
* For an Oracle 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
|
+
* Oracle notes:
|
|
16
|
+
* - Identifiers are emitted unquoted by default (Oracle folds them to
|
|
17
|
+
* UPPERCASE). Set pParameters.quoteIdentifiers to wrap them in double
|
|
18
|
+
* quotes and preserve the original PascalCase (case-sensitive matching).
|
|
19
|
+
* - Bind parameters use the :name style consumed natively by oracledb.
|
|
20
|
+
* - Pagination uses the 12c+ OFFSET/FETCH clause by default and a
|
|
21
|
+
* ROWNUM double-subquery wrapper when pParameters.legacyPagination is set
|
|
22
|
+
* (for 11g and earlier, which have no OFFSET/FETCH).
|
|
23
|
+
* - INSERT appends a RETURNING <IDColumn> INTO :RETURNING_ID clause when the
|
|
24
|
+
* table has an AutoIdentity column (Oracle has no SCOPE_IDENTITY()).
|
|
25
|
+
* - Generated statements carry no trailing semicolon — oracledb rejects the
|
|
26
|
+
* terminator on non-PL/SQL statements.
|
|
27
|
+
*
|
|
28
|
+
* @author Steven Velozo <steven@velozo.com>
|
|
29
|
+
* @class FoxHoundDialectOracle
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
var FoxHoundDialectOracle = function(pFable)
|
|
33
|
+
{
|
|
34
|
+
// True UTC timestamp regardless of the database server's session time zone.
|
|
35
|
+
const SQL_NOW = "SYS_EXTRACT_UTC(SYSTIMESTAMP)";
|
|
36
|
+
|
|
37
|
+
let _Fable = pFable;
|
|
38
|
+
|
|
39
|
+
// Whether to wrap identifiers in double quotes (preserving case) or emit
|
|
40
|
+
// them bare (folded to UPPERCASE by Oracle). Set from
|
|
41
|
+
// pParameters.quoteIdentifiers at the top of each public dialect method;
|
|
42
|
+
// query building is synchronous so this closure value is safe to reuse.
|
|
43
|
+
let _QuoteIdentifiers = false;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Generate a table name from the scope
|
|
47
|
+
*
|
|
48
|
+
* @method: generateTableName
|
|
49
|
+
* @param: {Object} pParameters SQL Query Parameters
|
|
50
|
+
* @return: {String} Returns the table name clause
|
|
51
|
+
*/
|
|
52
|
+
var generateTableName = function(pParameters)
|
|
53
|
+
{
|
|
54
|
+
// Every Foxhound query has a table name; this lazily creates the
|
|
55
|
+
// parameterTypes map even for column-less queries (e.g. COUNT(*)).
|
|
56
|
+
if (!pParameters.query.hasOwnProperty('parameterTypes'))
|
|
57
|
+
{
|
|
58
|
+
pParameters.query.parameterTypes = {};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (_QuoteIdentifiers)
|
|
62
|
+
{
|
|
63
|
+
if (pParameters.scope && pParameters.scope.indexOf('"') >= 0)
|
|
64
|
+
{
|
|
65
|
+
return ' '+pParameters.scope;
|
|
66
|
+
}
|
|
67
|
+
return ' "'+pParameters.scope+'"';
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return ' '+pParameters.scope;
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Record the oracledb bind type for a parameter, keyed by parameter name.
|
|
75
|
+
*
|
|
76
|
+
* These are driver-free strings ('NUMBER', 'STRING', 'CLOB', 'DATE') so the
|
|
77
|
+
* dialect stays browser-safe; the Meadow Oracle provider translates them to
|
|
78
|
+
* oracledb type descriptors at execution time (it is the only layer that may
|
|
79
|
+
* require the driver).
|
|
80
|
+
*
|
|
81
|
+
* @method: generateOracleParameterTypeEntry
|
|
82
|
+
* @param: {Object} pParameters SQL Query Parameters
|
|
83
|
+
* @param: {String} pColumnParameterName The bind parameter name
|
|
84
|
+
* @param: {Object|String} pColumn A schema column object, or a column name to look up
|
|
85
|
+
* @return: {Boolean} True if a known type was mapped
|
|
86
|
+
*/
|
|
87
|
+
var generateOracleParameterTypeEntry = function(pParameters, pColumnParameterName, pColumn)
|
|
88
|
+
{
|
|
89
|
+
if (!pParameters.query.hasOwnProperty('parameterTypes'))
|
|
90
|
+
{
|
|
91
|
+
pParameters.query.parameterTypes = {};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
let tmpColumnParameterTypeString = 'String';
|
|
95
|
+
if (typeof(pColumn) == 'object')
|
|
96
|
+
{
|
|
97
|
+
tmpColumnParameterTypeString = pColumn.Type;
|
|
98
|
+
}
|
|
99
|
+
else if (typeof(pColumn) == 'string')
|
|
100
|
+
{
|
|
101
|
+
var tmpSchema = Array.isArray(pParameters.query.schema) ? pParameters.query.schema : [];
|
|
102
|
+
for (let i = 0; i < tmpSchema.length; i++)
|
|
103
|
+
{
|
|
104
|
+
if (tmpSchema[i].Column == pColumn)
|
|
105
|
+
{
|
|
106
|
+
tmpColumnParameterTypeString = tmpSchema[i].Type;
|
|
107
|
+
break;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
else
|
|
112
|
+
{
|
|
113
|
+
_Fable.log.warn(`Meadow Oracle query attempted to add a parameter type but no valid column schema entry object or column name was passed; parameter name ${pColumnParameterName}.`);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if ((tmpColumnParameterTypeString == null) || (tmpColumnParameterTypeString == undefined))
|
|
117
|
+
{
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
switch (tmpColumnParameterTypeString)
|
|
122
|
+
{
|
|
123
|
+
case 'AutoIdentity':
|
|
124
|
+
case 'CreateIDUser':
|
|
125
|
+
case 'UpdateIDUser':
|
|
126
|
+
case 'DeleteIDUser':
|
|
127
|
+
case 'ForeignKey':
|
|
128
|
+
case 'Numeric':
|
|
129
|
+
case 'Integer':
|
|
130
|
+
case 'Deleted':
|
|
131
|
+
case 'Boolean':
|
|
132
|
+
case 'Decimal':
|
|
133
|
+
pParameters.query.parameterTypes[pColumnParameterName] = 'NUMBER';
|
|
134
|
+
break;
|
|
135
|
+
|
|
136
|
+
case 'String':
|
|
137
|
+
case 'AutoGUID':
|
|
138
|
+
pParameters.query.parameterTypes[pColumnParameterName] = 'STRING';
|
|
139
|
+
break;
|
|
140
|
+
|
|
141
|
+
case 'Text':
|
|
142
|
+
case 'JSON':
|
|
143
|
+
case 'JSONProxy':
|
|
144
|
+
pParameters.query.parameterTypes[pColumnParameterName] = 'CLOB';
|
|
145
|
+
break;
|
|
146
|
+
|
|
147
|
+
case 'CreateDate':
|
|
148
|
+
case 'UpdateDate':
|
|
149
|
+
case 'DeleteDate':
|
|
150
|
+
case 'DateTime':
|
|
151
|
+
pParameters.query.parameterTypes[pColumnParameterName] = 'DATE';
|
|
152
|
+
break;
|
|
153
|
+
|
|
154
|
+
default:
|
|
155
|
+
pParameters.query.parameterTypes[pColumnParameterName] = 'STRING';
|
|
156
|
+
return false;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return true;
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Generate a field list from the array of dataElements
|
|
164
|
+
*
|
|
165
|
+
* Each entry in the dataElements is a simple string
|
|
166
|
+
*
|
|
167
|
+
* @method: generateFieldList
|
|
168
|
+
* @param: {Object} pParameters SQL Query Parameters
|
|
169
|
+
* @param {Boolean} pIsForCountClause (optional) If true, generate fields for use within a count clause.
|
|
170
|
+
* @return: {String} Returns the field list clause, or empty string if explicit fields are requested but cannot be fulfilled
|
|
171
|
+
* due to missing schema.
|
|
172
|
+
*/
|
|
173
|
+
var generateFieldList = function(pParameters, pIsForCountClause)
|
|
174
|
+
{
|
|
175
|
+
var tmpDataElements = pParameters.dataElements;
|
|
176
|
+
if (!Array.isArray(tmpDataElements) || tmpDataElements.length < 1)
|
|
177
|
+
{
|
|
178
|
+
const tmpTableName = generateTableName(pParameters);
|
|
179
|
+
if (!pIsForCountClause)
|
|
180
|
+
{
|
|
181
|
+
return tmpTableName + '.*';
|
|
182
|
+
}
|
|
183
|
+
// we need to list all of the table fields explicitly; get them from the schema
|
|
184
|
+
const tmpSchema = Array.isArray(pParameters.query.schema) ? pParameters.query.schema : [];
|
|
185
|
+
if (tmpSchema.length < 1)
|
|
186
|
+
{
|
|
187
|
+
// this means we have no schema; returning an empty string here signals the calling code to handle this case
|
|
188
|
+
return '';
|
|
189
|
+
}
|
|
190
|
+
const idColumn = tmpSchema.find((entry) => entry.Type === 'AutoIdentity');
|
|
191
|
+
if (!idColumn)
|
|
192
|
+
{
|
|
193
|
+
// this means there is no autoincrementing unique ID column; treat as above
|
|
194
|
+
return '';
|
|
195
|
+
}
|
|
196
|
+
return ` ${generateSafeFieldName(idColumn.Column)}`;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
var tmpFieldList = ' ';
|
|
200
|
+
for (var i = 0; i < tmpDataElements.length; i++)
|
|
201
|
+
{
|
|
202
|
+
if (i > 0)
|
|
203
|
+
{
|
|
204
|
+
tmpFieldList += ', ';
|
|
205
|
+
}
|
|
206
|
+
if (Array.isArray(tmpDataElements[i]))
|
|
207
|
+
{
|
|
208
|
+
tmpFieldList += generateSafeFieldName(tmpDataElements[i][0]);
|
|
209
|
+
if (tmpDataElements[i].length > 1 && tmpDataElements[i][1])
|
|
210
|
+
{
|
|
211
|
+
tmpFieldList += " AS " + generateSafeFieldName(tmpDataElements[i][1]);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
else
|
|
215
|
+
{
|
|
216
|
+
tmpFieldList += generateSafeFieldName(tmpDataElements[i]);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
return tmpFieldList;
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Generate a field list for the outer SELECT of the legacy pagination
|
|
224
|
+
* wrapper. The outer FROM is a ROWNUM subquery, so the default
|
|
225
|
+
* "Table.*" qualifier can't resolve there — we need either an explicit
|
|
226
|
+
* column list from the schema or a bare "*".
|
|
227
|
+
*
|
|
228
|
+
* If the caller set explicit dataElements, reuse them (they reference bare
|
|
229
|
+
* column names, which work fine against the subquery). Otherwise emit an
|
|
230
|
+
* explicit list from the schema to keep "_RowNum" from leaking. As a last
|
|
231
|
+
* resort, fall back to "*".
|
|
232
|
+
*
|
|
233
|
+
* @param: {Object} pParameters SQL Query Parameters
|
|
234
|
+
* @return: {String} Field list (prefixed with a single leading space)
|
|
235
|
+
*/
|
|
236
|
+
var generateOuterFieldListForLegacyPagination = function(pParameters)
|
|
237
|
+
{
|
|
238
|
+
var tmpDataElements = pParameters.dataElements;
|
|
239
|
+
if (Array.isArray(tmpDataElements) && tmpDataElements.length > 0)
|
|
240
|
+
{
|
|
241
|
+
return generateFieldList(pParameters);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
var tmpSchema = Array.isArray(pParameters.query.schema) ? pParameters.query.schema : [];
|
|
245
|
+
if (tmpSchema.length > 0)
|
|
246
|
+
{
|
|
247
|
+
var tmpList = ' ';
|
|
248
|
+
for (var i = 0; i < tmpSchema.length; i++)
|
|
249
|
+
{
|
|
250
|
+
if (i > 0) tmpList += ', ';
|
|
251
|
+
tmpList += generateSafeFieldName(tmpSchema[i].Column);
|
|
252
|
+
}
|
|
253
|
+
return tmpList;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// No schema, no explicit dataElements — "*" is the best we can do.
|
|
257
|
+
// "_RowNum" will surface on marshalled records; downstream code can
|
|
258
|
+
// ignore it. Schemas are the norm via Meadow so this is rare.
|
|
259
|
+
return ' *';
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
const SURROUNDING_QUOTES_AND_WHITESPACE_REGEX = /^[" ]+|[" ]+$/g;
|
|
263
|
+
|
|
264
|
+
const cleanseQuoting = (str) =>
|
|
265
|
+
{
|
|
266
|
+
return str.replace(SURROUNDING_QUOTES_AND_WHITESPACE_REGEX, '');
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Ensure a field name is emitted correctly for the current quoting mode.
|
|
271
|
+
*
|
|
272
|
+
* Unquoted (default): bare identifiers (Oracle folds to UPPERCASE).
|
|
273
|
+
* Quoted: wrapped in double quotes, preserving case.
|
|
274
|
+
*/
|
|
275
|
+
var generateSafeFieldName = function(pFieldName)
|
|
276
|
+
{
|
|
277
|
+
let pFieldNames = pFieldName.split('.');
|
|
278
|
+
if (pFieldNames.length > 1)
|
|
279
|
+
{
|
|
280
|
+
const cleansedTable = cleanseQuoting(pFieldNames[0]);
|
|
281
|
+
const cleansedFieldName = cleanseQuoting(pFieldNames[1]);
|
|
282
|
+
if (cleansedFieldName === '*')
|
|
283
|
+
{
|
|
284
|
+
return _QuoteIdentifiers ? '"'+cleansedTable+'".*' : cleansedTable+'.*';
|
|
285
|
+
}
|
|
286
|
+
return _QuoteIdentifiers
|
|
287
|
+
? '"'+cleansedTable+'"."'+cleansedFieldName+'"'
|
|
288
|
+
: cleansedTable+'.'+cleansedFieldName;
|
|
289
|
+
}
|
|
290
|
+
const cleansedFieldName = cleanseQuoting(pFieldNames[0]);
|
|
291
|
+
if (cleansedFieldName === '*')
|
|
292
|
+
{
|
|
293
|
+
return '*';
|
|
294
|
+
}
|
|
295
|
+
return _QuoteIdentifiers ? '"'+cleansedFieldName+'"' : cleansedFieldName;
|
|
296
|
+
};
|
|
297
|
+
|
|
298
|
+
var resolveJsonColumnPath = function(pColumnName, pSchema)
|
|
299
|
+
{
|
|
300
|
+
if (!Array.isArray(pSchema) || pSchema.length < 1) return null;
|
|
301
|
+
var tmpParts = pColumnName.replace(/`/g, '').replace(/"/g, '').split('.');
|
|
302
|
+
for (var tmpStartIdx = 0; tmpStartIdx < Math.min(tmpParts.length - 1, 2); tmpStartIdx++)
|
|
303
|
+
{
|
|
304
|
+
var tmpBaseColumn = tmpParts[tmpStartIdx];
|
|
305
|
+
for (var s = 0; s < pSchema.length; s++)
|
|
306
|
+
{
|
|
307
|
+
if (pSchema[s].Column === tmpBaseColumn &&
|
|
308
|
+
(pSchema[s].Type === 'JSON' || pSchema[s].Type === 'JSONProxy'))
|
|
309
|
+
{
|
|
310
|
+
var tmpActualColumn = (pSchema[s].Type === 'JSONProxy') ? pSchema[s].StorageColumn : tmpBaseColumn;
|
|
311
|
+
var tmpJsonPath = '$.' + tmpParts.slice(tmpStartIdx + 1).join('.');
|
|
312
|
+
return { column: tmpActualColumn, path: tmpJsonPath };
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
return null;
|
|
317
|
+
};
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Generate a query from the array of where clauses
|
|
321
|
+
*
|
|
322
|
+
* Each clause is an object like:
|
|
323
|
+
{
|
|
324
|
+
Column:'Name',
|
|
325
|
+
Operator:'EQ',
|
|
326
|
+
Value:'John',
|
|
327
|
+
Connector:'And',
|
|
328
|
+
Parameter:'Name'
|
|
329
|
+
}
|
|
330
|
+
*
|
|
331
|
+
* @method: generateWhere
|
|
332
|
+
* @param: {Object} pParameters SQL Query Parameters
|
|
333
|
+
* @return: {String} Returns the WHERE clause prefixed with WHERE, or an empty string if unnecessary
|
|
334
|
+
*/
|
|
335
|
+
var generateWhere = function(pParameters)
|
|
336
|
+
{
|
|
337
|
+
var tmpFilter = Array.isArray(pParameters.filter) ? pParameters.filter : [];
|
|
338
|
+
|
|
339
|
+
if (!pParameters.query.disableDeleteTracking)
|
|
340
|
+
{
|
|
341
|
+
// Check if there is a Deleted column on the Schema. If so, we add this to the filters automatically (if not already present)
|
|
342
|
+
var tmpSchema = Array.isArray(pParameters.query.schema) ? pParameters.query.schema : [];
|
|
343
|
+
for (var i = 0; i < tmpSchema.length; i++)
|
|
344
|
+
{
|
|
345
|
+
var tmpSchemaEntry = tmpSchema[i];
|
|
346
|
+
|
|
347
|
+
if (tmpSchemaEntry.Type === 'Deleted')
|
|
348
|
+
{
|
|
349
|
+
var tmpHasDeletedParameter = false;
|
|
350
|
+
|
|
351
|
+
if (tmpFilter.length > 0)
|
|
352
|
+
{
|
|
353
|
+
for (var x = 0; x < tmpFilter.length; x++)
|
|
354
|
+
{
|
|
355
|
+
if (tmpFilter[x].Column === tmpSchemaEntry.Column)
|
|
356
|
+
{
|
|
357
|
+
tmpHasDeletedParameter = true;
|
|
358
|
+
break;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
if (!tmpHasDeletedParameter)
|
|
363
|
+
{
|
|
364
|
+
tmpFilter.push(
|
|
365
|
+
{
|
|
366
|
+
Column: tmpSchemaEntry.Column,
|
|
367
|
+
Operator: '=',
|
|
368
|
+
Value: 0,
|
|
369
|
+
Connector: 'AND',
|
|
370
|
+
Parameter: 'Deleted'
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
break;
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
if (tmpFilter.length < 1)
|
|
379
|
+
{
|
|
380
|
+
return '';
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
var tmpWhere = ' WHERE';
|
|
384
|
+
|
|
385
|
+
// This is used to disable the connectors for subsequent queries.
|
|
386
|
+
// Only the open parenthesis operator uses this, currently.
|
|
387
|
+
var tmpLastOperatorNoConnector = false;
|
|
388
|
+
|
|
389
|
+
for (var i = 0; i < tmpFilter.length; i++)
|
|
390
|
+
{
|
|
391
|
+
if ((tmpFilter[i].Connector != 'NONE') && (tmpFilter[i].Operator != ')') && (tmpWhere != ' WHERE') && (tmpLastOperatorNoConnector == false))
|
|
392
|
+
{
|
|
393
|
+
tmpWhere += ' '+tmpFilter[i].Connector;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
tmpLastOperatorNoConnector = false;
|
|
397
|
+
|
|
398
|
+
var tmpColumnParameter;
|
|
399
|
+
|
|
400
|
+
if (tmpFilter[i].Operator === '(')
|
|
401
|
+
{
|
|
402
|
+
tmpWhere += ' (';
|
|
403
|
+
tmpLastOperatorNoConnector = true;
|
|
404
|
+
}
|
|
405
|
+
else if (tmpFilter[i].Operator === ')')
|
|
406
|
+
{
|
|
407
|
+
tmpWhere += ' )';
|
|
408
|
+
}
|
|
409
|
+
else if (tmpFilter[i].Operator === 'IN' || tmpFilter[i].Operator === "NOT IN")
|
|
410
|
+
{
|
|
411
|
+
// oracledb will not expand a single bound array into an IN list,
|
|
412
|
+
// so expand the value list into discrete :name binds here.
|
|
413
|
+
var tmpInValues = Array.isArray(tmpFilter[i].Value)
|
|
414
|
+
? tmpFilter[i].Value
|
|
415
|
+
: String(tmpFilter[i].Value).split(',');
|
|
416
|
+
var tmpInPlaceholders = [];
|
|
417
|
+
for (var v = 0; v < tmpInValues.length; v++)
|
|
418
|
+
{
|
|
419
|
+
var tmpInParameter = tmpFilter[i].Parameter+'_w'+i+'_'+v;
|
|
420
|
+
tmpInPlaceholders.push(':'+tmpInParameter);
|
|
421
|
+
pParameters.query.parameters[tmpInParameter] = tmpInValues[v];
|
|
422
|
+
generateOracleParameterTypeEntry(pParameters, tmpInParameter, tmpFilter[i].Parameter);
|
|
423
|
+
}
|
|
424
|
+
tmpWhere += ' '+generateSafeFieldName(tmpFilter[i].Column)+' '+tmpFilter[i].Operator+' ('+tmpInPlaceholders.join(', ')+')';
|
|
425
|
+
}
|
|
426
|
+
else if (tmpFilter[i].Operator === 'IS NULL')
|
|
427
|
+
{
|
|
428
|
+
tmpWhere += ' '+generateSafeFieldName(tmpFilter[i].Column)+' '+tmpFilter[i].Operator;
|
|
429
|
+
}
|
|
430
|
+
else if (tmpFilter[i].Operator === 'IS NOT NULL')
|
|
431
|
+
{
|
|
432
|
+
tmpWhere += ' '+generateSafeFieldName(tmpFilter[i].Column)+' '+tmpFilter[i].Operator;
|
|
433
|
+
}
|
|
434
|
+
else
|
|
435
|
+
{
|
|
436
|
+
tmpColumnParameter = tmpFilter[i].Parameter+'_w'+i;
|
|
437
|
+
var tmpSchemaForJson = Array.isArray(pParameters.query.schema) ? pParameters.query.schema : [];
|
|
438
|
+
var tmpJsonRef = resolveJsonColumnPath(tmpFilter[i].Column, tmpSchemaForJson);
|
|
439
|
+
if (tmpJsonRef)
|
|
440
|
+
{
|
|
441
|
+
tmpWhere += ' JSON_VALUE('+generateSafeFieldName(tmpJsonRef.column)+", '"+tmpJsonRef.path+"') "+tmpFilter[i].Operator+' :'+tmpColumnParameter;
|
|
442
|
+
}
|
|
443
|
+
else
|
|
444
|
+
{
|
|
445
|
+
tmpWhere += ' '+generateSafeFieldName(tmpFilter[i].Column)+' '+tmpFilter[i].Operator+' :'+tmpColumnParameter;
|
|
446
|
+
}
|
|
447
|
+
pParameters.query.parameters[tmpColumnParameter] = tmpFilter[i].Value;
|
|
448
|
+
generateOracleParameterTypeEntry(pParameters, tmpColumnParameter, tmpFilter[i].Parameter);
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
return tmpWhere;
|
|
453
|
+
};
|
|
454
|
+
|
|
455
|
+
/**
|
|
456
|
+
* Find the table's AutoIdentity primary-key column from the schema, if any.
|
|
457
|
+
* Used as a deterministic default ORDER BY when the caller didn't set a
|
|
458
|
+
* sort, and as the RETURNING target for INSERT.
|
|
459
|
+
*
|
|
460
|
+
* @param: {Object} pParameters SQL Query Parameters
|
|
461
|
+
* @return: {String|null} The column name, or null if none found
|
|
462
|
+
*/
|
|
463
|
+
var findPrimaryKeyColumn = function(pParameters)
|
|
464
|
+
{
|
|
465
|
+
var tmpSchema = Array.isArray(pParameters.query.schema) ? pParameters.query.schema : [];
|
|
466
|
+
for (var i = 0; i < tmpSchema.length; i++)
|
|
467
|
+
{
|
|
468
|
+
if (tmpSchema[i].Type === 'AutoIdentity')
|
|
469
|
+
{
|
|
470
|
+
return tmpSchema[i].Column;
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
return null;
|
|
474
|
+
};
|
|
475
|
+
|
|
476
|
+
/**
|
|
477
|
+
* Generate an ORDER BY clause from the sort array
|
|
478
|
+
*
|
|
479
|
+
* When no sort is specified but the query has a cap (pagination is active),
|
|
480
|
+
* inject a default ORDER BY on the primary key so paging is deterministic.
|
|
481
|
+
* Unlike MSSQL, Oracle permits OFFSET/FETCH and ROWNUM paging without an
|
|
482
|
+
* ORDER BY, so when no PK can be inferred we simply omit the clause rather
|
|
483
|
+
* than emitting an invalid "ORDER BY (SELECT 1)".
|
|
484
|
+
*
|
|
485
|
+
* @method: generateOrderBy
|
|
486
|
+
* @param: {Object} pParameters SQL Query Parameters
|
|
487
|
+
* @return: {String} Returns the order by clause
|
|
488
|
+
*/
|
|
489
|
+
var generateOrderBy = function(pParameters)
|
|
490
|
+
{
|
|
491
|
+
var tmpOrderBy = pParameters.sort;
|
|
492
|
+
if (!Array.isArray(tmpOrderBy) || tmpOrderBy.length < 1)
|
|
493
|
+
{
|
|
494
|
+
if (pParameters.cap)
|
|
495
|
+
{
|
|
496
|
+
var tmpPK = findPrimaryKeyColumn(pParameters);
|
|
497
|
+
if (tmpPK)
|
|
498
|
+
{
|
|
499
|
+
return ' ORDER BY '+generateSafeFieldName(tmpPK);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
return '';
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
var tmpOrderClause = ' ORDER BY';
|
|
506
|
+
for (var i = 0; i < tmpOrderBy.length; i++)
|
|
507
|
+
{
|
|
508
|
+
if (i > 0)
|
|
509
|
+
{
|
|
510
|
+
tmpOrderClause += ',';
|
|
511
|
+
}
|
|
512
|
+
tmpOrderClause += ' '+generateSafeFieldName(tmpOrderBy[i].Column);
|
|
513
|
+
|
|
514
|
+
if (tmpOrderBy[i].Direction == 'Descending')
|
|
515
|
+
{
|
|
516
|
+
tmpOrderClause += ' DESC';
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
return tmpOrderClause;
|
|
520
|
+
};
|
|
521
|
+
|
|
522
|
+
/**
|
|
523
|
+
* Generate the limit clause using the 12c+ OFFSET/FETCH syntax.
|
|
524
|
+
*
|
|
525
|
+
* When pParameters.legacyPagination is set the Read function wraps the
|
|
526
|
+
* query in a ROWNUM subquery instead (11g and earlier have no OFFSET/FETCH),
|
|
527
|
+
* so this returns an empty string in that case.
|
|
528
|
+
*
|
|
529
|
+
* @method: generateLimit
|
|
530
|
+
* @param: {Object} pParameters SQL Query Parameters
|
|
531
|
+
* @return: {String} Returns the table limit clause
|
|
532
|
+
*/
|
|
533
|
+
var generateLimit = function(pParameters)
|
|
534
|
+
{
|
|
535
|
+
if (!pParameters.cap)
|
|
536
|
+
{
|
|
537
|
+
return '';
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
if (pParameters.legacyPagination)
|
|
541
|
+
{
|
|
542
|
+
return '';
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
var tmpLimit = ' OFFSET ';
|
|
546
|
+
if (pParameters.begin !== false)
|
|
547
|
+
{
|
|
548
|
+
tmpLimit += pParameters.begin;
|
|
549
|
+
}
|
|
550
|
+
else
|
|
551
|
+
{
|
|
552
|
+
tmpLimit += '0';
|
|
553
|
+
}
|
|
554
|
+
tmpLimit += ` ROWS FETCH NEXT ${pParameters.cap} ROWS ONLY`;
|
|
555
|
+
|
|
556
|
+
return tmpLimit;
|
|
557
|
+
};
|
|
558
|
+
|
|
559
|
+
/**
|
|
560
|
+
* Generate the join clause
|
|
561
|
+
*
|
|
562
|
+
* @method: generateJoins
|
|
563
|
+
* @param: {Object} pParameters SQL Query Parameters
|
|
564
|
+
* @return: {String} Returns the join clause
|
|
565
|
+
*/
|
|
566
|
+
var generateJoins = function(pParameters)
|
|
567
|
+
{
|
|
568
|
+
var tmpJoins = pParameters.join;
|
|
569
|
+
if (!Array.isArray(tmpJoins) || tmpJoins.length < 1)
|
|
570
|
+
{
|
|
571
|
+
return '';
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
var tmpJoinClause = '';
|
|
575
|
+
for (var i = 0; i < tmpJoins.length; i++)
|
|
576
|
+
{
|
|
577
|
+
var join = tmpJoins[i];
|
|
578
|
+
if (join.Type && join.Table && join.From && join.To)
|
|
579
|
+
{
|
|
580
|
+
var tmpJoinTable = _QuoteIdentifiers ? '"'+join.Table+'"' : join.Table;
|
|
581
|
+
tmpJoinClause += ` ${join.Type} ${tmpJoinTable} ON ${join.From} = ${join.To}`;
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
return tmpJoinClause;
|
|
586
|
+
};
|
|
587
|
+
|
|
588
|
+
/**
|
|
589
|
+
* Generate the update SET clause
|
|
590
|
+
*
|
|
591
|
+
* @method: generateUpdateSetters
|
|
592
|
+
* @param: {Object} pParameters SQL Query Parameters
|
|
593
|
+
* @return: {String} Returns the update set clause
|
|
594
|
+
*/
|
|
595
|
+
var generateUpdateSetters = function(pParameters)
|
|
596
|
+
{
|
|
597
|
+
var tmpRecords = pParameters.query.records;
|
|
598
|
+
if (!Array.isArray(tmpRecords) || tmpRecords.length < 1)
|
|
599
|
+
{
|
|
600
|
+
return false;
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
var tmpSchema = Array.isArray(pParameters.query.schema) ? pParameters.query.schema : [];
|
|
604
|
+
|
|
605
|
+
var tmpUpdate = '';
|
|
606
|
+
var tmpCurrentColumn = 0;
|
|
607
|
+
for(var tmpColumn in tmpRecords[0])
|
|
608
|
+
{
|
|
609
|
+
var tmpSchemaEntry = {Column:tmpColumn, Type:'Default'};
|
|
610
|
+
for (var i = 0; i < tmpSchema.length; i++)
|
|
611
|
+
{
|
|
612
|
+
if (tmpColumn == tmpSchema[i].Column)
|
|
613
|
+
{
|
|
614
|
+
tmpSchemaEntry = tmpSchema[i];
|
|
615
|
+
break;
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
if (pParameters.query.disableAutoUserStamp &&
|
|
620
|
+
tmpSchemaEntry.Type === 'UpdateIDUser')
|
|
621
|
+
{
|
|
622
|
+
continue;
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
switch (tmpSchemaEntry.Type)
|
|
626
|
+
{
|
|
627
|
+
case 'AutoIdentity':
|
|
628
|
+
case 'CreateDate':
|
|
629
|
+
case 'CreateIDUser':
|
|
630
|
+
case 'DeleteDate':
|
|
631
|
+
case 'DeleteIDUser':
|
|
632
|
+
continue;
|
|
633
|
+
}
|
|
634
|
+
if (tmpCurrentColumn > 0)
|
|
635
|
+
{
|
|
636
|
+
tmpUpdate += ',';
|
|
637
|
+
}
|
|
638
|
+
switch (tmpSchemaEntry.Type)
|
|
639
|
+
{
|
|
640
|
+
case 'UpdateDate':
|
|
641
|
+
if (pParameters.query.disableAutoDateStamp)
|
|
642
|
+
{
|
|
643
|
+
var tmpColumnParameter = 'MANUAL_UpdateDate';
|
|
644
|
+
tmpUpdate += ' '+generateSafeFieldName(tmpColumn)+' = :MANUAL_UpdateDate';
|
|
645
|
+
pParameters.query.parameters[tmpColumnParameter] = tmpRecords[0][tmpColumn];
|
|
646
|
+
generateOracleParameterTypeEntry(pParameters, tmpColumnParameter, tmpSchemaEntry);
|
|
647
|
+
}
|
|
648
|
+
else
|
|
649
|
+
{
|
|
650
|
+
tmpUpdate += ' '+generateSafeFieldName(tmpColumn)+' = ' + SQL_NOW;
|
|
651
|
+
}
|
|
652
|
+
break;
|
|
653
|
+
case 'UpdateIDUser':
|
|
654
|
+
var tmpColumnParameter = tmpColumn+'_'+tmpCurrentColumn;
|
|
655
|
+
tmpUpdate += ' '+generateSafeFieldName(tmpColumn)+' = :'+tmpColumnParameter;
|
|
656
|
+
pParameters.query.parameters[tmpColumnParameter] = pParameters.query.IDUser;
|
|
657
|
+
generateOracleParameterTypeEntry(pParameters, tmpColumnParameter, tmpColumn);
|
|
658
|
+
break;
|
|
659
|
+
case 'JSON':
|
|
660
|
+
var tmpJSONUpdateParam = tmpColumn+'_'+tmpCurrentColumn;
|
|
661
|
+
tmpUpdate += ' '+generateSafeFieldName(tmpColumn)+' = :'+tmpJSONUpdateParam;
|
|
662
|
+
pParameters.query.parameters[tmpJSONUpdateParam] = (typeof tmpRecords[0][tmpColumn] === 'string')
|
|
663
|
+
? tmpRecords[0][tmpColumn]
|
|
664
|
+
: JSON.stringify(tmpRecords[0][tmpColumn] || {});
|
|
665
|
+
generateOracleParameterTypeEntry(pParameters, tmpJSONUpdateParam, {Type:'Text'});
|
|
666
|
+
break;
|
|
667
|
+
case 'JSONProxy':
|
|
668
|
+
var tmpProxyUpdateParam = tmpSchemaEntry.StorageColumn+'_'+tmpCurrentColumn;
|
|
669
|
+
tmpUpdate += ' '+generateSafeFieldName(tmpSchemaEntry.StorageColumn)+' = :'+tmpProxyUpdateParam;
|
|
670
|
+
pParameters.query.parameters[tmpProxyUpdateParam] = (typeof tmpRecords[0][tmpColumn] === 'string')
|
|
671
|
+
? tmpRecords[0][tmpColumn]
|
|
672
|
+
: JSON.stringify(tmpRecords[0][tmpColumn] || {});
|
|
673
|
+
generateOracleParameterTypeEntry(pParameters, tmpProxyUpdateParam, {Type:'Text'});
|
|
674
|
+
break;
|
|
675
|
+
default:
|
|
676
|
+
var tmpColumnDefaultParameter = tmpColumn+'_'+tmpCurrentColumn;
|
|
677
|
+
tmpUpdate += ' '+generateSafeFieldName(tmpColumn)+' = :'+tmpColumnDefaultParameter;
|
|
678
|
+
pParameters.query.parameters[tmpColumnDefaultParameter] = tmpRecords[0][tmpColumn];
|
|
679
|
+
generateOracleParameterTypeEntry(pParameters, tmpColumnDefaultParameter, tmpSchemaEntry);
|
|
680
|
+
break;
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
tmpCurrentColumn++;
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
if (tmpUpdate === '')
|
|
687
|
+
{
|
|
688
|
+
return false;
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
return tmpUpdate;
|
|
692
|
+
};
|
|
693
|
+
|
|
694
|
+
/**
|
|
695
|
+
* Generate the update-delete SET clause
|
|
696
|
+
*
|
|
697
|
+
* @method: generateUpdateDeleteSetters
|
|
698
|
+
* @param: {Object} pParameters SQL Query Parameters
|
|
699
|
+
* @return: {String} Returns the soft-delete set clause
|
|
700
|
+
*/
|
|
701
|
+
var generateUpdateDeleteSetters = function(pParameters)
|
|
702
|
+
{
|
|
703
|
+
if (pParameters.query.disableDeleteTracking)
|
|
704
|
+
{
|
|
705
|
+
return false;
|
|
706
|
+
}
|
|
707
|
+
var tmpSchema = Array.isArray(pParameters.query.schema) ? pParameters.query.schema : [];
|
|
708
|
+
|
|
709
|
+
var tmpCurrentColumn = 0;
|
|
710
|
+
var tmpHasDeletedField = false;
|
|
711
|
+
var tmpUpdate = '';
|
|
712
|
+
var tmpSchemaEntry = {Type:'Default'};
|
|
713
|
+
for (var i = 0; i < tmpSchema.length; i++)
|
|
714
|
+
{
|
|
715
|
+
tmpSchemaEntry = tmpSchema[i];
|
|
716
|
+
|
|
717
|
+
var tmpUpdateSql = null;
|
|
718
|
+
|
|
719
|
+
switch (tmpSchemaEntry.Type)
|
|
720
|
+
{
|
|
721
|
+
case 'Deleted':
|
|
722
|
+
tmpUpdateSql = ' '+generateSafeFieldName(tmpSchemaEntry.Column)+' = 1';
|
|
723
|
+
tmpHasDeletedField = true;
|
|
724
|
+
break;
|
|
725
|
+
case 'DeleteDate':
|
|
726
|
+
tmpUpdateSql = ' '+generateSafeFieldName(tmpSchemaEntry.Column)+' = ' + SQL_NOW;
|
|
727
|
+
break;
|
|
728
|
+
case 'UpdateDate':
|
|
729
|
+
tmpUpdateSql = ' '+generateSafeFieldName(tmpSchemaEntry.Column)+' = ' + SQL_NOW;
|
|
730
|
+
break;
|
|
731
|
+
case 'DeleteIDUser':
|
|
732
|
+
var tmpColumnParameter = tmpSchemaEntry.Column+'_'+tmpCurrentColumn;
|
|
733
|
+
tmpUpdateSql = ' '+generateSafeFieldName(tmpSchemaEntry.Column)+' = :'+tmpColumnParameter;
|
|
734
|
+
pParameters.query.parameters[tmpColumnParameter] = pParameters.query.IDUser;
|
|
735
|
+
generateOracleParameterTypeEntry(pParameters, tmpColumnParameter, tmpSchemaEntry);
|
|
736
|
+
break;
|
|
737
|
+
default:
|
|
738
|
+
continue;
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
if (tmpCurrentColumn > 0)
|
|
742
|
+
{
|
|
743
|
+
tmpUpdate += ',';
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
tmpUpdate += tmpUpdateSql;
|
|
747
|
+
|
|
748
|
+
tmpCurrentColumn++;
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
if (!tmpHasDeletedField ||
|
|
752
|
+
tmpUpdate === '')
|
|
753
|
+
{
|
|
754
|
+
return false;
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
return tmpUpdate;
|
|
758
|
+
};
|
|
759
|
+
|
|
760
|
+
/**
|
|
761
|
+
* Generate the update-undelete SET clause
|
|
762
|
+
*
|
|
763
|
+
* @method: generateUpdateUndeleteSetters
|
|
764
|
+
* @param: {Object} pParameters SQL Query Parameters
|
|
765
|
+
* @return: {String} Returns the soft-undelete set clause
|
|
766
|
+
*/
|
|
767
|
+
var generateUpdateUndeleteSetters = function(pParameters)
|
|
768
|
+
{
|
|
769
|
+
var tmpSchema = Array.isArray(pParameters.query.schema) ? pParameters.query.schema : [];
|
|
770
|
+
|
|
771
|
+
var tmpCurrentColumn = 0;
|
|
772
|
+
var tmpHasDeletedField = false;
|
|
773
|
+
var tmpUpdate = '';
|
|
774
|
+
var tmpSchemaEntry = {Type:'Default'};
|
|
775
|
+
for (var i = 0; i < tmpSchema.length; i++)
|
|
776
|
+
{
|
|
777
|
+
tmpSchemaEntry = tmpSchema[i];
|
|
778
|
+
|
|
779
|
+
var tmpUpdateSql = null;
|
|
780
|
+
|
|
781
|
+
switch (tmpSchemaEntry.Type)
|
|
782
|
+
{
|
|
783
|
+
case 'Deleted':
|
|
784
|
+
tmpUpdateSql = ' '+generateSafeFieldName(tmpSchemaEntry.Column)+' = 0';
|
|
785
|
+
tmpHasDeletedField = true;
|
|
786
|
+
break;
|
|
787
|
+
case 'UpdateDate':
|
|
788
|
+
tmpUpdateSql = ' '+generateSafeFieldName(tmpSchemaEntry.Column)+' = ' + SQL_NOW;
|
|
789
|
+
break;
|
|
790
|
+
case 'UpdateIDUser':
|
|
791
|
+
var tmpColumnParameter = tmpSchemaEntry.Column+'_'+tmpCurrentColumn;
|
|
792
|
+
tmpUpdateSql = ' '+generateSafeFieldName(tmpSchemaEntry.Column)+' = :'+tmpColumnParameter;
|
|
793
|
+
pParameters.query.parameters[tmpColumnParameter] = pParameters.query.IDUser;
|
|
794
|
+
generateOracleParameterTypeEntry(pParameters, tmpColumnParameter, tmpSchemaEntry);
|
|
795
|
+
break;
|
|
796
|
+
default:
|
|
797
|
+
continue;
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
if (tmpCurrentColumn > 0)
|
|
801
|
+
{
|
|
802
|
+
tmpUpdate += ',';
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
tmpUpdate += tmpUpdateSql;
|
|
806
|
+
|
|
807
|
+
tmpCurrentColumn++;
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
if (!tmpHasDeletedField ||
|
|
811
|
+
tmpUpdate === '')
|
|
812
|
+
{
|
|
813
|
+
return false;
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
return tmpUpdate;
|
|
817
|
+
};
|
|
818
|
+
|
|
819
|
+
/**
|
|
820
|
+
* Generate the create SET clause values
|
|
821
|
+
*
|
|
822
|
+
* @method: generateCreateSetValues
|
|
823
|
+
* @param: {Object} pParameters SQL Query Parameters
|
|
824
|
+
* @return: {String} Returns the insert values clause
|
|
825
|
+
*/
|
|
826
|
+
var generateCreateSetValues = function(pParameters)
|
|
827
|
+
{
|
|
828
|
+
var tmpRecords = pParameters.query.records;
|
|
829
|
+
if (!Array.isArray(tmpRecords) || tmpRecords.length < 1)
|
|
830
|
+
{
|
|
831
|
+
return false;
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
var tmpSchema = Array.isArray(pParameters.query.schema) ? pParameters.query.schema : [];
|
|
835
|
+
|
|
836
|
+
var tmpCreateSet = '';
|
|
837
|
+
var tmpCurrentColumn = 0;
|
|
838
|
+
for(var tmpColumn in tmpRecords[0])
|
|
839
|
+
{
|
|
840
|
+
var tmpSchemaEntry = {Column:tmpColumn, Type:'Default'};
|
|
841
|
+
for (var i = 0; i < tmpSchema.length; i++)
|
|
842
|
+
{
|
|
843
|
+
if (tmpColumn == tmpSchema[i].Column)
|
|
844
|
+
{
|
|
845
|
+
tmpSchemaEntry = tmpSchema[i];
|
|
846
|
+
break;
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
if (!pParameters.query.disableDeleteTracking)
|
|
851
|
+
{
|
|
852
|
+
if (tmpSchemaEntry.Type === 'DeleteDate' ||
|
|
853
|
+
tmpSchemaEntry.Type === 'DeleteIDUser')
|
|
854
|
+
{
|
|
855
|
+
continue;
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
// AutoIdentity is omitted entirely from the INSERT (the IDENTITY
|
|
860
|
+
// column or the BEFORE-INSERT sequence trigger supplies it); skip
|
|
861
|
+
// adding a separator for it unless the consumer is overriding it.
|
|
862
|
+
if (tmpSchemaEntry.Type === 'AutoIdentity' && !pParameters.query.disableAutoIdentity)
|
|
863
|
+
{
|
|
864
|
+
continue;
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
if ((tmpCurrentColumn > 0) && (tmpCreateSet != ''))
|
|
868
|
+
{
|
|
869
|
+
tmpCreateSet += ',';
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
var buildDefaultDefinition = function()
|
|
873
|
+
{
|
|
874
|
+
var tmpColumnParameter = tmpColumn+'_'+tmpCurrentColumn;
|
|
875
|
+
tmpCreateSet += ' :'+tmpColumnParameter;
|
|
876
|
+
pParameters.query.parameters[tmpColumnParameter] = tmpRecords[0][tmpColumn];
|
|
877
|
+
generateOracleParameterTypeEntry(pParameters, tmpColumnParameter, tmpSchemaEntry);
|
|
878
|
+
};
|
|
879
|
+
|
|
880
|
+
var tmpColumnParameter;
|
|
881
|
+
switch (tmpSchemaEntry.Type)
|
|
882
|
+
{
|
|
883
|
+
case 'AutoIdentity':
|
|
884
|
+
// Only reached when disableAutoIdentity is set (overriding
|
|
885
|
+
// the generated key with an explicit value).
|
|
886
|
+
buildDefaultDefinition();
|
|
887
|
+
break;
|
|
888
|
+
case 'AutoGUID':
|
|
889
|
+
if (pParameters.query.disableAutoIdentity)
|
|
890
|
+
{
|
|
891
|
+
buildDefaultDefinition();
|
|
892
|
+
}
|
|
893
|
+
else if (tmpRecords[0][tmpColumn] &&
|
|
894
|
+
tmpRecords[0][tmpColumn].length >= 5 &&
|
|
895
|
+
tmpRecords[0][tmpColumn] !== '0x0000000000000000') //stricture default
|
|
896
|
+
{
|
|
897
|
+
buildDefaultDefinition();
|
|
898
|
+
}
|
|
899
|
+
else
|
|
900
|
+
{
|
|
901
|
+
tmpColumnParameter = tmpColumn+'_'+tmpCurrentColumn;
|
|
902
|
+
tmpCreateSet += ' :'+tmpColumnParameter;
|
|
903
|
+
pParameters.query.parameters[tmpColumnParameter] = pParameters.query.UUID;
|
|
904
|
+
generateOracleParameterTypeEntry(pParameters, tmpColumnParameter, tmpSchemaEntry);
|
|
905
|
+
}
|
|
906
|
+
break;
|
|
907
|
+
case 'UpdateDate':
|
|
908
|
+
case 'CreateDate':
|
|
909
|
+
case 'DeleteDate':
|
|
910
|
+
if (pParameters.query.disableAutoDateStamp)
|
|
911
|
+
{
|
|
912
|
+
buildDefaultDefinition();
|
|
913
|
+
}
|
|
914
|
+
else
|
|
915
|
+
{
|
|
916
|
+
tmpCreateSet += ' ' + SQL_NOW;
|
|
917
|
+
}
|
|
918
|
+
break;
|
|
919
|
+
case 'DeleteIDUser':
|
|
920
|
+
case 'UpdateIDUser':
|
|
921
|
+
case 'CreateIDUser':
|
|
922
|
+
if (pParameters.query.disableAutoUserStamp)
|
|
923
|
+
{
|
|
924
|
+
buildDefaultDefinition();
|
|
925
|
+
}
|
|
926
|
+
else
|
|
927
|
+
{
|
|
928
|
+
tmpColumnParameter = tmpColumn+'_'+tmpCurrentColumn;
|
|
929
|
+
tmpCreateSet += ' :'+tmpColumnParameter;
|
|
930
|
+
pParameters.query.parameters[tmpColumnParameter] = pParameters.query.IDUser;
|
|
931
|
+
generateOracleParameterTypeEntry(pParameters, tmpColumnParameter, tmpSchemaEntry);
|
|
932
|
+
}
|
|
933
|
+
break;
|
|
934
|
+
case 'JSON':
|
|
935
|
+
var tmpJSONCreateParam = tmpColumn+'_'+tmpCurrentColumn;
|
|
936
|
+
tmpCreateSet += ' :'+tmpJSONCreateParam;
|
|
937
|
+
pParameters.query.parameters[tmpJSONCreateParam] = (typeof tmpRecords[0][tmpColumn] === 'string')
|
|
938
|
+
? tmpRecords[0][tmpColumn]
|
|
939
|
+
: JSON.stringify(tmpRecords[0][tmpColumn] || {});
|
|
940
|
+
generateOracleParameterTypeEntry(pParameters, tmpJSONCreateParam, {Type:'Text'});
|
|
941
|
+
break;
|
|
942
|
+
case 'JSONProxy':
|
|
943
|
+
var tmpProxyCreateParam = tmpColumn+'_'+tmpCurrentColumn;
|
|
944
|
+
tmpCreateSet += ' :'+tmpProxyCreateParam;
|
|
945
|
+
pParameters.query.parameters[tmpProxyCreateParam] = (typeof tmpRecords[0][tmpColumn] === 'string')
|
|
946
|
+
? tmpRecords[0][tmpColumn]
|
|
947
|
+
: JSON.stringify(tmpRecords[0][tmpColumn] || {});
|
|
948
|
+
generateOracleParameterTypeEntry(pParameters, tmpProxyCreateParam, {Type:'Text'});
|
|
949
|
+
break;
|
|
950
|
+
default:
|
|
951
|
+
buildDefaultDefinition();
|
|
952
|
+
break;
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
tmpCurrentColumn++;
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
if (tmpCreateSet === '')
|
|
959
|
+
{
|
|
960
|
+
return false;
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
return tmpCreateSet;
|
|
964
|
+
};
|
|
965
|
+
|
|
966
|
+
/**
|
|
967
|
+
* Generate the create SET clause column list
|
|
968
|
+
*
|
|
969
|
+
* @method: generateCreateSetList
|
|
970
|
+
* @param: {Object} pParameters SQL Query Parameters
|
|
971
|
+
* @return: {String} Returns the insert column list
|
|
972
|
+
*/
|
|
973
|
+
var generateCreateSetList = function(pParameters)
|
|
974
|
+
{
|
|
975
|
+
var tmpRecords = pParameters.query.records;
|
|
976
|
+
|
|
977
|
+
var tmpSchema = Array.isArray(pParameters.query.schema) ? pParameters.query.schema : [];
|
|
978
|
+
|
|
979
|
+
var tmpCreateSet = '';
|
|
980
|
+
for(var tmpColumn in tmpRecords[0])
|
|
981
|
+
{
|
|
982
|
+
var tmpSchemaEntry = {Column:tmpColumn, Type:'Default'};
|
|
983
|
+
for (var i = 0; i < tmpSchema.length; i++)
|
|
984
|
+
{
|
|
985
|
+
if (tmpColumn == tmpSchema[i].Column)
|
|
986
|
+
{
|
|
987
|
+
tmpSchemaEntry = tmpSchema[i];
|
|
988
|
+
break;
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
if (!pParameters.query.disableDeleteTracking)
|
|
992
|
+
{
|
|
993
|
+
if (tmpSchemaEntry.Type === 'DeleteDate' ||
|
|
994
|
+
tmpSchemaEntry.Type === 'DeleteIDUser')
|
|
995
|
+
{
|
|
996
|
+
continue;
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
switch (tmpSchemaEntry.Type)
|
|
1000
|
+
{
|
|
1001
|
+
case 'AutoIdentity':
|
|
1002
|
+
// Skipped on INSERT (IDENTITY/sequence trigger supplies it)
|
|
1003
|
+
// unless the consumer is overriding the value.
|
|
1004
|
+
if (pParameters.query.disableAutoIdentity)
|
|
1005
|
+
{
|
|
1006
|
+
if (tmpCreateSet != '')
|
|
1007
|
+
{
|
|
1008
|
+
tmpCreateSet += ',';
|
|
1009
|
+
}
|
|
1010
|
+
tmpCreateSet += ' '+generateSafeFieldName(tmpColumn);
|
|
1011
|
+
}
|
|
1012
|
+
continue;
|
|
1013
|
+
case 'JSONProxy':
|
|
1014
|
+
if (tmpCreateSet != '')
|
|
1015
|
+
{
|
|
1016
|
+
tmpCreateSet += ',';
|
|
1017
|
+
}
|
|
1018
|
+
tmpCreateSet += ' '+generateSafeFieldName(tmpSchemaEntry.StorageColumn);
|
|
1019
|
+
break;
|
|
1020
|
+
default:
|
|
1021
|
+
if (tmpCreateSet != '')
|
|
1022
|
+
{
|
|
1023
|
+
tmpCreateSet += ',';
|
|
1024
|
+
}
|
|
1025
|
+
tmpCreateSet += ' '+generateSafeFieldName(tmpColumn);
|
|
1026
|
+
break;
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
return tmpCreateSet;
|
|
1031
|
+
};
|
|
1032
|
+
|
|
1033
|
+
|
|
1034
|
+
var Create = function(pParameters)
|
|
1035
|
+
{
|
|
1036
|
+
_QuoteIdentifiers = !!pParameters.quoteIdentifiers;
|
|
1037
|
+
var tmpTableName = generateTableName(pParameters);
|
|
1038
|
+
var tmpCreateSetList = generateCreateSetList(pParameters);
|
|
1039
|
+
var tmpCreateSetValues = generateCreateSetValues(pParameters);
|
|
1040
|
+
|
|
1041
|
+
if (!tmpCreateSetValues)
|
|
1042
|
+
{
|
|
1043
|
+
return false;
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
// Oracle has no SCOPE_IDENTITY(); return the generated key via a
|
|
1047
|
+
// RETURNING ... INTO out-bind that the Meadow Oracle provider reads.
|
|
1048
|
+
var tmpReturning = '';
|
|
1049
|
+
if (!pParameters.query.disableAutoIdentity)
|
|
1050
|
+
{
|
|
1051
|
+
var tmpIDColumn = findPrimaryKeyColumn(pParameters);
|
|
1052
|
+
if (tmpIDColumn)
|
|
1053
|
+
{
|
|
1054
|
+
tmpReturning = ' RETURNING '+generateSafeFieldName(tmpIDColumn)+' INTO :RETURNING_ID';
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
return 'INSERT INTO'+tmpTableName+' ('+tmpCreateSetList+') VALUES ('+tmpCreateSetValues+')'+tmpReturning;
|
|
1059
|
+
};
|
|
1060
|
+
|
|
1061
|
+
|
|
1062
|
+
/**
|
|
1063
|
+
* Read one or many records
|
|
1064
|
+
*
|
|
1065
|
+
* @method Read
|
|
1066
|
+
* @param {Object} pParameters SQL Query parameters
|
|
1067
|
+
* @return {String} Returns the generated query.
|
|
1068
|
+
*/
|
|
1069
|
+
var Read = function(pParameters)
|
|
1070
|
+
{
|
|
1071
|
+
_QuoteIdentifiers = !!pParameters.quoteIdentifiers;
|
|
1072
|
+
var tmpFieldList = generateFieldList(pParameters);
|
|
1073
|
+
var tmpTableName = generateTableName(pParameters);
|
|
1074
|
+
var tmpWhere = generateWhere(pParameters);
|
|
1075
|
+
var tmpJoin = generateJoins(pParameters);
|
|
1076
|
+
var tmpOrderBy = generateOrderBy(pParameters);
|
|
1077
|
+
var tmpLimit = generateLimit(pParameters);
|
|
1078
|
+
const tmpOptDistinct = pParameters.distinct ? ' DISTINCT' : '';
|
|
1079
|
+
|
|
1080
|
+
if (pParameters.queryOverride)
|
|
1081
|
+
{
|
|
1082
|
+
try
|
|
1083
|
+
{
|
|
1084
|
+
var tmpQueryTemplate = _Fable.Utility.template(pParameters.queryOverride);
|
|
1085
|
+
return tmpQueryTemplate({FieldList:tmpFieldList, TableName:tmpTableName, Where:tmpWhere, Join:tmpJoin, OrderBy:tmpOrderBy, Limit:tmpLimit, Distinct: tmpOptDistinct, _Params: pParameters});
|
|
1086
|
+
}
|
|
1087
|
+
catch (pError)
|
|
1088
|
+
{
|
|
1089
|
+
console.log('Error with custom Read Query ['+pParameters.queryOverride+']: '+pError);
|
|
1090
|
+
return false;
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
// Legacy pagination path — wrap in a ROWNUM double-subquery for 11g
|
|
1095
|
+
// and earlier, which have no OFFSET/FETCH. The innermost query keeps
|
|
1096
|
+
// the ORDER BY; the middle query captures ROWNUM with an upper-bound
|
|
1097
|
+
// predicate (enabling a COUNT STOPKEY short-circuit); the outer query
|
|
1098
|
+
// applies the lower bound. Enabled via pParameters.legacyPagination
|
|
1099
|
+
// (forwarded from the meadow-connection-oracle LegacyPagination config).
|
|
1100
|
+
if (pParameters.legacyPagination && pParameters.cap)
|
|
1101
|
+
{
|
|
1102
|
+
var tmpBegin = (pParameters.begin !== false) ? pParameters.begin : 0;
|
|
1103
|
+
var tmpEnd = tmpBegin + pParameters.cap;
|
|
1104
|
+
var tmpOuterFieldList = generateOuterFieldListForLegacyPagination(pParameters);
|
|
1105
|
+
return `SELECT${tmpOptDistinct}${tmpOuterFieldList} FROM (SELECT meadow_inner.*, ROWNUM AS "_RowNum" FROM (SELECT${tmpFieldList} FROM${tmpTableName}${tmpJoin}${tmpWhere}${tmpOrderBy}) meadow_inner WHERE ROWNUM <= ${tmpEnd}) WHERE "_RowNum" > ${tmpBegin}`;
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
return `SELECT${tmpOptDistinct}${tmpFieldList} FROM${tmpTableName}${tmpJoin}${tmpWhere}${tmpOrderBy}${tmpLimit}`;
|
|
1109
|
+
};
|
|
1110
|
+
|
|
1111
|
+
var Update = function(pParameters)
|
|
1112
|
+
{
|
|
1113
|
+
_QuoteIdentifiers = !!pParameters.quoteIdentifiers;
|
|
1114
|
+
var tmpTableName = generateTableName(pParameters);
|
|
1115
|
+
var tmpUpdateSetters = generateUpdateSetters(pParameters);
|
|
1116
|
+
var tmpWhere = generateWhere(pParameters);
|
|
1117
|
+
|
|
1118
|
+
if (!tmpUpdateSetters)
|
|
1119
|
+
{
|
|
1120
|
+
return false;
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
return 'UPDATE'+tmpTableName+' SET'+tmpUpdateSetters+tmpWhere;
|
|
1124
|
+
};
|
|
1125
|
+
|
|
1126
|
+
var Delete = function(pParameters)
|
|
1127
|
+
{
|
|
1128
|
+
_QuoteIdentifiers = !!pParameters.quoteIdentifiers;
|
|
1129
|
+
var tmpTableName = generateTableName(pParameters);
|
|
1130
|
+
var tmpUpdateDeleteSetters = generateUpdateDeleteSetters(pParameters);
|
|
1131
|
+
var tmpWhere = generateWhere(pParameters);
|
|
1132
|
+
|
|
1133
|
+
if (tmpUpdateDeleteSetters)
|
|
1134
|
+
{
|
|
1135
|
+
return 'UPDATE'+tmpTableName+' SET'+tmpUpdateDeleteSetters+tmpWhere;
|
|
1136
|
+
}
|
|
1137
|
+
else
|
|
1138
|
+
{
|
|
1139
|
+
return 'DELETE FROM'+tmpTableName+tmpWhere;
|
|
1140
|
+
}
|
|
1141
|
+
};
|
|
1142
|
+
|
|
1143
|
+
var Undelete = function(pParameters)
|
|
1144
|
+
{
|
|
1145
|
+
_QuoteIdentifiers = !!pParameters.quoteIdentifiers;
|
|
1146
|
+
var tmpTableName = generateTableName(pParameters);
|
|
1147
|
+
let tmpDeleteTrackingState = pParameters.query.disableDeleteTracking;
|
|
1148
|
+
pParameters.query.disableDeleteTracking = true;
|
|
1149
|
+
var tmpUpdateUndeleteSetters = generateUpdateUndeleteSetters(pParameters);
|
|
1150
|
+
var tmpWhere = generateWhere(pParameters);
|
|
1151
|
+
pParameters.query.disableDeleteTracking = tmpDeleteTrackingState;
|
|
1152
|
+
|
|
1153
|
+
if (tmpUpdateUndeleteSetters)
|
|
1154
|
+
{
|
|
1155
|
+
return 'UPDATE'+tmpTableName+' SET'+tmpUpdateUndeleteSetters+tmpWhere;
|
|
1156
|
+
}
|
|
1157
|
+
else
|
|
1158
|
+
{
|
|
1159
|
+
// No-op — the record can't be undeleted. Oracle requires a FROM.
|
|
1160
|
+
return 'SELECT NULL FROM DUAL';
|
|
1161
|
+
}
|
|
1162
|
+
};
|
|
1163
|
+
|
|
1164
|
+
var Count = function(pParameters)
|
|
1165
|
+
{
|
|
1166
|
+
_QuoteIdentifiers = !!pParameters.quoteIdentifiers;
|
|
1167
|
+
var tmpFieldList = pParameters.distinct ? generateFieldList(pParameters, true) : '*';
|
|
1168
|
+
var tmpTableName = generateTableName(pParameters);
|
|
1169
|
+
var tmpJoin = generateJoins(pParameters);
|
|
1170
|
+
var tmpWhere = generateWhere(pParameters);
|
|
1171
|
+
if (pParameters.distinct && tmpFieldList.length < 1)
|
|
1172
|
+
{
|
|
1173
|
+
console.warn('Distinct requested but no field list or schema are available, so not honoring distinct for count query.');
|
|
1174
|
+
}
|
|
1175
|
+
const tmpOptDistinct = pParameters.distinct && tmpFieldList.length > 0 ? 'DISTINCT' : '';
|
|
1176
|
+
|
|
1177
|
+
if (pParameters.queryOverride)
|
|
1178
|
+
{
|
|
1179
|
+
try
|
|
1180
|
+
{
|
|
1181
|
+
var tmpQueryTemplate = _Fable.Utility.template(pParameters.queryOverride);
|
|
1182
|
+
return tmpQueryTemplate({FieldList:[], TableName:tmpTableName, Where:tmpWhere, OrderBy:'', Limit:'', Distinct: tmpOptDistinct, _Params: pParameters});
|
|
1183
|
+
}
|
|
1184
|
+
catch (pError)
|
|
1185
|
+
{
|
|
1186
|
+
console.log('Error with custom Count Query ['+pParameters.queryOverride+']: '+pError);
|
|
1187
|
+
return false;
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
return `SELECT COUNT(${tmpOptDistinct}${tmpFieldList || '*'}) AS RowCount FROM${tmpTableName}${tmpJoin}${tmpWhere}`;
|
|
1192
|
+
};
|
|
1193
|
+
|
|
1194
|
+
var tmpDialect = ({
|
|
1195
|
+
Create: Create,
|
|
1196
|
+
Read: Read,
|
|
1197
|
+
Update: Update,
|
|
1198
|
+
Delete: Delete,
|
|
1199
|
+
Undelete: Undelete,
|
|
1200
|
+
Count: Count
|
|
1201
|
+
});
|
|
1202
|
+
|
|
1203
|
+
/**
|
|
1204
|
+
* Dialect Name
|
|
1205
|
+
*
|
|
1206
|
+
* @property name
|
|
1207
|
+
* @type string
|
|
1208
|
+
*/
|
|
1209
|
+
Object.defineProperty(tmpDialect, 'name',
|
|
1210
|
+
{
|
|
1211
|
+
get: function() { return 'Oracle'; },
|
|
1212
|
+
enumerable: true
|
|
1213
|
+
});
|
|
1214
|
+
|
|
1215
|
+
return tmpDialect;
|
|
1216
|
+
};
|
|
1217
|
+
|
|
1218
|
+
module.exports = FoxHoundDialectOracle;
|