@sap/cds-compiler 4.3.2 → 4.4.2
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 +39 -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 +44 -23
- package/lib/compiler/finalize-parse-cdl.js +1 -1
- package/lib/compiler/generate.js +70 -53
- package/lib/compiler/kick-start.js +7 -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 +85 -39
- 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 +260 -0
- package/lib/edm/annotations/edmJson.js +994 -0
- package/lib/edm/annotations/genericTranslation.js +82 -423
- 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 +10 -3
- package/lib/transform/localized.js +1 -1
- package/lib/transform/parseExpr.js +73 -21
- package/lib/transform/translateAssocsToJoins.js +22 -15
- 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
|
|
@@ -98,7 +98,9 @@ function transform4odataWithCsn(inputModel, options) {
|
|
|
98
98
|
inspectRef,
|
|
99
99
|
artifactRef,
|
|
100
100
|
effectiveType,
|
|
101
|
-
getFinalTypeInfo
|
|
101
|
+
getFinalTypeInfo,
|
|
102
|
+
dropDefinitionCache,
|
|
103
|
+
initDefinition,
|
|
102
104
|
} = csnUtils;
|
|
103
105
|
|
|
104
106
|
// are we working with structured OData or not
|
|
@@ -147,6 +149,8 @@ function transform4odataWithCsn(inputModel, options) {
|
|
|
147
149
|
// TODO: handle artifact.projection instead of artifact.query correctly in future V2
|
|
148
150
|
if (def.kind === 'entity' && def.projection) {
|
|
149
151
|
def.query = { SELECT: def.projection };
|
|
152
|
+
dropDefinitionCache(def);
|
|
153
|
+
initDefinition(def);
|
|
150
154
|
}
|
|
151
155
|
}],
|
|
152
156
|
{ skipArtifact: isExternalServiceMember }
|
|
@@ -161,12 +165,17 @@ function transform4odataWithCsn(inputModel, options) {
|
|
|
161
165
|
// rendering which may has to publish external definitions
|
|
162
166
|
expandToFinalBaseType(csn, transformers, csnUtils, services, options);
|
|
163
167
|
|
|
168
|
+
// - Generate artificial draft fields on a structured CSN if requested, flattening and struct
|
|
169
|
+
// expansion do their magic including foreign key generation and annotation propagation.
|
|
170
|
+
generateDrafts(csn, options, services);
|
|
171
|
+
|
|
164
172
|
// Check if structured elements and managed associations are compared in an expression
|
|
165
173
|
// and expand these structured elements. This tuple expansion allows all other
|
|
166
174
|
// subsequent procession steps (especially a2j) to see plain paths in expressions.
|
|
167
175
|
// If errors are detected, throwWithAnyError() will return from further processing
|
|
168
176
|
expandStructsInExpression(csn, { skipArtifact: isExternalServiceMember, drillRef: true });
|
|
169
177
|
|
|
178
|
+
|
|
170
179
|
if (!structuredOData) {
|
|
171
180
|
expansion.expandStructureReferences(csn, options, '_', { error, info, throwWithAnyError }, csnUtils, { skipArtifact: isExternalServiceMember });
|
|
172
181
|
const resolved = new WeakMap();
|
|
@@ -211,7 +220,6 @@ function transform4odataWithCsn(inputModel, options) {
|
|
|
211
220
|
forEachDefinition(csn, [ setDefaultTypeFacets, processOnCond ], { skipArtifact: isExternalServiceMember });
|
|
212
221
|
|
|
213
222
|
// Now all artificially generated things are in place
|
|
214
|
-
// - Generate artificial draft fields if requested
|
|
215
223
|
// TODO: should be done by the compiler - Check associations for valid foreign keys
|
|
216
224
|
// TODO: check if needed at all: Remove '$projection' from paths in the element's ON-condition
|
|
217
225
|
// - Check associations for:
|
|
@@ -219,7 +227,6 @@ function transform4odataWithCsn(inputModel, options) {
|
|
|
219
227
|
// - structured types must not contain associations for OData V2
|
|
220
228
|
// - Element must not be an 'array of' for OData V2 TODO: move to the validator
|
|
221
229
|
// - Perform checks for exposed non-abstract entities and views - check media type and key-ness
|
|
222
|
-
generateDrafts(csn, options, services)
|
|
223
230
|
|
|
224
231
|
// Deal with all kind of annotations manipulations here
|
|
225
232
|
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
|
|
|
@@ -809,8 +813,6 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
809
813
|
if(rhs.mixin)
|
|
810
814
|
{
|
|
811
815
|
if (hasDollarSelfPrefix) {
|
|
812
|
-
hasDollarSelfPrefix = true;
|
|
813
|
-
|
|
814
816
|
/* Do the $projection resolution ONLY in own query not for referenced forward ON condition
|
|
815
817
|
view YP as select from Y mixin ( toXP: association to XP on $projection.yid = toXP.xid; } into { yid };
|
|
816
818
|
view XP as select from X mixin { toYP: association to YP on $self = toYP.toXP; } into { xid, toYP.elt };
|
|
@@ -1065,9 +1067,9 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
1065
1067
|
tableAlias = [ aliasName, _artifact, _navigation ]
|
|
1066
1068
|
path = [ { id: ..., _artifact: ... (unused) } ]
|
|
1067
1069
|
*/
|
|
1068
|
-
function
|
|
1070
|
+
function rewritePathsInFilterExpression(node, getTableAliasAndPathSteps, env)
|
|
1069
1071
|
{
|
|
1070
|
-
|
|
1072
|
+
const innerEnv = {
|
|
1071
1073
|
lead: env.lead,
|
|
1072
1074
|
location: env.location,
|
|
1073
1075
|
position: env.position,
|
|
@@ -1076,9 +1078,15 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
1076
1078
|
callback: [
|
|
1077
1079
|
function(pathNode) {
|
|
1078
1080
|
if(checkPathDictionary(pathNode, env)) {
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
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
|
+
}
|
|
1082
1090
|
}
|
|
1083
1091
|
} ]
|
|
1084
1092
|
};
|
|
@@ -1317,8 +1325,8 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
1317
1325
|
// we need to unambiguously identify the target side with the full assoc prefix.
|
|
1318
1326
|
// If the path is on the target side, strip the prefix of and treat src/tgt
|
|
1319
1327
|
// paths uniformly.
|
|
1320
|
-
path = (env.assocStack
|
|
1321
|
-
|
|
1328
|
+
path = (env.assocStack?.stripAssocPrefix(path) || path);
|
|
1329
|
+
const lead = env.art || env.lead;
|
|
1322
1330
|
path.forEach((ps) => {
|
|
1323
1331
|
/* checks for all path steps */
|
|
1324
1332
|
if(ps.args) {
|
|
@@ -1715,6 +1723,7 @@ function constructPathNode(pathSteps, alias, rewritten=true)
|
|
|
1715
1723
|
o[k] = p[k];
|
|
1716
1724
|
});
|
|
1717
1725
|
setProp(o, '_artifact', p._artifact );
|
|
1726
|
+
setProp(o, '_navigation', p._navigation );
|
|
1718
1727
|
return o; })
|
|
1719
1728
|
};
|
|
1720
1729
|
|
|
@@ -1760,8 +1769,6 @@ function walkQuery(query, env)
|
|
|
1760
1769
|
walk(query.having, env);
|
|
1761
1770
|
env.location = 'OrderBy';
|
|
1762
1771
|
walk(query.orderBy, env);
|
|
1763
|
-
// outer orderBy's of anonymous union
|
|
1764
|
-
walk(query.$orderBy, env);
|
|
1765
1772
|
if(query.limit) {
|
|
1766
1773
|
env.location = 'Limit';
|
|
1767
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.
|