@sap/xsodata 7.5.5 → 8.0.0

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/CHANGELOG.md CHANGED
@@ -8,6 +8,12 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/).
8
8
 
9
9
  ## Unreleased
10
10
 
11
+ ## [8.0.0] - 2022-08-04
12
+
13
+ * HANA Cloud database support added
14
+ * Public synonym support added
15
+ * Update module dependencies
16
+
11
17
  ## [7.5.5] - 2022-04-21
12
18
 
13
19
  * On calc view: Stable sort order with $skip and $top usage for subsequent requests via "order by" of key fields in SQL statement
package/lib/db/connect.js CHANGED
@@ -1,4 +1,5 @@
1
1
  'use strict';
2
+
2
3
  /**
3
4
  * Add <db> attribute to <context>
4
5
  * Uses <context>.<handlerConfiguration.dbConfiguration>
@@ -60,13 +61,48 @@ function loadDbVersion(context, asyncDone) {
60
61
  context.logger.info('xsodata', 'db version: ' + version);
61
62
  context.gModel.setDbVersion(version);
62
63
  } else {
63
- context.gModel.setDbVersion(null); // don't try reload
64
+ context.gModel.setDbVersion(null); // don't try reload
65
+ }
66
+
67
+ return asyncDone(null, context);
68
+ });
69
+ }
70
+
71
+ function setHanaCloudContext(context, asyncDone) {
72
+
73
+ return context.db.client.exec('select version from "SYS"."M_DATABASE"', function (err, rows) {
74
+ if (err) {
75
+ return asyncDone(new SqlError(context, err), context);
76
+ }
77
+
78
+ if (rows && rows[0] && rows[0].VERSION) {
79
+ const version = rows[0].VERSION;
80
+ context.logger.info('xsodata', 'db version: ' + version);
81
+ context.db.isHanaCloudDb = isHanaCloudDb(context, rows[0].VERSION);
82
+ context.logger.info('xsodata', `isHanaCloud: ${context.db.isHanaCloudDb} (DB version based)`);
83
+ } else {
84
+ // 06/2022:
85
+ // Currently all SQL from Hana Cloud work also on Hana Service, i.e. take that version as default;
86
+ // could be changed in the future, i.e. using the context setting of 'context.db.isHanaCloudDb'
87
+ context.db.isHanaCloudDb = true;
88
+ context.logger.info('xsodata', `isHanaCloud: ${context.db.isHanaCloudDb} (default)`);
64
89
  }
65
90
 
66
91
  return asyncDone(null, context);
67
92
  });
68
93
  }
69
94
 
95
+ function isHanaCloudDb(context, version) {
96
+ const regex = /^([0-9])+\..*$/;
97
+ let result = version.match(regex);
98
+
99
+ if (result && result[1]) {
100
+ return (result[1] >= 4);
101
+ } else {
102
+ context.logger.error(`DB Version parsing failed: ${version}`);
103
+ return true; // default: Hana Cloud
104
+ }
105
+ }
70
106
 
71
107
  function getDbClientType(dbClient) {
72
108
  if (dbClient._settings) {
@@ -97,6 +133,8 @@ function prepareConnection(context, asyncDone) {
97
133
  // Set the isolation level
98
134
  executeList.push(utils.tryAndMeasure(execSQL, 'SET TRANSACTION ISOLATION LEVEL REPEATABLE READ', 'execSQL (isolation level)'));
99
135
  executeList.push(loadDbVersion);
136
+ // Set Hana Cloud indicator in context
137
+ executeList.push(setHanaCloudContext);
100
138
 
101
139
  async.waterfall(
102
140
  executeList,
@@ -163,9 +201,6 @@ function _connectInternal(context, asyncDone) {
163
201
 
164
202
 
165
203
  try {
166
- context.logger.silly('connect - db', 'use hdb.createClient');
167
-
168
-
169
204
  let db_module = null;
170
205
 
171
206
  if (context.handlerConfiguration.dbClient === 'hdb') {
@@ -173,11 +208,13 @@ function _connectInternal(context, asyncDone) {
173
208
  hdb_module = require('hdb');
174
209
  }
175
210
  db_module = hdb_module;
211
+ context.logger.silly('connect - db', 'use hdb.createClient');
176
212
  } else {
177
213
  if (!hana_client_module) {
178
214
  hana_client_module = require('@sap/hana-client');
179
215
  }
180
216
  db_module = hana_client_module;
217
+ context.logger.silly('connect - db', 'use hana-client');
181
218
  }
182
219
 
183
220
 
@@ -250,6 +287,7 @@ exports.connect = function (context, asyncDone) {
250
287
  }
251
288
  context.logger.debug('connect - db', 'connection is usable');
252
289
  context.db.connectionInitialized = true;
290
+ context.logger.debug('connect - context setting - Is Hana Cloud used?', `${context.db.isHanaCloudDb}`);
253
291
  return asyncDone(null, context);
254
292
 
255
293
  });
@@ -43,6 +43,12 @@ function EntityType(entityType, settings) {
43
43
  this.path = this._entityType.path;
44
44
  this.keys = determineKeys(this._entityType);
45
45
 
46
+ // virtual table / virtual view data (includes synonyms + virtual tables)
47
+ this._isVirtual = false;
48
+ this._virtualObjectSchema = null;
49
+ this._virtualObjectName = null;
50
+ this._remoteSource = null;
51
+
46
52
  // in .xsodata as "entityname" --> null
47
53
  // in .xsodata as "entityname" concurrencytoken; --> {}
48
54
  // in .xsodata as "entityname" concurrencytoken ("KEY"); --> { key : true }
@@ -107,6 +113,12 @@ function EntityType(entityType, settings) {
107
113
  this._admindata = prepareAdmindata(this._settings.admindata, this._entityType.admindata);
108
114
  }
109
115
 
116
+ EntityType.prototype.setVirtualInfo = function(objectSchema, objectName, remoteSource = null) {
117
+ this._isVirtual = true;
118
+ this._virtualObjectSchema = objectSchema;
119
+ this._virtualObjectName = objectName;
120
+ this._remoteSource = remoteSource;
121
+ };
110
122
 
111
123
  function mergeAdmindataTuples(into, data) {
112
124
  for (const tuple of data) {
@@ -44,11 +44,15 @@ function loadKeysForTable(context, entityType, cb) {
44
44
  return cb(null, entityType);
45
45
  };
46
46
 
47
- var schema = entityType.schema || context.defaultSchema;
48
-
49
- var sql =
50
- "SELECT * from CONSTRAINTS " +
51
- "WHERE SCHEMA_NAME = '" + schema + "' AND TABLE_NAME = '" + entityType.tableName + "' AND IS_PRIMARY_KEY = 'TRUE' ORDER BY position";
47
+ let sql = "";
48
+ if (entityType._isVirtual) {
49
+ sql =
50
+ "select * from " + (entityType._remoteSource ? "\"" + entityType._remoteSource + "\".\"SYS\".\"CONSTRAINTS\"" : "\"SYS\".\"CONSTRAINTS\" " ) +
51
+ "WHERE SCHEMA_NAME = '" + entityType._virtualObjectSchema + "' AND TABLE_NAME = '" + entityType._virtualObjectName + "' AND IS_PRIMARY_KEY = 'TRUE' ORDER BY position";
52
+ } else {
53
+ let schema = entityType.schema || context.defaultSchema;
54
+ sql = "SELECT * from CONSTRAINTS WHERE SCHEMA_NAME = '" + schema + "' AND TABLE_NAME = '" + entityType.tableName + "' AND IS_PRIMARY_KEY = 'TRUE' ORDER BY position";
55
+ }
52
56
  dataCollector2.executeSqlDirectly(context, sql, processRows);
53
57
  }
54
58
 
@@ -104,13 +108,13 @@ function loadTableInfo(context, entityType, tableStoreType, cb) {
104
108
  }
105
109
  };
106
110
 
107
- context.logger.silly("model", "loadTableInfo 2");
108
- var schema = entityType.schema || context.defaultSchema;
109
- // entityType.table is either a real table or a SQL view
110
- // So we need to lookup both: sys.table_columns and sys.view_columns
111
- // sys.columns is not usable due to access restrictions
112
- var sql =
111
+ let schema = "";
112
+ let parameters = [];
113
113
 
114
+ // entityType.table is either a real table or a SQL view;
115
+ // so we need to lookup both: sys.table_columns and sys.view_columns (if set: check via "remote source");
116
+ // sys.columns is not usable due to access restrictions
117
+ let sql =
114
118
  "SELECT " +
115
119
  " TO_BIGINT(" + model.entityKind.table + ") kind, " +
116
120
  " table_name, " +
@@ -121,7 +125,7 @@ function loadTableInfo(context, entityType, tableStoreType, cb) {
121
125
  " length, " +
122
126
  " scale, " +
123
127
  " default_value " +
124
- "FROM TABLE_COLUMNS " +
128
+ "FROM " + (entityType._remoteSource ? "\"" + entityType._remoteSource + "\"" + ".\"SYS\".\"TABLE_COLUMNS\"" : "TABLE_COLUMNS ") +
125
129
  "WHERE schema_name = ? AND " +
126
130
  " table_name = ? " +
127
131
  "UNION ALL " +
@@ -135,13 +139,17 @@ function loadTableInfo(context, entityType, tableStoreType, cb) {
135
139
  " length, " +
136
140
  " scale, " +
137
141
  " default_value " +
138
- "FROM VIEW_COLUMNS " +
142
+ "FROM " + (entityType._remoteSource ? "\"" + entityType._remoteSource + "\"" + ".\"SYS\".\"VIEW_COLUMNS\"" : "VIEW_COLUMNS ") +
139
143
  "WHERE schema_name = ? AND " +
140
144
  " view_name = ? " +
141
145
  "ORDER BY position";
142
146
 
143
- var parameters = [schema, entityType.tableName, schema, entityType.tableName];
144
-
147
+ if (entityType._isVirtual) {
148
+ parameters = [entityType._virtualObjectSchema, entityType._virtualObjectName, entityType._virtualObjectSchema, entityType._virtualObjectName];
149
+ } else {
150
+ schema = entityType.schema || context.defaultSchema;
151
+ parameters = [schema, entityType.tableName, schema, entityType.tableName];
152
+ }
145
153
  context.logger.silly("model", "sql loadTableInfo: " + sql);
146
154
  context.logger.silly("model", "sql parameters: " + parameters);
147
155
  dataCollector2.executeSqlAsPreparedStatement(context, sql, parameters, processRows);
@@ -151,26 +159,26 @@ function loadCalcViewInfo(context, entityType, cb) {
151
159
  /*
152
160
  Remark : it is not possible(or at least i was not able to) to execute a JOIN in a MDX SELECT
153
161
  */
