@sap/xsodata 7.4.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 +25 -0
- package/lib/db/connect.js +42 -4
- package/lib/db/dbSegment.js +87 -1
- package/lib/model/entityType.js +12 -0
- package/lib/model/metadataReader.js +186 -53
- package/lib/model/xsodataReader.js +1 -1
- package/lib/processor/batchProcessor.js +7 -7
- package/lib/processor/processor.js +4 -4
- package/lib/serializer/json.js +37 -8
- package/lib/sql/createDeleteLinksStatements.js +8 -1
- package/lib/sql/createDeleteStatements.js +1 -1
- package/lib/sql/createGetStatements.js +24 -8
- package/lib/sql/createLinksSQLStatements_1_n.js +17 -3
- package/lib/sql/dataCollector2.js +4 -6
- package/lib/sql/dataCollectorGet.js +4 -3
- package/lib/sql/sqlStatement.js +93 -3
- package/lib/sql/statementProcessor.js +4 -2
- package/lib/uri/resourcePathParser.js +4 -1
- package/lib/utils/errors/internalError.js +0 -3
- package/lib/utils/errors/sqlError.js +6 -2
- package/lib/utils/errors/xsODataError.js +4 -1
- package/lib/utils/logger.js +35 -32
- package/lib/utils/typedObjects.js +8 -0
- package/lib/xsodata.js +2 -1
- package/package.json +23 -27
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,31 @@ 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
|
+
|
|
17
|
+
## [7.5.5] - 2022-04-21
|
|
18
|
+
|
|
19
|
+
* On calc view: Stable sort order with $skip and $top usage for subsequent requests via "order by" of key fields in SQL statement
|
|
20
|
+
|
|
21
|
+
## [7.5.4] - 2022-04-21
|
|
22
|
+
|
|
23
|
+
Build infrastructure fix
|
|
24
|
+
|
|
25
|
+
## [7.5.1] - [7.5.3] - 2022-04-20
|
|
26
|
+
|
|
27
|
+
Build infrastructure fixes
|
|
28
|
+
|
|
29
|
+
## [7.5.0] - 2022-03-22
|
|
30
|
+
|
|
31
|
+
* Support of node version 16
|
|
32
|
+
* Update module dependencies
|
|
33
|
+
* Update of system query options and xsodata-settings help text
|
|
34
|
+
* Minor changes: Error reporting
|
|
35
|
+
|
|
11
36
|
## [7.4.5] - 2021-12-15
|
|
12
37
|
|
|
13
38
|
* Suppress $metadata annotation <code>sap:aggregation-role="dimension"</code> on calculation view property if it is used as description property referenced by annotation <code>sap:text</code> by another property of the calulation view. The annotation is only supressed if the corresponding xsodata-file has <code>settings</code> containing this: <code>noDimensionAnnoOnTextProperty true;</code>
|
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);
|
|
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
|
});
|
package/lib/db/dbSegment.js
CHANGED
|
@@ -92,6 +92,13 @@ function DbSegment(kind, entityType, nr) {
|
|
|
92
92
|
* @type {Array<String>}
|
|
93
93
|
*/
|
|
94
94
|
this._SelectedPropertiesOrdered = null;
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* For CV: (real) key names with alias
|
|
98
|
+
* @type {Array<String,string>}
|
|
99
|
+
*/
|
|
100
|
+
this._aliasedKeyPropertiesOnCalcView = [];
|
|
101
|
+
|
|
95
102
|
/**
|
|
96
103
|
* Store names existing of navigation properties
|
|
97
104
|
* @type {Array<String>}
|
|
@@ -593,6 +600,50 @@ DbSegment.prototype.getKeyProperties0123ForSelectAs0123 = function (noTable) {
|
|
|
593
600
|
return ret;
|
|
594
601
|
};
|
|
595
602
|
|
|
603
|
+
DbSegment.prototype.getKeyProperties0123ForSelectOnCalcViewAs0123 = function (noTable) {
|
|
604
|
+
let ret = [];
|
|
605
|
+
let keyNames;
|
|
606
|
+
let keyName;
|
|
607
|
+
let property;
|
|
608
|
+
let selectProperty;
|
|
609
|
+
let propertyType;
|
|
610
|
+
let i;
|
|
611
|
+
let j = -1;
|
|
612
|
+
|
|
613
|
+
keyNames = this.entityType.keyNamesOrdered;
|
|
614
|
+
|
|
615
|
+
for (i = 0; i < keyNames.length; i++) {
|
|
616
|
+
keyName = keyNames[i];
|
|
617
|
+
property = this.entityType.propertiesMap[keyName];
|
|
618
|
+
selectProperty = null;
|
|
619
|
+
|
|
620
|
+
if (property.KIND !== EntityType.entityKind.inputParameters) {
|
|
621
|
+
j = j + 1; // for real key properties use numbering: 0,1,2, ... (skip CV input params)
|
|
622
|
+
propertyType = property.aggregate ? null : property.DATA_TYPE_NAME;
|
|
623
|
+
selectProperty = new sqlStatement.SelectProperty(noTable ? null : this._Alias, keyName, propertyType, j.toString());
|
|
624
|
+
ret.push(selectProperty);
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
return ret;
|
|
628
|
+
};
|
|
629
|
+
|
|
630
|
+
DbSegment.prototype.setAliasedKeyPropertiesOnCalcView = function(selectProperties) {
|
|
631
|
+
this._aliasedKeyPropertiesOnCalcView = selectProperties;
|
|
632
|
+
};
|
|
633
|
+
|
|
634
|
+
DbSegment.prototype.hasAliasedKeyPropertiesOnCalcView = function() {
|
|
635
|
+
return this._aliasedKeyPropertiesOnCalcView.length !== 0;
|
|
636
|
+
};
|
|
637
|
+
|
|
638
|
+
DbSegment.prototype.getAliasedKeyPropertyOnCalcView = function(propertyName) {
|
|
639
|
+
for (let i = 0; i < this._aliasedKeyPropertiesOnCalcView.length; i++) {
|
|
640
|
+
if (this._aliasedKeyPropertiesOnCalcView[i].property === propertyName) {
|
|
641
|
+
return this._aliasedKeyPropertiesOnCalcView[i];
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
return null;
|
|
645
|
+
};
|
|
646
|
+
|
|
596
647
|
/**
|
|
597
648
|
* Returns a selectProperty for usage in the select part of sql statements
|
|
598
649
|
* The property are named "0","1","2",... and have as type the type of the corresponding key ( ordered by position
|
|
@@ -649,7 +700,30 @@ DbSegment.prototype.getKeyProperties0123ForOrderBy = function (noTable) {
|
|
|
649
700
|
return ret;
|
|
650
701
|
};
|
|
651
702
|
|
|
652
|
-
DbSegment.prototype.
|
|
703
|
+
DbSegment.prototype.getKeyProperties0123ForOrderByCalcView = function (noTable) {
|
|
704
|
+
let ret = [];
|
|
705
|
+
let keyName;
|
|
706
|
+
let property;
|
|
707
|
+
let j = -1;
|
|
708
|
+
|
|
709
|
+
let keyNames = this.entityType.keyNamesOrdered;
|
|
710
|
+
|
|
711
|
+
for (let i = 0; i < keyNames.length; i++) {
|
|
712
|
+
|
|
713
|
+
keyName = keyNames[i];
|
|
714
|
+
property = this.entityType.propertiesMap[keyName];
|
|
715
|
+
|
|
716
|
+
if (property.KIND !== EntityType.entityKind.inputParameters) {
|
|
717
|
+
j = j + 1;
|
|
718
|
+
let newSortOrder = new typedObjects.SortOrder(new typedObjects.Property(j.toString(), noTable ? null : this._Alias), 'ASC');
|
|
719
|
+
newSortOrder.setPropertyName(property.COLUMN_NAME);
|
|
720
|
+
ret.push(newSortOrder);
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
return ret;
|
|
724
|
+
};
|
|
725
|
+
|
|
726
|
+
DbSegment.prototype.getKeyPropertiesNotSelectedForSelect = function (noTable = undefined, inputParameters = null) {
|
|
653
727
|
let ret = [];
|
|
654
728
|
let keyNames;
|
|
655
729
|
let i;
|
|
@@ -659,6 +733,18 @@ DbSegment.prototype.getKeyPropertiesNotSelectedForSelect = function (noTable) {
|
|
|
659
733
|
|
|
660
734
|
keyNames = this.entityType.keyNamesOrdered;
|
|
661
735
|
|
|
736
|
+
// remove input params from key list
|
|
737
|
+
if (inputParameters !== null) {
|
|
738
|
+
for (let inputParameterName in inputParameters) {
|
|
739
|
+
if (inputParameters.hasOwnProperty(inputParameterName)) {
|
|
740
|
+
let indexKeyName = keyNames.indexOf(inputParameterName);
|
|
741
|
+
if (indexKeyName > -1) {
|
|
742
|
+
keyNames.splice(indexKeyName, 1);
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
|
|
662
748
|
for (i = 0; i < keyNames.length; i++) {
|
|
663
749
|
keyName = keyNames[i];
|
|
664
750
|
key = this.entityType.propertiesMap[keyName];
|
package/lib/model/entityType.js
CHANGED
|
@@ -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
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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
|
-
|
|
108
|
-
|
|
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
|
-
|
|
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
|
-
|
|
155
|
-
|
|
156
|
-
'
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
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
|
-
|
|
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
|
-
|
|
241
|
-
|
|
242
|
-
'
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
469
|
-
context.logger.
|
|
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
|
-
|
|
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
|
|
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,
|
|
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
|
-
//
|
|
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');
|