@sap/xsodata 7.5.0 → 7.5.5
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 +12 -0
- package/lib/db/dbSegment.js +87 -1
- package/lib/serializer/json.js +37 -8
- package/lib/sql/createGetStatements.js +23 -6
- package/lib/sql/sqlStatement.js +3 -2
- package/lib/utils/logger.js +3 -2
- package/lib/utils/typedObjects.js +8 -0
- package/package.json +9 -14
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,18 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/).
|
|
|
8
8
|
|
|
9
9
|
## Unreleased
|
|
10
10
|
|
|
11
|
+
## [7.5.5] - 2022-04-21
|
|
12
|
+
|
|
13
|
+
* On calc view: Stable sort order with $skip and $top usage for subsequent requests via "order by" of key fields in SQL statement
|
|
14
|
+
|
|
15
|
+
## [7.5.4] - 2022-04-21
|
|
16
|
+
|
|
17
|
+
Build infrastructure fix
|
|
18
|
+
|
|
19
|
+
## [7.5.1] - [7.5.3] - 2022-04-20
|
|
20
|
+
|
|
21
|
+
Build infrastructure fixes
|
|
22
|
+
|
|
11
23
|
## [7.5.0] - 2022-03-22
|
|
12
24
|
|
|
13
25
|
* Support of node version 16
|
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/serializer/json.js
CHANGED
|
@@ -134,9 +134,24 @@ JsonBuilder.prototype.createAbsUrl = function (dbSeg, row) {
|
|
|
134
134
|
// although no input parameter given from client the application MUST have filled the input parameters as key (undefined via db is not possible)
|
|
135
135
|
value = typeConverter.serializeDbValueToUriLiteral(row[keyProperty.COLUMN_NAME], keyProperty);
|
|
136
136
|
} else {
|
|
137
|
-
|
|
137
|
+
if (dbSeg.hasAliasedKeyPropertiesOnCalcView()) {
|
|
138
|
+
let foundKeyFieldAlias = null;
|
|
139
|
+
for (let i = 0; i < dbSeg._aliasedKeyPropertiesOnCalcView.length; i++) {
|
|
140
|
+
if (dbSeg._aliasedKeyPropertiesOnCalcView[i].property === keyProperty.COLUMN_NAME &&
|
|
141
|
+
dbSeg._aliasedKeyPropertiesOnCalcView[i].table === dbSeg._Alias) {
|
|
142
|
+
foundKeyFieldAlias = dbSeg._aliasedKeyPropertiesOnCalcView[i].alias;
|
|
143
|
+
break; // there must not be more than one entry!
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
if (foundKeyFieldAlias !== null && row[foundKeyFieldAlias] !== undefined) {
|
|
147
|
+
value = typeConverter.serializeDbValueToUriLiteral(row[foundKeyFieldAlias], keyProperty);
|
|
148
|
+
} else {
|
|
149
|
+
value = null;
|
|
150
|
+
}
|
|
151
|
+
} else {
|
|
152
|
+
value = null;
|
|
153
|
+
}
|
|
138
154
|
}
|
|
139
|
-
|
|
140
155
|
} else {
|
|
141
156
|
value = typeConverter.serializeDbValueToUriLiteral(row[keyProperty.COLUMN_NAME], keyProperty);
|
|
142
157
|
}
|
|
@@ -167,13 +182,27 @@ JsonBuilder.prototype.serializeRow = function (context, dbSeg, row, converter) {
|
|
|
167
182
|
ret.__metadata.etag = 'W/"' + row.__etag + '"';
|
|
168
183
|
}
|
|
169
184
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
}
|
|
185
|
+
if (!dbSeg.hasAliasedKeyPropertiesOnCalcView()) {
|
|
186
|
+
dbSeg.getSelectedPropsWithGenKey().forEach(function toConverter(property, index) {
|
|
187
|
+
ret[property] = converter[index](row[property]);
|
|
188
|
+
});
|
|
189
|
+
} else {
|
|
190
|
+
let selectedProperties = dbSeg.getSelectedPropsWithGenKey();
|
|
191
|
+
for (let i = 0; i < selectedProperties.length; i++) {
|
|
192
|
+
if (!row[selectedProperties[i]]) {
|
|
193
|
+
let aliasedKeyProperty = dbSeg.getAliasedKeyPropertyOnCalcView(selectedProperties[i]);
|
|
194
|
+
if (aliasedKeyProperty !== null && aliasedKeyProperty.alias) {
|
|
195
|
+
ret[selectedProperties[i]] = converter[i](row[aliasedKeyProperty.alias]);
|
|
196
|
+
} else {
|
|
197
|
+
ret[selectedProperties[i]] = converter[i](row[selectedProperties[i]]); // keeps null
|
|
198
|
+
}
|
|
199
|
+
} else {
|
|
200
|
+
ret[selectedProperties[i]] = converter[i](row[selectedProperties[i]]);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
175
204
|
|
|
176
|
-
for (i = 0; i < dbSeg._SelectedNavigations.length; i++) {
|
|
205
|
+
for (let i = 0; i < dbSeg._SelectedNavigations.length; i++) {
|
|
177
206
|
var sn = dbSeg._SelectedNavigations[i];
|
|
178
207
|
var navDbSeg = dbSeg.getRelevantNavigationSegments()[sn];
|
|
179
208
|
if (navDbSeg && navDbSeg.isExpand) {
|
|
@@ -191,8 +191,20 @@ function masterTableSimpleSelect(sqlContext, countFallback) {
|
|
|
191
191
|
}
|
|
192
192
|
} else {
|
|
193
193
|
if (!isCount) {
|
|
194
|
-
if (!(dbSeg.entityType.kind === EntityType.entityKind.calculationView &&
|
|
194
|
+
if (!(dbSeg.entityType.kind === EntityType.entityKind.calculationView &&
|
|
195
|
+
dbSeg.entityType._entityType.parameters &&
|
|
196
|
+
dbSeg.entityType._entityType.parameters.viaKey === true)) {
|
|
195
197
|
select.addSortOrders(dbSeg.getKeyProperties0123ForOrderBy(true));
|
|
198
|
+
} else {
|
|
199
|
+
let orderByProperties = dbSeg.getKeyProperties0123ForOrderByCalcView(true);
|
|
200
|
+
for (let i= 0; i < select.select.length; i++) {
|
|
201
|
+
for (let j= 0; j < orderByProperties.length; j++) {
|
|
202
|
+
if (orderByProperties[j].getPropertyName() === select.select[i].property) {
|
|
203
|
+
select.select[i].alias = orderByProperties[j].expression.property;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
select.addSortOrders(orderByProperties);
|
|
196
208
|
}
|
|
197
209
|
}
|
|
198
210
|
}
|
|
@@ -384,16 +396,21 @@ function masterTableSimpleSelect(sqlContext, countFallback) {
|
|
|
384
396
|
stm.addSelects(dbSeg.getAllSelectedPropertiesConsideringAggregates());
|
|
385
397
|
} else {
|
|
386
398
|
if (dbSeg.entityType.kind === EntityType.entityKind.calculationView && dbSeg.entityType._entityType.parameters && dbSeg.entityType._entityType.parameters.viaKey === true) {
|
|
387
|
-
// stm.addSelect(dbSeg.getKeyProperties0123ForSelectAs0123());
|
|
388
399
|
// Adding properties from getKeyProperties0123ForSelectAs0123 is not needed since
|
|
389
|
-
// if (in future)
|
|
390
|
-
// that's because calcview don't return their input parameter in the
|
|
400
|
+
// if (in future) an association via input parameter is used then the input parameter values must be added via ? into the sql command
|
|
401
|
+
// that's because calcview don't return their input parameter in the result set
|
|
391
402
|
let tmp = dbSeg.getPropertiesForSelectCollectInputParameters();
|
|
403
|
+
|
|
392
404
|
// since the input parameters may not be returned from the calcview the parameters are merged after the select
|
|
393
|
-
// from "calcview2" into the result.
|
|
405
|
+
// from "calcview2" into the result. // Q: What does that mean: "calcview2"?
|
|
394
406
|
stm.byPassInputs = tmp.input;
|
|
395
407
|
stm.addSelects(tmp.selects);
|
|
396
|
-
|
|
408
|
+
|
|
409
|
+
stm.addSelects(dbSeg.getKeyPropertiesNotSelectedForSelect(undefined, dbSeg.entityType.inputParameters));
|
|
410
|
+
|
|
411
|
+
// keep aliased key properties
|
|
412
|
+
let selectProperties = dbSeg.getKeyProperties0123ForSelectOnCalcViewAs0123();
|
|
413
|
+
dbSeg.setAliasedKeyPropertiesOnCalcView(selectProperties);
|
|
397
414
|
} else {
|
|
398
415
|
stm.addSelect(dbSeg.getKeyProperties0123ForSelectAs0123());
|
|
399
416
|
stm.addSelects(dbSeg.getPropertiesForSelect());
|
package/lib/sql/sqlStatement.js
CHANGED
|
@@ -620,7 +620,6 @@ function Select() {
|
|
|
620
620
|
this.offset = null;
|
|
621
621
|
this.fallbackStatement = null;
|
|
622
622
|
this.addCalcViewHint = false;
|
|
623
|
-
|
|
624
623
|
this.customData = {}; // restriction on the count of selected records
|
|
625
624
|
}
|
|
626
625
|
|
|
@@ -661,6 +660,9 @@ Select.prototype.toSqlHana = function (context, parameter) {
|
|
|
661
660
|
sql += this.select[i].toSqlHana(context, parameter);
|
|
662
661
|
}
|
|
663
662
|
|
|
663
|
+
// note: $orderby properties for CV that are NOT in select list result in SQL error
|
|
664
|
+
// but: That was never supported by this lib!
|
|
665
|
+
|
|
664
666
|
sql += '\nfrom ';
|
|
665
667
|
|
|
666
668
|
sql += this.froms.map(function (from) {
|
|
@@ -998,7 +1000,6 @@ Select.prototype.addSelects = function (newSelects) {
|
|
|
998
1000
|
}
|
|
999
1001
|
}
|
|
1000
1002
|
}
|
|
1001
|
-
|
|
1002
1003
|
if (add) {
|
|
1003
1004
|
this.select.push(newSelect);
|
|
1004
1005
|
}
|
package/lib/utils/logger.js
CHANGED
|
@@ -43,8 +43,9 @@ let XSODATA_FORCE_LOG_LONG_SQL = process.env.XSODATA_FORCE_LOG_LONG_SQL || 2;
|
|
|
43
43
|
// 0-1: no SQL query parameters are logged
|
|
44
44
|
// 2: SQL query parameters are logged
|
|
45
45
|
let XSODATA_LOG_SQL_PARAMETERS = process.env.XSODATA_LOG_SQL_PARAMETERS || 2;
|
|
46
|
-
|
|
47
|
-
let
|
|
46
|
+
// XSODATA_LOG_SQL_PARAM_VALUE_SIZE: sets the length of the parameter value loggged out
|
|
47
|
+
let XSODATA_LOG_SQL_PARAM_VALUE_SIZE = process.env.XSODATA_LOG_SQL_PARAM_VALUE_SIZE || 80;
|
|
48
|
+
let XSODATA_LOG_SQL_PARAM_ARRAY_SIZE = process.env.XSODATA_LOG_SQL_PARAM_ARRAY_SIZE || 80;
|
|
48
49
|
|
|
49
50
|
// Log long sql results
|
|
50
51
|
// Requires XSODATA_LOG_SQL_COMMAND >= 2
|
|
@@ -1081,6 +1081,14 @@ function SortOrder(expression, ascending) {
|
|
|
1081
1081
|
this.ascending = ascending;
|
|
1082
1082
|
}
|
|
1083
1083
|
|
|
1084
|
+
SortOrder.prototype.setPropertyName = function(propertyName) {
|
|
1085
|
+
this.propertyName = propertyName;
|
|
1086
|
+
};
|
|
1087
|
+
|
|
1088
|
+
SortOrder.prototype.getPropertyName = function() {
|
|
1089
|
+
return this.propertyName;
|
|
1090
|
+
};
|
|
1091
|
+
|
|
1084
1092
|
SortOrder.prototype.setAlias = function (alias) {
|
|
1085
1093
|
this.expression.setAlias(alias);
|
|
1086
1094
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sap/xsodata",
|
|
3
|
-
"version": "7.5.
|
|
3
|
+
"version": "7.5.5",
|
|
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",
|
|
@@ -8,21 +8,17 @@
|
|
|
8
8
|
"serve": "node test_apps/server",
|
|
9
9
|
"serve-debug": "node --inspect-brk test_apps/server",
|
|
10
10
|
"pretest": "npm -g ls --depth=0 && npm ls --depth=0",
|
|
11
|
-
"test": "npm run unit-tests",
|
|
12
11
|
"lint": "jshint .",
|
|
13
|
-
"all-tests": "node ./node_modules/mocha/bin/_mocha --timeout 20000 test/**/test*.js test_apps/test_**/**/test*.js",
|
|
14
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'",
|
|
15
14
|
"scenario-tests": "node ./node_modules/mocha/bin/_mocha --timeout 20000 test_apps/test_**/**/test*.js",
|
|
16
15
|
"unit-tests": "node ./node_modules/mocha/bin/_mocha test/**/test*.js",
|
|
17
|
-
"cover": "
|
|
18
|
-
"cover-
|
|
19
|
-
"cover-scenario-tests": "istanbul cover -x test_apps/**/* --report lcov --dir ./_coverage ./node_modules/mocha/bin/_mocha -- -R spec test_apps/**/test*.js",
|
|
20
|
-
"cover-jenkins-istanbul": "istanbul cover ./node_modules/mocha/bin/_mocha 'test/**/test*.js' 'test_apps/**/test*.js' -- --timeout 20000 && istanbul check-coverage ./_coverage/coverage.json",
|
|
16
|
+
"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",
|
|
21
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",
|
|
22
|
-
"report": "node ./node_modules/mocha/bin/_mocha test/**/test*.js test_apps/test_**/**/test*.js --reporter mocha-simple-html-reporter --reporter-options output=
|
|
23
|
-
"report-unit-tests": "node ./node_modules/mocha/bin/_mocha test/**/test*.js --reporter mocha-simple-html-reporter --reporter-options output=
|
|
24
|
-
"report-scenario-tests": "node ./node_modules/mocha/bin/_mocha test_apps/test_**/**/test*.js --reporter mocha-simple-html-reporter --reporter-options output=
|
|
25
|
-
"env": "env",
|
|
19
|
+
"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
|
+
"report-unit-tests": "node ./node_modules/mocha/bin/_mocha test/**/test*.js --reporter mocha-simple-html-reporter --reporter-options output=report_unit.html",
|
|
21
|
+
"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",
|
|
26
22
|
"gen": "npm run genfilter && npm run genorderby && npm run gensegment && npm run genxso && npm run genxml && npm run genDateTimeParser",
|
|
27
23
|
"genfilter": "jison lib/grammars/filter.jison -o lib/parsers/jison_filter_parser.js",
|
|
28
24
|
"genorderby": "jison lib/grammars/orderby.jison -o lib/parsers/jison_orderby_parser.js",
|
|
@@ -68,10 +64,9 @@
|
|
|
68
64
|
"chai": "4.2.0",
|
|
69
65
|
"expect": "1.20.2",
|
|
70
66
|
"filter-node-package": "=2.2.0",
|
|
71
|
-
"istanbul": "0.4.5",
|
|
72
67
|
"jison": "0.4.18",
|
|
73
|
-
"jshint": "2.
|
|
74
|
-
"mocha": "
|
|
68
|
+
"jshint": "2.13.4",
|
|
69
|
+
"mocha": "9.2.2",
|
|
75
70
|
"mocha-simple-html-reporter": "1.1.0",
|
|
76
71
|
"node-mocks-http": "=1.7.6",
|
|
77
72
|
"nyc": "^15.1.0",
|