154
- var schema = entityType.schema || context.defaultSchema;
155
- var sql = 'select ' +
156
- 'SCHEMA_NAME,' +
157
- 'QUALIFIED_NAME,' +
158
- 'CATALOG_NAME,' +
159
- 'CUBE_NAME,' +
160
- 'COLUMN_NAME,' + // JOIN ON
161
- 'MEASURE_AGGREGATOR,' + // 1, 2, 3, 4 in sync with entityType.CV_AGGREGATE_FUNCTIONS
162
- 'MEASURE_AGGREGATABLE, ' +
163
- 'SEMANTIC_TYPE, ' +
164
- 'COLUMN_CAPTION, ' +
165
- 'DIMENSION_TYPE, ' +
166
- 'UNIT_COLUMN_NAME, ' +
167
- 'DESC_NAME, ' +
168
- '"ORDER" ' +
169
- "from _SYS_BI.BIMC_DIMENSION_VIEW_HDI " +
162
+ let schema = entityType.schema || context.defaultSchema;
163
+ let sql =
164
+ 'select ' +
165
+ 'SCHEMA_NAME,' +
166
+ 'QUALIFIED_NAME,' +
167
+ 'CATALOG_NAME,' +
168
+ 'CUBE_NAME,' +
169
+ 'COLUMN_NAME,' + // JOIN ON
170
+ 'MEASURE_AGGREGATOR,' + // 1, 2, 3, 4, 5 in sync with entityType.CV_AGGREGATE_FUNCTIONS
171
+ 'MEASURE_AGGREGATABLE, ' +
172
+ 'SEMANTIC_TYPE, ' +
173
+ 'COLUMN_CAPTION, ' +
174
+ 'DIMENSION_TYPE, ' +
175
+ 'UNIT_COLUMN_NAME, ' +
176
+ 'DESC_NAME, ' +
177
+ '"ORDER" ' +
178
+ "from " + (entityType._remoteSource ? "\"" + entityType._remoteSource + "\"" + ".\"_SYS_BI\".\"BIMC_DIMENSION_VIEW_HDI\"" : "_SYS_BI.BIMC_DIMENSION_VIEW_HDI ") +
170
179
  "where SCHEMA_NAME = ? AND QUALIFIED_NAME = ? " +
171
180
  'order by "ORDER"';
172
181
 
