@sap/cds-compiler 4.3.0 → 4.4.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 +36 -0
- package/lib/api/main.js +14 -24
- package/lib/api/options.js +1 -0
- package/lib/api/trace.js +38 -0
- package/lib/base/location.js +46 -1
- package/lib/base/message-registry.js +68 -16
- package/lib/base/messages.js +8 -3
- package/lib/checks/.eslintrc.json +1 -0
- package/lib/checks/actionsFunctions.js +1 -1
- package/lib/checks/annotationsOData.js +2 -2
- package/lib/checks/selectItems.js +4 -1
- package/lib/compiler/assert-consistency.js +3 -2
- package/lib/compiler/base.js +1 -1
- package/lib/compiler/builtins.js +25 -1
- package/lib/compiler/checks.js +6 -5
- package/lib/compiler/define.js +12 -10
- package/lib/compiler/extend.js +22 -22
- package/lib/compiler/finalize-parse-cdl.js +1 -1
- package/lib/compiler/generate.js +70 -53
- package/lib/compiler/kick-start.js +9 -5
- package/lib/compiler/populate.js +31 -22
- package/lib/compiler/propagator.js +6 -2
- package/lib/compiler/resolve.js +52 -17
- package/lib/compiler/shared.js +74 -38
- package/lib/compiler/tweak-assocs.js +64 -23
- package/lib/compiler/utils.js +40 -23
- package/lib/edm/.eslintrc.json +2 -0
- package/lib/edm/EdmPrimitiveTypeDefinitions.js +252 -0
- package/lib/edm/annotations/edmJson.js +994 -0
- package/lib/edm/annotations/genericTranslation.js +75 -421
- package/lib/edm/annotations/vocabularyDefinitions.js +160 -0
- package/lib/edm/csn2edm.js +12 -5
- package/lib/edm/edm.js +14 -73
- package/lib/edm/edmPreprocessor.js +6 -0
- package/lib/gen/Dictionary.json +187 -16
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +1 -1
- package/lib/gen/languageLexer.interp +1 -1
- package/lib/gen/languageLexer.js +1129 -671
- package/lib/gen/languageParser.js +4285 -4283
- package/lib/json/from-csn.js +13 -18
- package/lib/json/to-csn.js +11 -6
- package/lib/language/antlrParser.js +0 -1
- package/lib/language/docCommentParser.js +1 -1
- package/lib/language/errorStrategy.js +95 -30
- package/lib/language/genericAntlrParser.js +21 -1
- package/lib/main.js +13 -3
- package/lib/model/csnRefs.js +42 -8
- package/lib/model/csnUtils.js +14 -2
- package/lib/model/enrichCsn.js +33 -5
- package/lib/model/revealInternalProperties.js +5 -0
- package/lib/modelCompare/compare.js +76 -14
- package/lib/modelCompare/utils/filter.js +19 -12
- package/lib/optionProcessor.js +2 -0
- package/lib/render/.eslintrc.json +1 -1
- package/lib/render/manageConstraints.js +1 -0
- package/lib/render/toHdbcds.js +3 -0
- package/lib/render/toRename.js +3 -1
- package/lib/render/toSql.js +46 -92
- package/lib/render/utils/common.js +76 -0
- package/lib/render/utils/delta.js +17 -3
- package/lib/sql-identifier.js +1 -1
- package/lib/transform/db/.eslintrc.json +1 -0
- package/lib/transform/db/applyTransformations.js +30 -4
- package/lib/transform/db/associations.js +22 -10
- package/lib/transform/db/backlinks.js +6 -2
- package/lib/transform/db/expansion.js +2 -2
- package/lib/transform/db/transformExists.js +13 -39
- package/lib/transform/draft/db.js +14 -3
- package/lib/transform/draft/odata.js +5 -18
- package/lib/transform/effective/associations.js +46 -15
- package/lib/transform/effective/main.js +7 -2
- package/lib/transform/effective/misc.js +43 -24
- package/lib/transform/effective/queries.js +20 -22
- package/lib/transform/effective/types.js +6 -2
- package/lib/transform/forOdata.js +5 -2
- package/lib/transform/localized.js +1 -1
- package/lib/transform/parseExpr.js +73 -21
- package/lib/transform/translateAssocsToJoins.js +24 -16
- package/lib/utils/term.js +2 -2
- package/package.json +2 -1
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
const { setProp } = require('../../base/model');
|
|
4
4
|
const flattening = require('../db/flattening');
|
|
5
5
|
const {
|
|
6
|
-
applyTransformations, forEachDefinition, forEachMemberRecursively, implicitAs, cloneCsnNonDict,
|
|
6
|
+
applyTransformations, forEachDefinition, forEachMemberRecursively, implicitAs, cloneCsnNonDict, forEachMember, applyTransformationsOnNonDictionary,
|
|
7
7
|
} = require('../../model/csnUtils');
|
|
8
8
|
const associations = require('../db/associations');
|
|
9
9
|
const backlinks = require('../db/backlinks');
|
|
@@ -33,31 +33,62 @@ function turnAssociationsIntoUnmanaged( csn, options, csnUtils, { error, warning
|
|
|
33
33
|
}, [ 'definitions', artifactName ]);
|
|
34
34
|
});
|
|
35
35
|
// Flatten out the fks and create the corresponding elements
|
|
36
|
-
flattening.handleManagedAssociationsAndCreateForeignKeys(csn, options, error, warning, '_', true, csnUtils, { allowArtifact:
|
|
36
|
+
flattening.handleManagedAssociationsAndCreateForeignKeys(csn, options, error, warning, '_', true, csnUtils, { allowArtifact: () => true, skipDict: {} });
|
|
37
37
|
|
|
38
38
|
// Add the foreign keys also to the columns if the association itself was explicitly selected
|
|
39
39
|
// TODO: Extend the expansion to also expand managed to their foreign
|
|
40
40
|
// TODO: Remember where in .elements we had associations and do it then?
|
|
41
41
|
applyTransformations(csn, {
|
|
42
|
-
columns: (
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
const element = csnUtils.getElement(col);
|
|
47
|
-
if (element && element.keys)
|
|
48
|
-
element.keys.forEach(fk => addForeignKeyToColumns(fk, newColumns, col, options));
|
|
49
|
-
}
|
|
50
|
-
parent.columns = newColumns;
|
|
51
|
-
},
|
|
52
|
-
}, [], { allowArtifact: artifact => artifact.kind === 'entity' });
|
|
42
|
+
columns: expandManagedToFksInArray(),
|
|
43
|
+
groupBy: expandManagedToFksInArray(true),
|
|
44
|
+
orderBy: expandManagedToFksInArray(true),
|
|
45
|
+
}, [], { allowArtifact: artifact => artifact.query !== undefined || artifact.projection !== undefined });
|
|
53
46
|
|
|
54
|
-
forEachDefinition(csn, associations.getFKAccessFinalizer(csn, csnUtils, '_'));
|
|
47
|
+
forEachDefinition(csn, associations.getFKAccessFinalizer(csn, csnUtils, '_', true));
|
|
48
|
+
applyTransformations(csn, {
|
|
49
|
+
elements: (_parent, prop, elements, path) => {
|
|
50
|
+
forEachMember(_parent, (element, elementName, _prop, elPath) => {
|
|
51
|
+
if (element.on) {
|
|
52
|
+
applyTransformationsOnNonDictionary(element, 'on', {
|
|
53
|
+
ref: (parent, __prop, ref) => {
|
|
54
|
+
if (ref[0] === '$self' && ref.length > 1 && !ref[1].startsWith('$')) // TODO: Is this safe?
|
|
55
|
+
parent.ref = ref.slice(1);
|
|
56
|
+
},
|
|
57
|
+
}, {}, elPath);
|
|
58
|
+
}
|
|
59
|
+
}, path);
|
|
60
|
+
},
|
|
61
|
+
});
|
|
55
62
|
// Calculate the on-conditions from the .keys -
|
|
56
|
-
associations.attachOnConditions(csn, csnUtils, '_');
|
|
63
|
+
associations.attachOnConditions(csn, csnUtils, '_', { allowArtifact: () => true }, options);
|
|
57
64
|
|
|
58
65
|
return csn;
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Expand managed associations in an array and insert them in-place
|
|
69
|
+
*
|
|
70
|
+
* If requested, leave out the assocs themselves
|
|
71
|
+
*
|
|
72
|
+
* @param {boolean} [killAssoc=false]
|
|
73
|
+
* @returns {Function} applyTransformationsCallback
|
|
74
|
+
*/
|
|
75
|
+
function expandManagedToFksInArray( killAssoc = false ) {
|
|
76
|
+
return function expand(parent, prop, array, path) {
|
|
77
|
+
const newColumns = [];
|
|
78
|
+
for (let i = 0; i < array.length; i++) {
|
|
79
|
+
const col = array[i];
|
|
80
|
+
const element = csnUtils.getElement(col) || col.ref && csnUtils.inspectRef(path.concat(prop, i)).art;
|
|
81
|
+
if (!killAssoc || !element?.keys)
|
|
82
|
+
newColumns.push(col);
|
|
83
|
+
if (element?.keys)
|
|
84
|
+
element.keys.forEach(fk => addForeignKeyToColumns(fk, newColumns, col, options));
|
|
85
|
+
}
|
|
86
|
+
parent[prop] = newColumns;
|
|
87
|
+
};
|
|
88
|
+
}
|
|
59
89
|
}
|
|
60
90
|
|
|
91
|
+
|
|
61
92
|
/**
|
|
62
93
|
* FKs need to be added to the .columns
|
|
63
94
|
* @todo stolen from lib/transform/db/views.js
|
|
@@ -34,6 +34,8 @@ function effectiveCsn( model, options ) {
|
|
|
34
34
|
|
|
35
35
|
const csn = cloneCsnNonDict(model, options);
|
|
36
36
|
|
|
37
|
+
delete csn.vocabularies; // must not be set for effective CSN
|
|
38
|
+
|
|
37
39
|
const { expandStructsInExpression } = transformUtils.getTransformers(csn, options, '_');
|
|
38
40
|
queries.projectionToSELECTAndAddColumns(csn);
|
|
39
41
|
|
|
@@ -58,11 +60,11 @@ function effectiveCsn( model, options ) {
|
|
|
58
60
|
|
|
59
61
|
messageFunctions.throwWithAnyError();
|
|
60
62
|
|
|
61
|
-
const resolveTypesInActionsAfterFlattening = types.resolve(csn, csnUtils);
|
|
62
|
-
|
|
63
63
|
// Expand a structured thing in: keys, columns, order by, group by
|
|
64
64
|
expansion.expandStructureReferences(csn, options, '_', messageFunctions, csnUtils);
|
|
65
65
|
|
|
66
|
+
const resolveTypesInActionsAfterFlattening = types.resolve(csn, csnUtils);
|
|
67
|
+
|
|
66
68
|
csnUtils = getUtils(csn, 'init-all');
|
|
67
69
|
|
|
68
70
|
// Remove properties attached by validator - they do not "grow" as the model grows.
|
|
@@ -73,6 +75,9 @@ function effectiveCsn( model, options ) {
|
|
|
73
75
|
|
|
74
76
|
resolveTypesInActionsAfterFlattening();
|
|
75
77
|
|
|
78
|
+
// ensure getElement works on flattened struct_assoc columns
|
|
79
|
+
csnUtils = getUtils(csn, 'init-all');
|
|
80
|
+
|
|
76
81
|
processCalculatedElementsInEntities(csn);
|
|
77
82
|
associations.managedToUnmanaged(csn, options, csnUtils, messageFunctions);
|
|
78
83
|
associations.transformBacklinks(csn, options, csnUtils, messageFunctions);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const {
|
|
4
|
-
forEachDefinition, forEachMemberRecursively, getArtifactDatabaseNameOf, getElementDatabaseNameOf,
|
|
4
|
+
forEachDefinition, forEachMemberRecursively, getArtifactDatabaseNameOf, getElementDatabaseNameOf, applyTransformations,
|
|
5
5
|
} = require('../../model/csnUtils');
|
|
6
6
|
/**
|
|
7
7
|
* Attach @cds.persistence.name to all artifacts and "things".
|
|
@@ -14,16 +14,21 @@ function attachPersistenceName( csn, options, csnUtils ) {
|
|
|
14
14
|
const { addStringAnnotationTo } = csnUtils;
|
|
15
15
|
|
|
16
16
|
forEachDefinition(csn, (artifact, artifactName) => {
|
|
17
|
-
|
|
18
|
-
addStringAnnotationTo('@cds.persistence.name', getArtifactDatabaseNameOf(artifactName, options.sqlMapping, csn, options.sqlDialect), artifact);
|
|
17
|
+
addStringAnnotationTo('@cds.persistence.name', getArtifactDatabaseNameOf(artifactName, options.sqlMapping, csn, options.sqlDialect), artifact);
|
|
19
18
|
|
|
20
|
-
|
|
21
|
-
}
|
|
19
|
+
forEachMemberRecursively(artifact, (member, memberName) => addStringAnnotationTo('@cds.persistence.name', getElementDatabaseNameOf(memberName, options.sqlMapping, options.sqlDialect), member), [ 'definitions', artifactName ]);
|
|
22
20
|
});
|
|
23
21
|
}
|
|
24
22
|
|
|
25
|
-
|
|
26
|
-
|
|
23
|
+
/**
|
|
24
|
+
* Delete the given prop from parent.
|
|
25
|
+
*
|
|
26
|
+
* @param {object} parent
|
|
27
|
+
* @param {string|number} prop
|
|
28
|
+
*/
|
|
29
|
+
function killProp( parent, prop ) {
|
|
30
|
+
delete parent[prop];
|
|
31
|
+
}
|
|
27
32
|
|
|
28
33
|
/**
|
|
29
34
|
* Remove definitions from the CSN:
|
|
@@ -36,26 +41,40 @@ const memberPropertiesToRemove = [ 'localized', 'enum', 'keys' ];
|
|
|
36
41
|
* @param {CSN.Model} csn
|
|
37
42
|
* @todo Callback-like architecture and merge with persistence name?
|
|
38
43
|
*/
|
|
39
|
-
function
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
44
|
+
function _removeDefinitionsAndProperties( csn ) {
|
|
45
|
+
const killers = {
|
|
46
|
+
$ignore: (a, b, c, path, parentParent) => {
|
|
47
|
+
const tail = path[path.length - 1];
|
|
48
|
+
delete parentParent[tail];
|
|
49
|
+
},
|
|
50
|
+
kind: (artifact, a, b, path) => {
|
|
51
|
+
if (artifact.kind === 'aspect' || artifact.kind === 'type')
|
|
52
|
+
delete csn.definitions[path[1]];
|
|
53
|
+
|
|
54
|
+
else if (artifact['@cds.persistence.skip'] === 'if-unused')
|
|
46
55
|
artifact['@cds.persistence.skip'] = false;
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
+
},
|
|
57
|
+
// Still used in flattenStructuredElements - in db/flattening.js
|
|
58
|
+
_flatElementNameWithDots: killProp,
|
|
59
|
+
// Set when setting default string/binary length - used in copyTypeProperties and fixBorkedElementsOfLocalized
|
|
60
|
+
// to not copy the .length property if it was only set via default
|
|
61
|
+
$default: killProp,
|
|
62
|
+
// Set when we turn UUID into String, checked during generateDraftForHana
|
|
63
|
+
$renamed: killProp,
|
|
64
|
+
// Set when we remove .key from temporal things, used in localized.js
|
|
65
|
+
$key: killProp,
|
|
66
|
+
includes: killProp,
|
|
67
|
+
localized: killProp,
|
|
68
|
+
enum: killProp,
|
|
69
|
+
keys: killProp,
|
|
70
|
+
excluding: killProp, // * is resolved, so has no effect anymore
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
applyTransformations(csn, killers, [], { skipIgnore: false });
|
|
56
74
|
}
|
|
57
75
|
|
|
76
|
+
|
|
58
77
|
module.exports = {
|
|
59
78
|
attachPersistenceName,
|
|
60
|
-
removeDefinitionsAndProperties,
|
|
79
|
+
removeDefinitionsAndProperties: _removeDefinitionsAndProperties,
|
|
61
80
|
};
|
|
@@ -11,28 +11,26 @@ const { forEachDefinition, applyTransformationsOnNonDictionary } = require('../.
|
|
|
11
11
|
function projectionToSELECTAndAddColumns( csn ) {
|
|
12
12
|
const redoProjections = [];
|
|
13
13
|
forEachDefinition(csn, (artifact) => {
|
|
14
|
-
if (artifact.
|
|
15
|
-
if (artifact.projection)
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
SELECT
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
});
|
|
35
|
-
}
|
|
14
|
+
if (artifact.projection) {
|
|
15
|
+
if (!artifact.projection.columns)
|
|
16
|
+
artifact.projection.columns = [ '*' ];
|
|
17
|
+
artifact.query = { SELECT: artifact.projection };
|
|
18
|
+
delete artifact.projection;
|
|
19
|
+
redoProjections.push(() => {
|
|
20
|
+
if (artifact.query) {
|
|
21
|
+
artifact.projection = artifact.query.SELECT;
|
|
22
|
+
delete artifact.query;
|
|
23
|
+
if (artifact.$syntax === 'projection')
|
|
24
|
+
delete artifact.$syntax;
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
else if (artifact.query) {
|
|
29
|
+
applyTransformationsOnNonDictionary(artifact, 'query', {
|
|
30
|
+
SELECT: (parent, prop, SELECT) => {
|
|
31
|
+
SELECT.columns ??= [ '*' ];
|
|
32
|
+
},
|
|
33
|
+
});
|
|
36
34
|
}
|
|
37
35
|
});
|
|
38
36
|
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
const {
|
|
4
4
|
cloneCsnNonDict, applyTransformations, applyTransformationsOnNonDictionary, cloneCsnDictionary, applyTransformationsOnDictionary,
|
|
5
5
|
} = require('../../model/csnUtils');
|
|
6
|
+
const { forEachKey } = require('../../utils/objectUtils');
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* Resolve all references to structured types in entities to the underlying elements.
|
|
@@ -33,7 +34,7 @@ function resolveTypes( csn, csnUtils ) {
|
|
|
33
34
|
later.push({ [artifactName]: artifact });
|
|
34
35
|
else if (artifact.actions)
|
|
35
36
|
later.push(artifact.actions);
|
|
36
|
-
} ], { skipDict: { actions: true } });
|
|
37
|
+
} ], { skipDict: { actions: true }, processAnnotations: true });
|
|
37
38
|
|
|
38
39
|
return function resolveTypesInActions() {
|
|
39
40
|
later.forEach(action => applyTransformationsOnDictionary(action, { type: parent => resolveType(parent) }));
|
|
@@ -64,7 +65,10 @@ function resolveTypes( csn, csnUtils ) {
|
|
|
64
65
|
delete parent.type;
|
|
65
66
|
}
|
|
66
67
|
else if (final?.type) {
|
|
67
|
-
|
|
68
|
+
forEachKey(final, (key) => { // copy `type` + properties (default, etc.)
|
|
69
|
+
if (parent[key] === undefined || key === 'type')
|
|
70
|
+
parent[key] = final[key];
|
|
71
|
+
});
|
|
68
72
|
}
|
|
69
73
|
|
|
70
74
|
// Drill down - there might be other type references
|
|
@@ -161,12 +161,17 @@ function transform4odataWithCsn(inputModel, options) {
|
|
|
161
161
|
// rendering which may has to publish external definitions
|
|
162
162
|
expandToFinalBaseType(csn, transformers, csnUtils, services, options);
|
|
163
163
|
|
|
164
|
+
// - Generate artificial draft fields on a structured CSN if requested, flattening and struct
|
|
165
|
+
// expansion do their magic including foreign key generation and annotation propagation.
|
|
166
|
+
generateDrafts(csn, options, services);
|
|
167
|
+
|
|
164
168
|
// Check if structured elements and managed associations are compared in an expression
|
|
165
169
|
// and expand these structured elements. This tuple expansion allows all other
|
|
166
170
|
// subsequent procession steps (especially a2j) to see plain paths in expressions.
|
|
167
171
|
// If errors are detected, throwWithAnyError() will return from further processing
|
|
168
172
|
expandStructsInExpression(csn, { skipArtifact: isExternalServiceMember, drillRef: true });
|
|
169
173
|
|
|
174
|
+
|
|
170
175
|
if (!structuredOData) {
|
|
171
176
|
expansion.expandStructureReferences(csn, options, '_', { error, info, throwWithAnyError }, csnUtils, { skipArtifact: isExternalServiceMember });
|
|
172
177
|
const resolved = new WeakMap();
|
|
@@ -211,7 +216,6 @@ function transform4odataWithCsn(inputModel, options) {
|
|
|
211
216
|
forEachDefinition(csn, [ setDefaultTypeFacets, processOnCond ], { skipArtifact: isExternalServiceMember });
|
|
212
217
|
|
|
213
218
|
// Now all artificially generated things are in place
|
|
214
|
-
// - Generate artificial draft fields if requested
|
|
215
219
|
// TODO: should be done by the compiler - Check associations for valid foreign keys
|
|
216
220
|
// TODO: check if needed at all: Remove '$projection' from paths in the element's ON-condition
|
|
217
221
|
// - Check associations for:
|
|
@@ -219,7 +223,6 @@ function transform4odataWithCsn(inputModel, options) {
|
|
|
219
223
|
// - structured types must not contain associations for OData V2
|
|
220
224
|
// - Element must not be an 'array of' for OData V2 TODO: move to the validator
|
|
221
225
|
// - Perform checks for exposed non-abstract entities and views - check media type and key-ness
|
|
222
|
-
generateDrafts(csn, options, services)
|
|
223
226
|
|
|
224
227
|
// Deal with all kind of annotations manipulations here
|
|
225
228
|
const skipPersNameKinds = {'service':1, 'context':1, 'namespace':1, 'annotation':1, 'action':1, 'function':1};
|
|
@@ -648,7 +648,7 @@ function _addLocalizationViews(csn, options, config) {
|
|
|
648
648
|
filter: (name) => name.startsWith('localized.'),
|
|
649
649
|
notFound(name, index) {
|
|
650
650
|
if (!ignoreUnknownExtensions) {
|
|
651
|
-
messageFunctions.message('
|
|
651
|
+
messageFunctions.message('ext-undefined-def', [ 'extensions', index ],
|
|
652
652
|
{ art: name });
|
|
653
653
|
}
|
|
654
654
|
},
|
|
@@ -34,12 +34,13 @@
|
|
|
34
34
|
*
|
|
35
35
|
* @param {any} xpr A JSON object.
|
|
36
36
|
* @param {Object} state Object
|
|
37
|
-
* anno: Don't eliminate arrays with single entry in
|
|
37
|
+
* anno: Don't eliminate arrays with single entry in expressions (TODO?) as they are collections
|
|
38
38
|
* array: Bias AST representation.
|
|
39
39
|
* nary: return n-ary or binary tree
|
|
40
40
|
*/
|
|
41
41
|
|
|
42
|
-
function parseExpr(xpr, state = {
|
|
42
|
+
function parseExpr(xpr, state = { array: true, nary: false }) {
|
|
43
|
+
state.anno = 0;
|
|
43
44
|
// Notes:
|
|
44
45
|
// - Variables `s` and `e` are used as index variables into `xpr`s for start and end.
|
|
45
46
|
// - xpr's are our CSN expressions, see <https://cap.cloud.sap/docs/cds/cxn>
|
|
@@ -47,7 +48,28 @@ function parseExpr(xpr, state = { anno: 0, array: true, nary: false }) {
|
|
|
47
48
|
return parseExprInt(xpr, state);
|
|
48
49
|
|
|
49
50
|
function parseExprInt(xpr, state) {
|
|
50
|
-
return conditionOR(...CaseWhen(xpr), state);
|
|
51
|
+
return conditionOR(...CaseWhen(Cast(xpr, state)), state);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function Cast(xpr, state) {
|
|
55
|
+
if(xpr != null && !state.array) {
|
|
56
|
+
if(Array.isArray(xpr))
|
|
57
|
+
return xpr.map(x => Cast(x, state));
|
|
58
|
+
if(typeof xpr === 'object') {
|
|
59
|
+
const castKeys = Object.keys(xpr).filter(k => k !== 'cast');
|
|
60
|
+
if(xpr.cast != null && castKeys.length === 1) {
|
|
61
|
+
return { 'cast': [ xpr.cast, { [castKeys[0]]: xpr[castKeys[0]] } ] };
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
for(let n in xpr) {
|
|
65
|
+
// xpr could be an array with polluted prototype
|
|
66
|
+
if (Object.hasOwnProperty.call(xpr, n))
|
|
67
|
+
xpr[n] = Cast(xpr[n], state)
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return xpr;
|
|
51
73
|
}
|
|
52
74
|
|
|
53
75
|
function CaseWhen(xpr) {
|
|
@@ -95,11 +117,11 @@ function parseExpr(xpr, state = { anno: 0, array: true, nary: false }) {
|
|
|
95
117
|
|
|
96
118
|
while(xpr[whenPos] !== 'when' && whenPos < endPos) whenPos++;
|
|
97
119
|
if(xpr[whenPos] === 'when' && whenPos - (casePos+1) >= 1) {
|
|
98
|
-
const
|
|
120
|
+
const caseExpr = xpr.slice(casePos+1, whenPos)
|
|
99
121
|
if(state.array)
|
|
100
|
-
caseTree.push(
|
|
122
|
+
caseTree.push(caseExpr);
|
|
101
123
|
else
|
|
102
|
-
|
|
124
|
+
caseTree.case.push(caseExpr.length === 1 ? caseExpr[0] : caseExpr);
|
|
103
125
|
}
|
|
104
126
|
|
|
105
127
|
while(xpr[whenPos] === 'when') {
|
|
@@ -112,11 +134,11 @@ function parseExpr(xpr, state = { anno: 0, array: true, nary: false }) {
|
|
|
112
134
|
let thenPos = whenPos+1;
|
|
113
135
|
while(xpr[thenPos] !== 'then' && thenPos < endPos) thenPos++;
|
|
114
136
|
if(xpr[thenPos] === 'then') {
|
|
115
|
-
const
|
|
137
|
+
const whenExpr = xpr.slice(whenPos+1, thenPos);
|
|
116
138
|
if(state.array)
|
|
117
|
-
caseTree.push(
|
|
139
|
+
caseTree.push(whenExpr);
|
|
118
140
|
else
|
|
119
|
-
when.when.push(
|
|
141
|
+
when.when.push(whenExpr.length === 1 ? whenExpr[0] : whenExpr);
|
|
120
142
|
}
|
|
121
143
|
|
|
122
144
|
whenPos = thenPos+1;
|
|
@@ -188,17 +210,19 @@ function parseExpr(xpr, state = { anno: 0, array: true, nary: false }) {
|
|
|
188
210
|
function compareTerm(xpr, s, e, state) {
|
|
189
211
|
if(Array.isArray(xpr)) {
|
|
190
212
|
let i = s;
|
|
213
|
+
let not = false;
|
|
214
|
+
let between;
|
|
191
215
|
while(i < e && xpr[i] !== 'between') i++;
|
|
192
216
|
const b = i < e ? i : -1;
|
|
193
217
|
while(i < e && xpr[i] !== 'and') i++;
|
|
194
218
|
const a = i < e ? i : -1;
|
|
195
219
|
if(b >= 0) {
|
|
196
220
|
let token = [ 'between' ];
|
|
197
|
-
|
|
221
|
+
not = (xpr[b-1] === 'not');
|
|
198
222
|
if(not)
|
|
199
223
|
token.splice(0,0, 'not');
|
|
200
224
|
const expr = expression(xpr, s, not ? b-1 : b, state);
|
|
201
|
-
|
|
225
|
+
between = state.array
|
|
202
226
|
? [ expr, ...token ]
|
|
203
227
|
: { 'between': [ expr ] };
|
|
204
228
|
if(a >= 0) {
|
|
@@ -217,6 +241,9 @@ function parseExpr(xpr, state = { anno: 0, array: true, nary: false }) {
|
|
|
217
241
|
else
|
|
218
242
|
between.between.push(unspec);
|
|
219
243
|
}
|
|
244
|
+
if(not && !state.array) {
|
|
245
|
+
between = { 'not': between }
|
|
246
|
+
}
|
|
220
247
|
return between;
|
|
221
248
|
}
|
|
222
249
|
}
|
|
@@ -263,7 +290,10 @@ function parseExpr(xpr, state = { anno: 0, array: true, nary: false }) {
|
|
|
263
290
|
function unary(xpr, s, e, state) {
|
|
264
291
|
if(Array.isArray(xpr)) {
|
|
265
292
|
if(xpr[s] === '+' || xpr[s] === '-' || (!state.array && xpr[s] === 'new')) {
|
|
266
|
-
|
|
293
|
+
if(state.array)
|
|
294
|
+
return [ xpr[s], unary(xpr, s+1, e, state) ];
|
|
295
|
+
else
|
|
296
|
+
return { [xpr[s]]: unary(xpr, s+1, e, state) };
|
|
267
297
|
}
|
|
268
298
|
}
|
|
269
299
|
return terminal(xpr, s, e, state);
|
|
@@ -277,7 +307,7 @@ function parseExpr(xpr, state = { anno: 0, array: true, nary: false }) {
|
|
|
277
307
|
'xpr', 'on', 'where', 'orderBy', 'groupBy', 'having' ];
|
|
278
308
|
|
|
279
309
|
if(Array.isArray(xpr) && xpr.length > 0) {
|
|
280
|
-
if(e-s <= 1 && state.anno === 0)
|
|
310
|
+
if(e-s <= 1 && state.anno === 0 && typeof xpr[e-1] !== 'string')
|
|
281
311
|
return parseExprInt(xpr[e-1], state);
|
|
282
312
|
else
|
|
283
313
|
return xpr.slice(s, e).map(ix => parseExprInt(ix, state));
|
|
@@ -311,28 +341,50 @@ function parseExpr(xpr, state = { anno: 0, array: true, nary: false }) {
|
|
|
311
341
|
}
|
|
312
342
|
|
|
313
343
|
function binaryExpr(xpr, token, next, s, e, state) {
|
|
314
|
-
const
|
|
344
|
+
const naryExpr = [];
|
|
345
|
+
let not = false;
|
|
315
346
|
if (Array.isArray(xpr)) {
|
|
316
347
|
let [tl, p] = findToken(s, e);
|
|
317
348
|
if (p >= 0) {
|
|
318
349
|
let lhs = next(xpr, s, p, state);
|
|
319
|
-
|
|
350
|
+
naryExpr.push(lhs);
|
|
320
351
|
let op = xpr.slice(p, p+tl);
|
|
321
352
|
s = p+tl;
|
|
322
353
|
[tl, p] = findToken(s, e);
|
|
323
354
|
while(p >= 0) {
|
|
324
355
|
let rhs = next(xpr, s, p, state);
|
|
325
|
-
|
|
326
|
-
|
|
356
|
+
naryExpr.push(...op, rhs);
|
|
357
|
+
if(state.array)
|
|
358
|
+
lhs = [ lhs, ...op, rhs ];
|
|
359
|
+
else {
|
|
360
|
+
not = op.length > 1 && op[0] === 'not';
|
|
361
|
+
if(not)
|
|
362
|
+
op = op.slice(1);
|
|
363
|
+
lhs = (not
|
|
364
|
+
? { 'not': { [op.join('')]: [lhs, rhs] } }
|
|
365
|
+
: { [op.join('')]: [lhs, rhs] });
|
|
366
|
+
}
|
|
327
367
|
op = xpr.slice(p, p+tl);
|
|
328
368
|
s = p+tl;
|
|
329
369
|
[tl, p] = findToken(s, e);
|
|
330
370
|
}
|
|
331
|
-
|
|
371
|
+
|
|
372
|
+
let rhs = next(xpr, s, e, state);
|
|
373
|
+
if(Array.isArray(rhs) && rhs.length === 0)
|
|
374
|
+
rhs = undefined;
|
|
375
|
+
naryExpr.push(...op, rhs);
|
|
376
|
+
|
|
332
377
|
if (state.array)
|
|
333
|
-
return (state.nary ?
|
|
334
|
-
else
|
|
335
|
-
|
|
378
|
+
return (state.nary ? naryExpr : [ lhs, ...op, rhs ])
|
|
379
|
+
else {
|
|
380
|
+
not = op.length > 1 && op[0] === 'not';
|
|
381
|
+
if(not)
|
|
382
|
+
op = op.slice(1);
|
|
383
|
+
return (not
|
|
384
|
+
? { 'not': { [op.join('')]: [ lhs, rhs ] } }
|
|
385
|
+
: { [op.join('')]: [ lhs, rhs ] });
|
|
386
|
+
|
|
387
|
+
}
|
|
336
388
|
}
|
|
337
389
|
}
|
|
338
390
|
return next(xpr, s, e, state);
|
|
@@ -17,7 +17,9 @@ function translateAssocsToJoinsCSN(csn, options){
|
|
|
17
17
|
// Do not re-complain about localized
|
|
18
18
|
const compileOptions = { ...options, $skipNameCheck: true };
|
|
19
19
|
delete compileOptions.csnFlavor;
|
|
20
|
-
|
|
20
|
+
|
|
21
|
+
//require('fs').writeFileSync('./csninput_a2j.json', JSON.stringify(csn, null,2))
|
|
22
|
+
//console.log('CSN passed by A2J to compiler:',JSON.stringify(csn,null,2))
|
|
21
23
|
const model = recompileX(csn, compileOptions);
|
|
22
24
|
timetrace.stop('A2J: Recompiling model');
|
|
23
25
|
timetrace.start('A2J: Translating associations to joins');
|
|
@@ -48,7 +50,9 @@ function translateAssocsToJoinsCSN(csn, options){
|
|
|
48
50
|
// If A2J reports error - end! Continuing with a broken CSN makes no sense
|
|
49
51
|
makeMessageFunction(model, options).throwWithAnyError();
|
|
50
52
|
// FIXME: Move this somewhere more appropriate
|
|
51
|
-
|
|
53
|
+
const newCsn = compactModel(model, compileOptions);
|
|
54
|
+
//require('fs').writeFileSync('./csnoutput_a2j.json', JSON.stringify(newCsn, null,2))
|
|
55
|
+
return newCsn;
|
|
52
56
|
}
|
|
53
57
|
|
|
54
58
|
function translateAssocsToJoins(model, inputOptions = {})
|
|
@@ -444,7 +448,7 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
444
448
|
{
|
|
445
449
|
// Filter conditions are unique for each JOIN, they don't need to be copied
|
|
446
450
|
let filter = childQat._filter;
|
|
447
|
-
|
|
451
|
+
rewritePathsInFilterExpression(filter, function(pathNode) {
|
|
448
452
|
return [ /* tableAlias=> */ constructTableAliasPathStep(childQat.$QA),
|
|
449
453
|
/* filterPath=> */ pathNode.path ]; // eslint-disable-line indent-legacy
|
|
450
454
|
}, env);
|
|
@@ -490,7 +494,7 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
490
494
|
{
|
|
491
495
|
// Filter conditions are unique for each JOIN, they don't need to be copied
|
|
492
496
|
let filter = assocQAT._filter;
|
|
493
|
-
|
|
497
|
+
rewritePathsInFilterExpression(filter, function(pathNode) {
|
|
494
498
|
return [ tgtTableAlias, pathNode.path ];
|
|
495
499
|
}, env);
|
|
496
500
|
|
|
@@ -733,7 +737,8 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
733
737
|
// this will substitute multiple backlink conditions ($self = ... AND $self = ...AND ...)
|
|
734
738
|
if(expr.op) {
|
|
735
739
|
let x = clone(expr);
|
|
736
|
-
|
|
740
|
+
if(expr.args)
|
|
741
|
+
x.args = expr.args.map(cloneOnCondition);
|
|
737
742
|
return x;
|
|
738
743
|
}
|
|
739
744
|
|
|
@@ -808,8 +813,6 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
808
813
|
if(rhs.mixin)
|
|
809
814
|
{
|
|
810
815
|
if (hasDollarSelfPrefix) {
|
|
811
|
-
hasDollarSelfPrefix = true;
|
|
812
|
-
|
|
813
816
|
/* Do the $projection resolution ONLY in own query not for referenced forward ON condition
|
|
814
817
|
view YP as select from Y mixin ( toXP: association to XP on $projection.yid = toXP.xid; } into { yid };
|
|
815
818
|
view XP as select from X mixin { toYP: association to YP on $self = toYP.toXP; } into { xid, toYP.elt };
|
|
@@ -1064,9 +1067,9 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
1064
1067
|
tableAlias = [ aliasName, _artifact, _navigation ]
|
|
1065
1068
|
path = [ { id: ..., _artifact: ... (unused) } ]
|
|
1066
1069
|
*/
|
|
1067
|
-
function
|
|
1070
|
+
function rewritePathsInFilterExpression(node, getTableAliasAndPathSteps, env)
|
|
1068
1071
|
{
|
|
1069
|
-
|
|
1072
|
+
const innerEnv = {
|
|
1070
1073
|
lead: env.lead,
|
|
1071
1074
|
location: env.location,
|
|
1072
1075
|
position: env.position,
|
|
@@ -1075,9 +1078,15 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
1075
1078
|
callback: [
|
|
1076
1079
|
function(pathNode) {
|
|
1077
1080
|
if(checkPathDictionary(pathNode, env)) {
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
+
const head = pathNode.path[0];
|
|
1082
|
+
if(head._navigation?.kind === '$self') {
|
|
1083
|
+
substituteDollarSelf(pathNode);
|
|
1084
|
+
}
|
|
1085
|
+
else {
|
|
1086
|
+
const [ tableAlias, path ] = getTableAliasAndPathSteps(pathNode);
|
|
1087
|
+
const pathStr = path.map(ps => ps.id).join(pathDelimiter);
|
|
1088
|
+
replaceNodeContent(pathNode, constructPathNode([ tableAlias, { id: pathStr, _artifact: pathNode._artifact } ]));
|
|
1089
|
+
}
|
|
1081
1090
|
}
|
|
1082
1091
|
} ]
|
|
1083
1092
|
};
|
|
@@ -1316,8 +1325,8 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
1316
1325
|
// we need to unambiguously identify the target side with the full assoc prefix.
|
|
1317
1326
|
// If the path is on the target side, strip the prefix of and treat src/tgt
|
|
1318
1327
|
// paths uniformly.
|
|
1319
|
-
path = (env.assocStack
|
|
1320
|
-
|
|
1328
|
+
path = (env.assocStack?.stripAssocPrefix(path) || path);
|
|
1329
|
+
const lead = env.art || env.lead;
|
|
1321
1330
|
path.forEach((ps) => {
|
|
1322
1331
|
/* checks for all path steps */
|
|
1323
1332
|
if(ps.args) {
|
|
@@ -1714,6 +1723,7 @@ function constructPathNode(pathSteps, alias, rewritten=true)
|
|
|
1714
1723
|
o[k] = p[k];
|
|
1715
1724
|
});
|
|
1716
1725
|
setProp(o, '_artifact', p._artifact );
|
|
1726
|
+
setProp(o, '_navigation', p._navigation );
|
|
1717
1727
|
return o; })
|
|
1718
1728
|
};
|
|
1719
1729
|
|
|
@@ -1759,8 +1769,6 @@ function walkQuery(query, env)
|
|
|
1759
1769
|
walk(query.having, env);
|
|
1760
1770
|
env.location = 'OrderBy';
|
|
1761
1771
|
walk(query.orderBy, env);
|
|
1762
|
-
// outer orderBy's of anonymous union
|
|
1763
|
-
walk(query.$orderBy, env);
|
|
1764
1772
|
if(query.limit) {
|
|
1765
1773
|
env.location = 'Limit';
|
|
1766
1774
|
walk(query.limit.rows, env);
|
package/lib/utils/term.js
CHANGED
|
@@ -14,8 +14,8 @@
|
|
|
14
14
|
|
|
15
15
|
'use strict';
|
|
16
16
|
|
|
17
|
-
const stderrHasColor = process.stderr
|
|
18
|
-
const stdoutHasColor = process.stdout
|
|
17
|
+
const stderrHasColor = process.stderr?.isTTY;
|
|
18
|
+
const stdoutHasColor = process.stdout?.isTTY;
|
|
19
19
|
|
|
20
20
|
// Note: We require both stderr and stdout to be TTYs, as we don't
|
|
21
21
|
// know (in our exported functions) where the text will end up.
|