173
-
174
182
  function processRows(err, rows) {
175
183
  if (logMetadata) {
176
184
  context.logger.silly('metadataReader', 'rows\n' + JSON.stringify(rows, null, 4));
@@ -232,27 +240,37 @@ function loadCalcViewInfo(context, entityType, cb) {
232
240
  The current HANA version has a bug, and prepared statements are not working in mdx select.
233
241
  Hence i replaced prepared statement with direct sql statement.
234
242
  */
235
- var parameters = [schema, entityType.tableName];
243
+
244
+ let parameters = [];
245
+ if (entityType._isVirtual) {
246
+ // CV information must be read from the remote database;
247
+ // no such concept of "virtual" CV data in local database as it is the case for tables
248
+ parameters = [entityType._virtualObjectSchema, entityType._virtualObjectName];
249
+ } else {
250
+ parameters = [schema, entityType.tableName];
251
+ }
236
252
  dataCollector2.executeSqlAsPreparedStatement(context, sql, parameters, processRows);
237
253
  }
238
254
 
239
255
  function loadInputParameters(parameters, entityType, context, cb) {
240
- // At the time this sql was developed SCHEMA_NAME == NULL
241
- var sql = 'select distinct ' +
242
- 'VARIABLE_NAME, ' +
243
- 'COLUMN_TYPE_D, ' +
244
- 'COLUMN_SQL_TYPE, ' +
245
- 'MANDATORY, ' +
246
- 'DESCRIPTION, ' +
247
- 'DEFAULT_VALUE, ' +
248
- 'SEMANTIC_TYPE, ' +
249
- 'SELECTION_TYPE, ' +
250
- 'MULTILINE, ' +
251
- '"ORDER" ' +
252
- 'from _SYS_BI.BIMC_VARIABLE_VIEW_HDI where QUALIFIED_NAME = ? order by "ORDER"';
256
+
257
+ let sql =
258
+ 'select distinct ' +
259
+ 'VARIABLE_NAME, ' +
260
+ 'COLUMN_TYPE_D, ' +
261
+ 'COLUMN_SQL_TYPE, ' +
262
+ 'MANDATORY, ' +
263
+ 'DESCRIPTION, ' +
264
+ 'DEFAULT_VALUE, ' +
265
+ 'SEMANTIC_TYPE, ' +
266
+ 'SELECTION_TYPE, ' +
267
+ 'MULTILINE, ' +
268
+ '"ORDER" ' +
269
+ 'from ' + (entityType._remoteSource ? '"' + entityType._remoteSource + '"' + '"_SYS_BI"."BIMC_VARIABLE_VIEW_HDI"' : '_SYS_BI.BIMC_VARIABLE_VIEW_HDI ') +
270
+ 'where QUALIFIED_NAME = ? order by "ORDER"';
253
271
 
254
272
  context.logger.debug('metadataReader', 'loadInputParameters: ' + entityType.tableName);
255
- context.logger.silly("metadataReader", "sql loadInputParameters: " + sql);
273
+ context.logger.debug("metadataReader", "sql loadInputParameters: " + sql);
256
274
 
257
275
  dataCollector2.executeSqlAsPreparedStatement(context, sql, [entityType.tableName], (err, rows) => {
258
276
 
@@ -459,17 +477,127 @@ function loadEntityType(context, entityType, cbErr) {
459
477
  * @param {Function} callback - callback function, which is called when the operation is completed.
460
478
  */
461
479
  function determineStoreType(context, entityType, callback) {
462
- var schema = entityType.schema || context.defaultSchema,
480
+
481
+ let schema = entityType.schema || context.defaultSchema,
463
482
  sql = 'select IS_COLUMN_TABLE as "IS_COLUMN_TYPE" from TABLES where SCHEMA_NAME = ? AND TABLE_NAME = ? ' +
464
- 'union select IS_COLUMN_VIEW as "IS_COLUMN_TYPE" from VIEWS where SCHEMA_NAME = ? AND VIEW_NAME = ?',
483
+ 'union select IS_COLUMN_VIEW as "IS_COLUMN_TYPE" from VIEWS where SCHEMA_NAME = ? AND VIEW_NAME = ?',
465
484
  tableName = entityType.tableName,
466
485
  parameters = [schema, tableName, schema, tableName];
467
486
 
468
- context.logger.silly("model", "sql determineStoreType: " + sql);
469
- context.logger.silly("model", "sql determineStoreType parameters: " + parameters);
487
+ context.logger.debug("model", "sql determineStoreType: " + sql);
488
+ context.logger.debug("model", "sql determineStoreType parameters: " + parameters);
470
489
 
471
490
  dataCollector2.executeSqlAsPreparedStatement(context, sql, parameters, (error, rows) => {
472
- return processTableType(error, rows, context, tableName, callback);
491
+
492
+ if (error) {
493
+ return callback(new SqlError(context, error));
494
+ }
495
+
496
+ // if no error and no rows are returned: check for public synonym;
497
+ // if a virtual table is used (includes remote tables and remote views / CVs), we got a row back here!
498
+ if (rows.length === 0) {
499
+ context.logger.debug("model", `No table/view information found for ${tableName}. Try public synonym.`);
500
+ processSynonymTableType(context, entityType, tableName, (synonymErr, synonymFound, tableStoreType) => {
501
+ if (synonymErr) {
502
+ return callback(synonymErr);
503
+ }
504
+ if (synonymFound) {
505
+ return callback(null, tableStoreType);
506
+ }
507
+ return callback(new NotFound(`No table/view or synonym data found for table ${tableName}.`, context));
508
+ });
509
+ } else {
510
+ // check for virtual table; add VT data for entity type, if existing
511
+ let virtualTableSQL = "select * from SYS.VIRTUAL_TABLES where schema_name = ? and table_name = ?";
512
+ let virtualTableParameters = [schema, tableName];
513
+ context.logger.info("model", `Check for virtual table for ${tableName}`);
514
+ dataCollector2.executeSqlAsPreparedStatement(context, virtualTableSQL, virtualTableParameters, (sqlError, virtualTableRows) => {
515
+ if (sqlError) {
516
+ context.logger.error("model", `Failed read data for virtual table: ${tableName}` + sqlError);
517
+ return callback(new SqlError(context, sqlError));
518
+ }
519
+ if (virtualTableRows.length !== 0) {
520
+ entityType.setVirtualInfo(
521
+ virtualTableRows[0].REMOTE_OWNER_NAME,
522
+ virtualTableRows[0].REMOTE_OBJECT_NAME,
523
+ virtualTableRows[0].REMOTE_SOURCE_NAME );
524
+ context.logger.info("model", `(Remote) Virtual table information for table ${tableName}`);
525
+ context.logger.info("model", `Remote schema: ${virtualTableRows[0].REMOTE_OWNER_NAME}`);
526
+ context.logger.info("model", `Remote object: ${virtualTableRows[0].REMOTE_OBJECT_NAME}`);
527
+ context.logger.info("model", `Remote source: ${virtualTableRows[0].REMOTE_SOURCE_NAME}`);
528
+ } else {
529
+ context.logger.info("model", `No virtual table information exists for ${tableName}`);
530
+ }
531
+ // use data already found from TABLES / VIEWS for setting store type
532
+ return processTableType(error, rows, context, tableName, callback);
533
+ });
534
+ }
535
+ });
536
+ }
537
+
538
+ function processSynonymTableType(context, entityType, tableName, callback) {
539
+
540
+ const synonymSql = `select * from SYNONYMS where SCHEMA_NAME = 'PUBLIC' AND SYNONYM_NAME = '${tableName}'`;
541
+
542
+ dataCollector2.executeSqlDirectly(context, synonymSql, (synonymErr, synonymRows) => {
543
+ if (synonymErr) {
544
+ context.logger.error("model", "Failed to read synonym: " + synonymErr);
545
+ return callback(new SqlError(context, synonymErr), false, null);
546
+ }
547
+ if (synonymRows.length === 0) {
548
+ return callback(null, false, null); // not a synonym
549
+ }
550
+ if (synonymRows.length !== 1) {
551
+ context.logger.error("model", "No (unique) synonym data found for " + tableName + " table.");
552
+ return callback(new NotFound("No (unique) synonym data found for " + tableName + " table.", context), false, null);
553
+ }
554
+
555
+ // check for local or remote synonym
556
+ if (synonymRows[0].OBJECT_SCHEMA === '_SYS_LDB') { // remote synonym
557
+
558
+ const virtualTableName = synonymRows[0].OBJECT_NAME;
559
+ const virtualTableSql = `select * from VIRTUAL_TABLES where SCHEMA_NAME = '_SYS_LDB' and TABLE_NAME = '${virtualTableName}'`;
560
+
561
+ context.logger.info("model", `Checking virtual tables for remote synonym information for table ${virtualTableName}`);
562
+
563
+ // get remote source
564
+ dataCollector2.executeSqlDirectly(context, virtualTableSql, (virtualTableErr, virtualTableRows) => {
565
+ if (virtualTableErr) {
566
+ context.logger.error("model", `Failed to read virtual table ${virtualTableName} for synonym ${tableName}: ${virtualTableErr}`);
567
+ return callback(new SqlError(context, virtualTableErr), false, null);
568
+ }
569
+
570
+ context.logger.info("model", `(Remote) Virtual table information for synonym ${tableName} via table ${virtualTableName}`);
571
+ context.logger.info("model", `Remote schema: ${virtualTableRows[0].REMOTE_OWNER_NAME}`);
572
+ context.logger.info("model", `Remote object: ${virtualTableRows[0].REMOTE_OBJECT_NAME}`);
573
+ context.logger.info("model", `Remote source: ${virtualTableRows[0].REMOTE_SOURCE_NAME}`);
574
+
575
+ entityType.setVirtualInfo(
576
+ virtualTableRows[0].REMOTE_OWNER_NAME, // remote schema name
577
+ virtualTableRows[0].REMOTE_OBJECT_NAME, // remote object name
578
+ virtualTableRows[0].REMOTE_SOURCE_NAME); // remote source
579
+
580
+ // in SYNONYMS, each remote object has always the setting IS_COLUMN_OBJECT === FALSE (keep it as is)
581
+ let isColumnStoreType = synonymRows[0].IS_COLUMN_OBJECT;
582
+ let tableStoreType = isColumnStoreType === "TRUE" ? "column" : "row";
583
+ return callback(null, true, tableStoreType);
584
+ });
585
+
586
+ } else { // local synonym
587
+
588
+ context.logger.info("model", `(Local) Synonym information for ${synonymRows[0].SYNONYM_NAME}`);
589
+ context.logger.info("model", `Local schema: ${synonymRows[0].OBJECT_SCHEMA}`);
590
+ context.logger.info("model", `Local object: ${synonymRows[0].OBJECT_NAME}`);
591
+
592
+ entityType.setVirtualInfo(
593
+ synonymRows[0].OBJECT_SCHEMA, // local schema name
594
+ synonymRows[0].OBJECT_NAME); // local object name
595
+
596
+ // in SYNONYMS, a local object has the "correct" store type setting
597
+ let isColumnObjectType = synonymRows[0].IS_COLUMN_OBJECT;
598
+ let tableStoreType = isColumnObjectType === "TRUE" ? "column" : "row";
599
+ return callback(null, true, tableStoreType);
600
+ }
473
601
  });
474
602
  }
475
603
 
@@ -487,7 +615,7 @@ function processTableType(error, dbRows, context, tableName, callback) {
487
615
  tableStoreType;
488
616
 
489
617
  if (error) {
490
- return callback(new SqlError(context, error));
618
+ return (error instanceof SqlError) ? callback(error) : callback(new SqlError(context, error));
491
619
  }
492
620
 
493
621
  if (dbRows.length === 0) {
@@ -497,6 +625,8 @@ function processTableType(error, dbRows, context, tableName, callback) {
497
625
  context.logger.silly("processTableType", "rows:\n" + JSON.stringify(dbRows, null, 4));
498
626
  }
499
627
 
628
+ // if found table is a virtual (remote) table, the IS_COLUMN_TYPE is always set to FALSE,
629
+ // but this setting does not affect SQL processing (joins on temp. tables)
500
630
  isColumnStoreType = dbRows[0].IS_COLUMN_TYPE;
501
631
 
502
632
  // HANA returns "TRUE"/"FALSE" as a string and not as a boolean value
@@ -592,6 +722,9 @@ exports.loadModelMetadata = function (context, asyncDone) {
592
722
  }
593
723
  },
594
724
  function (err) {
725
+ if (err) {
726
+ context.logger.error('model', `Error on loadEntityTypes for ${context.uriTree.xsoFile}:` + err);
727
+ }
595
728
  context.logger.silly('model', 'loadEntityTypes finished for ' + context.uriTree.xsoFile);
596
729
  return asyncDone(err, context);
597
730
  }
@@ -182,7 +182,7 @@ function checkSchema(context, asyncDone) {
182
182
  entityTypes = parsedXsodata.service.entityTypes;
183
183
 
184
184
 
185
- // Run through entityTypes and check if navigates peroperty id provided
185
+ // Run through entityTypes and check if navigates property id provided
186
186
  for (key in entityTypes) {
187
187
  if (entityTypes.hasOwnProperty(key) === true) {
188
188
 
@@ -148,15 +148,15 @@ function processChangeSet(batchContext, part, asyncDone) {
148
148
 
149
149
  utils.try(collectContentIds),
150
150
 
151
- utils.try(processChangeSetPartsCreateTable),
151
+ utils.try(processChangeSetPartsCreateTable), // for *ALL* operations of current changeset: create all temp. tables once at beginning
152
152
 
153
- utils.try(processChangeSetParts),
153
+ utils.try(processChangeSetParts), // execute all operations
154
154
 
155
- utils.try(processChangeSetPartsPreCommit),
155
+ utils.try(processChangeSetPartsPreCommit), // mainly custom-exit processing (pre-commit exit)
156
156
 
157
- utils.try(processChangeSetPartsCommit),
157
+ utils.try(processChangeSetPartsCommit), // all changes of operations are committed
158
158
 
159
- utils.try(processChangeSetPartsPostCommit)
159
+ utils.try(processChangeSetPartsPostCommit) // mainly custom-exit processing (post-commit exit)
160
160
 
161
161
  ], function (err, batchContext) {
162
162
  if (!err) {
@@ -220,7 +220,7 @@ function processAppWithState(app, state, batchContext, asyncDone) {
220
220
 
221
221
  function processPart(batchContext, part, asyncDone) {
222
222
  batchContext.logger.silly('batchProcessor', 'processPart');
223
- if (part.type === 'app') {
223
+ if (part.type === 'app') { // processing a batch single operation (not in changeset)
224
224
  async.waterfall(
225
225
  [
226
226
  utils.injectContext(batchContext),
@@ -288,7 +288,7 @@ exports.process = function (context, asyncDone) {
288
288
  var batchContext = {
289
289
  parentContext: context,
290
290
  logger: context.logger,
291
- callRegisteredStep : context.callRegisteredStep, // pass to inner context
291
+ callRegisteredStep : context.callRegisteredStep, // pass to inner context
292
292
  inChangeSet: false,
293
293
  batchData: null,
294
294
  batchParsed: null,
@@ -21,15 +21,15 @@ exports.processRequest = function (context, asyncDone) {
21
21
  } else if (oData.kind === odataUri.URI_KIND_MetaData) {
22
22
  Measurement.measureWithCB(metadataProcessor.process, context, asyncDone, 'metadataProcessor.process');
23
23
  } else if (oData.kind === odataUri.URI_KIND_Batch) {
24
- Measurement.measureWithCB(batchProcessor.process, context, asyncDone, 'batchProcessor.process');
24
+ Measurement.measureWithCB(batchProcessor.process, context, asyncDone, 'batchProcessor.process'); // $batch-request itself
25
25
  } else if (oData.kind === odataUri.URI_KIND_Resource) {
26
26
 
27
27
  if (!context.batchContext) {
28
- //normal no batch request processing
28
+ // regular request processing
29
29
  Measurement.measureWithCB(resourceProcessor.process, context, asyncDone, 'resourceProcessor.process');
30
30
  } else {
31
- var batchContext = context.batchContext;
32
- if (batchContext.status === batchRunState.createTables) {
31
+ var batchContext = context.batchContext; // $batch-request: operation processing (based on status)
32
+ if (batchContext.status === batchRunState.createTables) { // on batch changes: all temp. tables of all operations are generated at beginning batch
33
33
  Measurement.measureWithCB(resourceProcessor.processInBatchCreateTables, context, asyncDone, 'processInBatchCreateTables');
34
34
  }else if (batchContext.status === batchRunState.execution) {
35
35
  Measurement.measureWithCB(resourceProcessor.processInBatch, context, asyncDone, 'processInBatch');
@@ -22,7 +22,14 @@ exports.createDeleteLinksMNStatementsCreateTmpTables = function (context, asyncD
22
22
  context.sql = sqlContext;
23
23
  dbSegLast.sql.stmContainer = new sql.PostContainer();
24
24
 
25
- createMNDeleteStatementContainerCreateTmpTables (sqlContext);
25
+ // set Hana Cloud / Service context in SQL context; default == cloud
26
+ let isHanaCloudDb = true;
27
+ if (context.db !== undefined && context.db.isHanaCloudDb !== undefined) {
28
+ isHanaCloudDb = context.db.isHanaCloudDb;
29
+ }
30
+ context.sql.isHanaCloudDb = isHanaCloudDb;
31
+
32
+ createMNDeleteStatementContainerCreateTmpTables(sqlContext);
26
33
  return asyncDone(null, context);
27
34
  };
28
35
 
@@ -8,7 +8,7 @@ exports.createDeleteStatementsForCreateTmpTables = function (context, asyncDone)
8
8
 
9
9
  context.logger.silly('createDeleteStatements', 'createDeleteStatementsForCreateTmpTables');
10
10
 
11
- var sqlContext = {
11
+ const sqlContext = {
12
12
  context: context,
13
13
  netId: context.uniqueNetworkRequestID,
14
14
  reqId: context.uniqueRequestID,
@@ -23,6 +23,7 @@ exports._masterTableInsert = masterTableInsert;
23
23
  * @returns {*}
24
24
  */
25
25
  exports.createGetSqlStatements = function (context, asyncDone) {
26
+
26
27
  var sqlContext = context.sql = {
27
28
  netId: context.uniqueNetworkRequestID,
28
29
  reqId: context.uniqueRequestID,
@@ -54,8 +55,6 @@ exports.createGetSqlStatements = function (context, asyncDone) {
54
55
  function masterTable(sqlContext) {
55
56
  let dbSeg = sqlContext.dbSegLast;
56
57
 
57
-
58
-
59
58
  sqlContext.context.logger.debug('createGetStatements', 'masterTable');
60
59
 
61
60
  //dbSeg.sql.stmContainer = new sql.GetContainer();
@@ -36,7 +36,21 @@ function createSQLCreateStmt(dbSeg, rId, withForeignKeyProperties) {
36
36
  stmCreate.setModifiers(['local', 'temporary', dbSeg.entityType.tableStoreType]);
37
37
  stmCreate.setTableName(rId);
38
38
  stmCreate.setAs(createSelectTemplate(dbSeg, withForeignKeyProperties));
39
- stmCreate.setPostModifiers(['with no data']);
39
+ // Note about the modifier 'WITHOUT CONSTRAINT' for create temporary table:
40
+ // Is only important in HANA Cloud databases, but is also valid in HANA Service databases.
41
+ // => Characteristics of creating temporary tables:
42
+ // (1) HANA Service: Temporary tables do not have constraints (e.g. NOT-NULL for key columns)
43
+ // (2) HANA Cloud: Temporary tables have constraints, if created via this SQL:
44
+ // "create local temporary <store-type> <tab-name> as (select ... from <source-table> ...)"
45
+ // => temp. table inherits the constraints from <source-table>
46
+ // => use the modifier 'WITHOUT CONSTRAINT' to switch off this inheritance
47
+ // Test cases affected for HANA Cloud:
48
+ // - test_apps/test_authorization/test_authorization_scopes_batch.js
49
+ // - test_apps/test_authorization/test_authorization_scopes.js
50
+ // - test_apps/test_db/test_clean_dbcon_after_xsodata_processing.js
51
+ // - test_apps/test_xsodata/test_links.js
52
+ // - test_apps/test_xsodata/test_linksBatch.js
53
+ stmCreate.setPostModifiers(['with no data', 'WITHOUT CONSTRAINT']);
40
54
  return stmCreate;
41
55
  }
42
56
 
@@ -71,14 +85,14 @@ exports.createLinksStatementsCreateTmpTables = function (context, isDeleteScenar
71
85
  var dbSegToBeUpdated = context.oData.links.toBeUpdated;
72
86
 
73
87
  var sqlContext = {
74
- context: context,
88
+ context: context, // sqlContext gets parent context (includes "isHanaCloudDb")
75
89
  netId: context.uniqueNetworkRequestID,
76
90
  reqId: context.uniqueRequestID,
77
91
  rId: 1,
78
92
  dbSegToBeUpdated: dbSegToBeUpdated,
79
93
  dbSegPrincipal: context.oData.links.principal,
80
94
  dbSegDependent: context.oData.links.dependent,
81
- systemQueryParameters: context.oData.systemQueryParameters
95
+ systemQueryParameters: context.oData.systemQueryParameters,
82
96
  };
83
97
 
84
98
  if (isDeleteScenario) {
@@ -9,12 +9,10 @@ const CreateSqlErrorLog = (context, err, sql, parameters) => {
9
9
  return;
10
10
  }
11
11
 
12
- context.logger.error('SQL Exec', 'Error: \n' + err);
13
- context.logger.error('SQL Exec', 'SQL: \n' + sql);
12
+ context.logger.error('SQL Exec', 'Error: ' + err);
13
+ context.logger.error('SQL Exec', 'SQL: ' + sql);
14
+ context.logger.logSqlParameters(parameters, true, true); // force parameter logging as error
14
15
 
15
- if (parameters) {
16
- context.logger.logSqlParameters(parameters, true, true); // force parameter logging as error
17
- }
18
16
  return new SqlError(context, err);
19
17
  };
20
18
 
@@ -128,7 +126,7 @@ const executeSqlDirectly = (context, sql, cb) => {
128
126
  let startTime = context.logger.getStartTimeSql();
129
127
  client.exec(sql, (errExec, rows) => {
130
128
  context.logger.logSqlTime('exec', startTime, sql);
131
- return cb(CreateSqlErrorLog(context, errExec), rows);
129
+ return cb(CreateSqlErrorLog(context, errExec, sql), rows);
132
130
  });
133
131
  };
134
132
 
@@ -64,7 +64,8 @@ exports.select = function (context, asyncDone) {
64
64
  var container = context.sql.container;
65
65
  var stms = container.select.concat(container.selectTmp);
66
66
 
67
- async.mapSeries(stms,
67
+ async.mapSeries(
68
+ stms,
68
69
  function (item, cb) {
69
70
  try {
70
71
  execStatement(item, function (err, rows) {
@@ -85,7 +86,7 @@ exports.select = function (context, asyncDone) {
85
86
  context.logger.info('SQL Exec', 'Check maxRecords :' + maxRecords);
86
87
  if (rows.length > maxRecords) {
87
88
  // since there are possibly more
88
- return cb(new BadRequest("Too many records in result set. Use $top with to reduce the record count.", context));
89
+ return cb(new BadRequest("Too many records in result set. Use $top to reduce the record count.", context));
89
90
  }
90
91
  }
91
92
 
@@ -158,7 +159,7 @@ exports.select = function (context, asyncDone) {
158
159
  }
159
160
  }
160
161
  }
161
- context.logger.silly('dataCollector', JSON.stringify(rows.length));
162
+ context.logger.silly('GET dataCollector - row count', JSON.stringify(rows.length));
162
163
  // context.logger.silly('dataCollector', JSON.stringify(rows, null, 2));
163
164
  return cb(null, rows);
164
165
 
@@ -19,15 +19,31 @@ exports.SqlBuildHanaContext = function (context) {
19
19
  this.isNav = function (string) {
20
20
  return dbSeg._SelectedNavigations.indexOf(string) > -1;
21
21
  };
22
+ this.isHanaCloudDb = getHanaCloudDbSetting(context);
22
23
  } else {
23
24
  this.oDataNullSupport = false;
24
25
  this.isNav = false;
26
+ this.isHanaCloudDb = true; // use default Hana Cloud - Q: Is this needed? In a clone (without context, the original context should have this setting)
25
27
  }
26
28
  this.inOrderBy = false;
27
29
  this.table = null;
28
30
  this.noAlias = null; // only used in a cloned SqlBuildHanaContext to avoid using aliases in orderby clauses
29
31
  };
30
32
 
33
+ function getHanaCloudDbSetting(context) {
34
+
35
+ // 06/2022: All used SQL from Hana Cloud DB work also on Hana Service DB,
36
+ // i.e. take that as default; but that may change in the future!
37
+ let isHanaCloudDb = true;
38
+
39
+ if (context.batchContext && context.batchContext.parentContext && context.batchContext.parentContext.db && context.batchContext.parentContext.db.isHanaCloudDb !== undefined) {
40
+ isHanaCloudDb = context.batchContext.parentContext.db.isHanaCloudDb; // batch request context
41
+ } else if (context.db && context.db.isHanaCloudDb !== undefined) {
42
+ isHanaCloudDb = context.db.isHanaCloudDb; // regular request context
43
+ }
44
+ return isHanaCloudDb;
45
+ }
46
+
31
47
  exports.SqlBuildHanaContext.prototype.clone = function () {
32
48
  var c = new exports.SqlBuildHanaContext();
33
49
  c.oDataNullSupport = this.oDataNullSupport;
@@ -35,6 +51,7 @@ exports.SqlBuildHanaContext.prototype.clone = function () {
35
51
  c.table = this.table; // used in Property.prototype.toSqlHana
36
52
  c.noAlias = this.noAlias;
37
53
  c.inOrderBy = this.inOrderBy;
54
+ c.isHanaCloudDb = this.isHanaCloudDb; // keep this setting in any clone!!!
38
55
  return c;
39
56
  };
40
57
 
@@ -446,7 +463,7 @@ Create.prototype.toSqlHana = function (context, parameter) {
446
463
  for (i = 0; i < this.modifiers.length; i++) {
447
464
  sql += ' ' + this.modifiers[i];
448
465
  }
449
- sql += '\ntable ';
466
+ sql += ' table ';
450
467
  sql += '"' + this.table + '" ';
451
468
  if (this.as) {
452
469
  sql += '\nas ( ';
@@ -1145,6 +1162,78 @@ Update.prototype.addWhereKeyValuePairs = function (keyValuePairs) {
1145
1162
 
1146
1163
 
1147
1164
  Update.prototype.toSqlHana = function (context, parameter) {
1165
+
1166
+ // 'context.isHanaCloudDb': is set in SQL context (hence 'context' here) to member 'isHanaCloudDb';
1167
+ // check creation of 'new SqlBuildHanaContext' above
1168
+ let isHanaCloudDb = (context.isHanaCloudDb) ? context.isHanaCloudDb : true; // Hana Cloud as default
1169
+ return (isHanaCloudDb) ? this.getHANACloudUpdateSql(context, parameter) : this.getHANAServiceUpdateSql(context, parameter);
1170
+ };
1171
+
1172
+
1173
+ Update.prototype.getHANACloudUpdateSql = function(context, parameter) {
1174
+
1175
+ var sql = '';
1176
+
1177
+ sql += 'MERGE INTO ';
1178
+ if (this.update.table.substr(0, 1) === '#') {
1179
+ sql += ' "' + this.update.table + '" ';
1180
+ if (this.update.alias) {
1181
+ sql += ' "' + this.update.alias + '"';
1182
+ }
1183
+ } else {
1184
+
1185
+ if (this.update.schema) {
1186
+ sql += '"' + this.update.schema + '".';
1187
+ }
1188
+ sql += '"' + this.update.table + '"';
1189
+ if (this.update.alias) {
1190
+ sql += ' "' + this.update.alias + '"';
1191
+ }
1192
+ }
1193
+
1194
+ sql += ' USING ';
1195
+ if (this.from.table.substr(0, 1) === '#') {
1196
+ sql += ' "' + this.from.table + '" ';
1197
+ if (this.from.alias) {
1198
+ sql += ' "' + this.from.alias + '"';
1199
+ }
1200
+ } else {
1201
+ if (this.from.schema) {
1202
+ sql += '"' + this.from.schema + '".';
1203
+ }
1204
+ sql += '"' + this.from.table + '"';
1205
+ if (this.from.alias) {
1206
+ sql += ' "' + this.from.alias + '"';
1207
+ }
1208
+ }
1209
+
1210
+ if (this.whereAnded.length > 0) {
1211
+ sql += ' ON ';
1212
+ for (let i = 0; i < this.whereAnded.length; i++) {
1213
+ sql += (i === 0 ? '' : ' and ');
1214
+ sql += '(';
1215
+ sql += this.whereAnded[i].toSqlHana(context, parameter);
1216
+ sql += ')';
1217
+ }
1218
+ }
1219
+
1220
+ sql += ' WHEN MATCHED THEN UPDATE ';
1221
+
1222
+ sql += ' SET ';
1223
+ for (let i = 0; i < this.copyFromTo.length; i++) {
1224
+ var cft = this.copyFromTo[i];
1225
+ sql += (i === 0 ? '' : ', ');
1226
+
1227
+ sql += cft.to.toSqlHana(context, parameter, { withoutTable: true });
1228
+ sql += '=';
1229
+ sql += cft.from.toSqlHana(context, parameter);
1230
+ }
1231
+
1232
+ return sql;
1233
+ };
1234
+
1235
+
1236
+ Update.prototype.getHANAServiceUpdateSql = function(context, parameter) {
1148
1237
  var i;
1149
1238
 
1150
1239
  var sql = '';
@@ -36,7 +36,8 @@ const execStmsDirectlyNoResult = function (context, stmsInput, asyncDone) {
36
36
  const execStmsAsPreparedNoResult = function (context, stmsInput, asyncDone) {
37
37
  const stms = Array.isArray(stmsInput) ? stmsInput : [stmsInput];
38
38
  context.logger.silly('dataCollector', 'execParallelNoResult');
39
- async.mapSeries(stms,
39
+ async.mapSeries(
40
+ stms,
40
41
  function (item, cb) {
41
42
  try {
42
43
  var p = [];
@@ -58,7 +59,8 @@ const execStmsAsPreparedNoResult = function (context, stmsInput, asyncDone) {
58
59
  const execStmsAsPrepared = function (context,stmsInput, asyncDone) {
59
60
  const stms = Array.isArray(stmsInput) ? stmsInput : [stmsInput];
60
61
  context.logger.silly('dataCollector', 'execParallelNoResult');
61
- async.mapSeries(stms,
62
+ async.mapSeries(
63
+ stms,
62
64
  function (item, cb) {
63
65
  try {
64
66
  var p = [];
@@ -180,7 +180,7 @@ ResourcePathReader.prototype.consumeFollowerSegment = function consumeFollowerSe
180
180
 
181
181
  if (prevDbSegment.isCollection === true) {
182
182
  //E.g. /Teams/invalid
183
- throw new Http400_BadRequest("The segment 'invalid' at position 6 in the request URI is not valid. Since the previous segment refers to a collection, the only supported value for the next segment is '$count'.");
183
+ throw new Http400_BadRequest(`The segment '${segment.identifier}' in the request URI is not valid. Since the previous segment refers to a collection, the only supported value for the next segment is '$count'.`);
184
184
  }
185
185
 
186
186
  //check for primitive property
@@ -16,9 +16,6 @@ function InternalError(message, context, cause) {
16
16
  this.context = context;
17
17
  if (cause && cause.stack && context && context.logger) {
18
18
  context.logger.error('InternalError', cause.stack);
19
- if (cause.cause && cause.cause.message) {
20
- context.logger.error('InternalError', cause.cause.message);
21
- }
22
19
  }
23
20
  }
24
21
 
@@ -12,10 +12,14 @@ module.exports = SqlError;
12
12
  * @param information
13
13
  * @constructor
14
14
  */
15
- function SqlError(context, cause, information) {
15
+ function SqlError(context, cause, information) {
16
16
  const errorText = "Error while executing a DB query";
17
17
  InternalError.call(this, errorText, context, cause);
18
- this.message = errorText;
18
+ if (cause && cause.message) {
19
+ this.message = `${errorText}: ${cause.message}`;
20
+ } else {
21
+ this.message = errorText;
22
+ }
19
23
  this.information = information;
20
24
  }
21
25
 
@@ -20,7 +20,10 @@ function XsODataError(message, cause) {
20
20
  }
21
21
 
22
22
  XsODataError.prototype.toString = function () {
23
- return this.stack.toString();
23
+ let ret = "";
24
+ ret = this.stack.toString();
25
+ ret += (this.cause) ? ("\n" + this.cause + "\n") : "";
26
+ return ret;
24
27
  };
25
28
 
26
29
  util.inherits(XsODataError, Error);
@@ -109,25 +109,27 @@ function printSqlData(array) {
109
109
 
110
110
  function printSqlParameters(array) {
111
111
  let ret = '';
112
- for (const value of array) {
113
- if (ret.length) {
114
- ret += ',';
115
- }
116
-
117
- if (typeof value === 'string' && value.length > XSODATA_LOG_SQL_PARAM_VALUE_SIZE) {
118
- ret += value.substr(0, XSODATA_LOG_SQL_PARAM_VALUE_SIZE) + '<' + value.length + '>';
119
- continue;
120
- }
121
- if (Array.isArray(value) && value.length > XSODATA_LOG_SQL_PARAM_ARRAY_SIZE) {
122
- ret += value.slice(0, XSODATA_LOG_SQL_PARAM_VALUE_SIZE);
123
- continue;
124
- }
125
- if (Buffer.isBuffer(value) && value.length > XSODATA_LOG_SQL_PARAM_VALUE_SIZE) {
126
- ret += value.slice(0, XSODATA_LOG_SQL_PARAM_VALUE_SIZE).toString('hex') + '.LEN:' + value.length;
127
- continue;
112
+ if (array && Array.isArray(array)) {
113
+ for (const value of array) {
114
+ if (ret.length) {
115
+ ret += ',';
116
+ }
117
+
118
+ if (typeof value === 'string' && value.length > XSODATA_LOG_SQL_PARAM_VALUE_SIZE) {
119
+ ret += value.substr(0, XSODATA_LOG_SQL_PARAM_VALUE_SIZE) + '<' + value.length + '>';
120
+ continue;
121
+ }
122
+ if (Array.isArray(value) && value.length > XSODATA_LOG_SQL_PARAM_ARRAY_SIZE) {
123
+ ret += value.slice(0, XSODATA_LOG_SQL_PARAM_VALUE_SIZE);
124
+ continue;
125
+ }
126
+ if (Buffer.isBuffer(value) && value.length > XSODATA_LOG_SQL_PARAM_VALUE_SIZE) {
127
+ ret += value.slice(0, XSODATA_LOG_SQL_PARAM_VALUE_SIZE).toString('hex') + '.LEN:' + value.length;
128
+ continue;
129
+ }
130
+
131
+ ret += value;
128
132
  }
129
-
130
- ret += value;
131
133
  }
132
134
 
133
135
  return ret;
@@ -236,6 +238,18 @@ Logger.prototype.logSqlCommand = function (sql) {
236
238
  }
237
239
  };
238
240
 
241
+ Logger.prototype.logSqlParameters = function (parameters, force, asError) {
242
+
243
+ if (XSODATA_LOG_SQL_COMMAND >= 2 && (XSODATA_LOG_SQL_PARAMETERS >= 2 || force || asError)) {
244
+ let out = printSqlParameters(parameters);
245
+ if (asError) {
246
+ this.logger.error(this.getKey() + 'SQL param: ' + out);
247
+ } else {
248
+ this.logger.debug(this.getKey() + 'SQL param: ' + out);
249
+ }
250
+ }
251
+ };
252
+
239
253
  Logger.prototype.getStartTimeSql = function () {
240
254
  if (XSODATA_LOG_SQL_TIME || XSODATA_FORCE_LOG_LONG_SQL) {
241
255
  return process.hrtime();
@@ -293,16 +307,4 @@ Logger.prototype.logRequestTime = function (text, startTime, url) {
293
307
  }
294
308
  };
295
309
 
296
- Logger.prototype.logSqlParameters = function (parameters, force, asError) {
297
-
298
- if (XSODATA_LOG_SQL_COMMAND >= 2 && (XSODATA_LOG_SQL_PARAMETERS >= 2 || force || asError)) {
299
- let out = printSqlParameters(parameters);
300
- if (asError) {
301
- this.logger.error(this.getKey() + 'SQL param: ' + out);
302
- } else {
303
- this.logger.debug(this.getKey() + 'SQL param: ' + out);
304
- }
305
- }
306
- };
307
-
308
310
  module.exports = Logger;
package/lib/xsodata.js CHANGED
@@ -215,7 +215,7 @@ exports.ODataHandler.prototype.createRequestChain = function (context) {
215
215
  * @param { module:configuration.RequestOptions } requestOptions @see module:configuration.RequestOptions
216
216
  * @param applicationDone Callback call after request is processed. Signature ( err : Object, context : Object )
217
217
  */
218
- exports.ODataHandler.prototype.processRequest = function process(request, response, requestOptions, applicationDone) {
218
+ exports.ODataHandler.prototype.processRequest = function(request, response, requestOptions, applicationDone) {
219
219
  var networkContext;
220
220
  var context;
221
221
  var baseMeasurement;
@@ -241,6 +241,7 @@ exports.ODataHandler.prototype.processRequest = function process(request, respon
241
241
  context.logger.info('xsodata', 'process url: ' + request.url);
242
242
  context.logger.info('xsodata', 'version: ' + packageJson.version);
243
243
  context.logger.info('xsodata', 'uriPrefix: ' + context.uriPrefix);
244
+ context.logger.info('xsodata', 'node version: ' + process.version);
244
245
 
245
246
  context.url = request.url;
246
247
  context.request = simpleRequest.createRequest(request, context);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sap/xsodata",
3
- "version": "7.5.5",
3
+ "version": "8.0.0",
4
4
  "description": "Expose data from a HANA database as OData V2 service with help of .xsodata files.",
5
5
  "main": "index.js",
6
6
  "license": "SEE LICENSE IN developer-license-3.1.txt",
@@ -9,13 +9,14 @@
9
9
  "serve-debug": "node --inspect-brk test_apps/server",
10
10
  "pretest": "npm -g ls --depth=0 && npm ls --depth=0",
11
11
  "lint": "jshint .",
12
- "all-tests-jenkins": "node ./node_modules/mocha/bin/_mocha --timeout 20000 'test/**/test*.js' 'test_apps/test_**/**/test*.js'",
13
- "all-tests-cv": "node ./node_modules/mocha/bin/_mocha --timeout 20000 'test_apps/test_cv/**/test*.js'",
14
- "scenario-tests": "node ./node_modules/mocha/bin/_mocha --timeout 20000 test_apps/test_**/**/test*.js",
12
+ "all-tests-jenkins": "node ./node_modules/mocha/bin/_mocha --no-color --timeout 20000 'test/**/test*.js' 'test_apps/test_**/**/test*.js'",
13
+ "all-tests-cv": "node ./node_modules/mocha/bin/_mocha --no-color --timeout 20000 'test_apps/test_cv/**/test*.js'",
14
+ "scenario-tests": "node ./node_modules/mocha/bin/_mocha --no-color --timeout 20000 test_apps/test_**/**/test*.js",
15
+ "all-tests-synonym": "node ./node_modules/mocha/bin/_mocha --no-color --timeout 20000 test_apps/test_synonyms/test_*.js",
15
16
  "unit-tests": "node ./node_modules/mocha/bin/_mocha test/**/test*.js",
16
17
  "cover-unit-tests": "nyc --reporter=text --reporter=html --all --include lib/ npm run unit-tests",
17
- "cover-scenario-tests": "nyc --reporter=text --reporter=html --all --include lib/ --check-coverage true --lines 80 --branches 69 --functions 87 --statements 80 npm run scenario-tests",
18
- "cover-jenkins": "nyc --reporter=text --reporter=html --all --include lib/ --check-coverage true --lines 80 --branches 69 --functions 87 --statements 80 npm run all-tests-jenkins",
18
+ "cover-scenario-tests": "nyc --reporter=text --reporter=html --all --include lib/ --check-coverage true --lines 80 --branches 68 --functions 86 --statements 80 npm run scenario-tests",
19
+ "cover-jenkins": "nyc --reporter=text --reporter=html --all --include lib/ --check-coverage true --lines 80 --branches 68 --functions 86 --statements 80 npm run all-tests-jenkins",
19
20
  "report": "node ./node_modules/mocha/bin/_mocha test/**/test*.js test_apps/test_**/**/test*.js --reporter mocha-simple-html-reporter --reporter-options output=report_all.html",
20
21
  "report-unit-tests": "node ./node_modules/mocha/bin/_mocha test/**/test*.js --reporter mocha-simple-html-reporter --reporter-options output=report_unit.html",
21
22
  "report-scenario-tests": "node ./node_modules/mocha/bin/_mocha test_apps/test_**/**/test*.js --reporter mocha-simple-html-reporter --reporter-options output=report_scenario.html",
@@ -45,12 +46,12 @@
45
46
  ".npmignore"
46
47
  ],
47
48
  "dependencies": {
48
- "@sap/xsenv": "3.2.1",
49
+ "@sap/xsenv": "3.3.2",
49
50
  "@sap/xssec": "3.2.13",
50
- "async": "3.2.3",
51
- "big.js": "6.1.1",
52
- "body-parser": "1.19.2",
53
- "hdb": "0.19.1",
51
+ "async": "3.2.4",
52
+ "big.js": "6.2.1",
53
+ "body-parser": "1.20.0",
54
+ "hdb": "0.19.4",
54
55
  "lodash": "4.17.21",
55
56
  "negotiator": "0.6.3",
56
57
  "rwlock": "5.0.0",
@@ -63,7 +64,7 @@
63
64
  "@sap/hana-client": "2.10.20",
64
65
  "chai": "4.2.0",
65
66
  "expect": "1.20.2",
66
- "filter-node-package": "=2.2.0",
67
+ "filter-node-package": "3.0.0",
67
68
  "jison": "0.4.18",
68
69
  "jshint": "2.13.4",
69
70
  "mocha": "9.2.2",
@@ -72,6 +73,6 @@
72
73
  "nyc": "^15.1.0",
73
74
  "pegjs": "0.10.0",
74
75
  "sinon": "6.1.4",
75
- "xml2js": "0.4.19"
76
+ "xml2js": "0.4.23"
76
77
  }
77
78
  }