@sap/cds-compiler 4.2.4 → 4.3.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.
Files changed (66) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/bin/cdsc.js +8 -0
  3. package/bin/cdshi.js +3 -3
  4. package/doc/CHANGELOG_BETA.md +7 -0
  5. package/lib/api/main.js +19 -0
  6. package/lib/base/location.js +16 -0
  7. package/lib/base/message-registry.js +47 -16
  8. package/lib/base/messages.js +49 -38
  9. package/lib/base/model.js +1 -1
  10. package/lib/checks/checkPathsInStoredCalcElement.js +83 -0
  11. package/lib/checks/existsExpressionsOnlyForeignKeys.js +71 -0
  12. package/lib/checks/existsMustEndInAssoc.js +27 -0
  13. package/lib/checks/onConditions.js +47 -1
  14. package/lib/checks/validator.js +10 -1
  15. package/lib/compiler/assert-consistency.js +23 -15
  16. package/lib/compiler/base.js +31 -14
  17. package/lib/compiler/builtins.js +21 -20
  18. package/lib/compiler/checks.js +36 -49
  19. package/lib/compiler/define.js +71 -91
  20. package/lib/compiler/extend.js +27 -25
  21. package/lib/compiler/finalize-parse-cdl.js +1 -1
  22. package/lib/compiler/generate.js +67 -87
  23. package/lib/compiler/kick-start.js +9 -5
  24. package/lib/compiler/populate.js +32 -30
  25. package/lib/compiler/propagator.js +2 -0
  26. package/lib/compiler/resolve.js +29 -25
  27. package/lib/compiler/shared.js +57 -31
  28. package/lib/compiler/tweak-assocs.js +203 -22
  29. package/lib/compiler/utils.js +0 -18
  30. package/lib/gen/Dictionary.json +10 -4
  31. package/lib/gen/language.checksum +1 -1
  32. package/lib/gen/languageParser.js +3 -3
  33. package/lib/inspect/inspectPropagation.js +2 -1
  34. package/lib/json/from-csn.js +63 -28
  35. package/lib/json/to-csn.js +23 -13
  36. package/lib/language/antlrParser.js +1 -1
  37. package/lib/language/errorStrategy.js +5 -1
  38. package/lib/language/genericAntlrParser.js +67 -61
  39. package/lib/main.d.ts +26 -1
  40. package/lib/main.js +2 -1
  41. package/lib/model/csnRefs.js +1 -0
  42. package/lib/model/csnUtils.js +28 -0
  43. package/lib/model/revealInternalProperties.js +3 -9
  44. package/lib/optionProcessor.js +17 -1
  45. package/lib/render/toCdl.js +1 -1
  46. package/lib/transform/db/associations.js +3 -4
  47. package/lib/transform/db/backlinks.js +293 -0
  48. package/lib/transform/db/expansion.js +9 -7
  49. package/lib/transform/db/flattening.js +3 -2
  50. package/lib/transform/db/rewriteCalculatedElements.js +1 -67
  51. package/lib/transform/db/transformExists.js +3 -58
  52. package/lib/transform/db/views.js +8 -14
  53. package/lib/transform/effective/.eslintrc.json +4 -0
  54. package/lib/transform/effective/associations.js +101 -0
  55. package/lib/transform/effective/main.js +88 -0
  56. package/lib/transform/effective/misc.js +61 -0
  57. package/lib/transform/effective/queries.js +42 -0
  58. package/lib/transform/effective/types.js +121 -0
  59. package/lib/transform/forRelationalDB.js +12 -235
  60. package/lib/transform/localized.js +22 -3
  61. package/lib/transform/parseExpr.js +7 -3
  62. package/lib/transform/transformUtils.js +5 -22
  63. package/lib/transform/translateAssocsToJoins.js +44 -39
  64. package/lib/transform/universalCsn/universalCsnEnricher.js +17 -1
  65. package/package.json +1 -2
  66. package/lib/language/language.g4 +0 -3260
@@ -0,0 +1,101 @@
1
+ 'use strict';
2
+
3
+ const { setProp } = require('../../base/model');
4
+ const flattening = require('../db/flattening');
5
+ const {
6
+ applyTransformations, forEachDefinition, forEachMemberRecursively, implicitAs, cloneCsnNonDict,
7
+ } = require('../../model/csnUtils');
8
+ const associations = require('../db/associations');
9
+ const backlinks = require('../db/backlinks');
10
+
11
+
12
+ /**
13
+ * Turn managed associations into unmanaged ones by
14
+ * - unfolding the .keys
15
+ * - creating the foreign keys in .elements
16
+ * - adding a corresponding on-condition
17
+ * @param {CSN.Model} csn Input CSN - will not be transformed
18
+ * @param {CSN.Options} options
19
+ * @param {object} csnUtils
20
+ * @param {object} messageFunctions
21
+ * @param {Function} messageFunctions.error
22
+ * @param {Function} messageFunctions.warning
23
+ * @todo Remove .keys afterwards
24
+ * @todo Add created foreign keys into .columns in case of a query?
25
+ * @returns {CSN.Model}
26
+ */
27
+ function turnAssociationsIntoUnmanaged( csn, options, csnUtils, { error, warning } ) {
28
+ // TODO: Do we really need this?
29
+ forEachDefinition(csn, (artifact, artifactName) => {
30
+ setProp(artifact, '$path', [ 'definitions', artifactName ]);
31
+ forEachMemberRecursively(artifact, (member, memberName, prop, path) => {
32
+ setProp(member, '$path', path);
33
+ }, [ 'definitions', artifactName ]);
34
+ });
35
+ // Flatten out the fks and create the corresponding elements
36
+ flattening.handleManagedAssociationsAndCreateForeignKeys(csn, options, error, warning, '_', true, csnUtils, { allowArtifact: a => a.kind === 'entity' });
37
+
38
+ // Add the foreign keys also to the columns if the association itself was explicitly selected
39
+ // TODO: Extend the expansion to also expand managed to their foreign
40
+ // TODO: Remember where in .elements we had associations and do it then?
41
+ applyTransformations(csn, {
42
+ columns: (parent, prop, columns) => {
43
+ const newColumns = [];
44
+ for (const col of columns) {
45
+ newColumns.push(col);
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' });
53
+
54
+ forEachDefinition(csn, associations.getFKAccessFinalizer(csn, csnUtils, '_'));
55
+ // Calculate the on-conditions from the .keys -
56
+ associations.attachOnConditions(csn, csnUtils, '_');
57
+
58
+ return csn;
59
+ }
60
+
61
+ /**
62
+ * FKs need to be added to the .columns
63
+ * @todo stolen from lib/transform/db/views.js
64
+ * @todo Can we maybe do this during expansion already?
65
+ * @param {object} foreignKey
66
+ * @param {object[]} columns
67
+ * @param {CSN.Column} associationColumn
68
+ * @param {CSN.Options} options
69
+ */
70
+ function addForeignKeyToColumns( foreignKey, columns, associationColumn, options ) {
71
+ const ref = cloneCsnNonDict(associationColumn.ref, options);
72
+ ref[ref.length - 1] = [ implicitAs(ref) ].concat(foreignKey.as || foreignKey.ref).join('_');
73
+ const result = {
74
+ ref,
75
+ };
76
+ if (associationColumn.as) {
77
+ const columnName = `${associationColumn.as}_${foreignKey.as || implicitAs(foreignKey.ref)}`;
78
+ result.as = columnName;
79
+ }
80
+
81
+ if (associationColumn.key)
82
+ result.key = true;
83
+
84
+ columns.push(result);
85
+ }
86
+
87
+ /**
88
+ * Translate $self backlinks to "normal" on-conditions
89
+ * @param {CSN.Model} csn Input CSN - will not be transformed
90
+ * @param {CSN.Options} options
91
+ * @param {object} csnUtils
92
+ * @param {object} messageFunctions
93
+ */
94
+ function transformBacklinks( csn, options, csnUtils, messageFunctions ) {
95
+ forEachDefinition(csn, backlinks.getBacklinkTransformer(csnUtils, messageFunctions, options, '_', true));
96
+ }
97
+
98
+ module.exports = {
99
+ managedToUnmanaged: turnAssociationsIntoUnmanaged,
100
+ transformBacklinks,
101
+ };
@@ -0,0 +1,88 @@
1
+ 'use strict';
2
+
3
+ const { isBetaEnabled } = require('../../base/model');
4
+ const { CompilerAssertion } = require('../../base/error');
5
+ const { makeMessageFunction } = require('../../base/messages');
6
+ const { cloneCsnNonDict, getUtils, isAspect } = require('../../model/csnUtils');
7
+ const transformUtils = require('../transformUtils');
8
+ const flattening = require('../db/flattening');
9
+ const types = require('./types');
10
+ // const { addLocalizationViews } = require('../../transform/localized');
11
+ const validate = require('../../checks/validator');
12
+ const expansion = require('../db/expansion');
13
+ const queries = require('./queries');
14
+ const associations = require('./associations');
15
+ const generateDrafts = require('../draft/db');
16
+ const handleExists = require('../db/transformExists');
17
+ const misc = require('./misc');
18
+ const { rewriteCalculatedElementsInViews, processCalculatedElementsInEntities } = require('../db/rewriteCalculatedElements');
19
+
20
+ /**
21
+ * This is just a PoC for now!
22
+ *
23
+ * Transform the given CSN into a so called effective CSN, by
24
+ * - dissolving structured types
25
+ * - turning managed into unmanaged associations
26
+ * @private
27
+ * @param {CSN.Model} model Input CSN - will not be transformed
28
+ * @param {CSN.Options} options
29
+ * @returns {CSN.Model}
30
+ */
31
+ function effectiveCsn( model, options ) {
32
+ if (!isBetaEnabled(options, 'effectiveCsn'))
33
+ throw new CompilerAssertion('effective CSN is only supported with beta flag `effectiveCsn`!');
34
+
35
+ const csn = cloneCsnNonDict(model, options);
36
+
37
+ const { expandStructsInExpression } = transformUtils.getTransformers(csn, options, '_');
38
+ queries.projectionToSELECTAndAddColumns(csn);
39
+
40
+ let csnUtils = getUtils(csn, 'init-all');
41
+ const messageFunctions = makeMessageFunction(csn, options, 'for.effective');
42
+
43
+ // Run validations on CSN - each validator function has access to the message functions and the inspect ref via this
44
+ const cleanup = validate.forRelationalDB(csn, {
45
+ ...messageFunctions, csnUtils, ...csnUtils, csn, options, isAspect,
46
+ });
47
+
48
+ rewriteCalculatedElementsInViews(csn, options, csnUtils, '_', messageFunctions);
49
+
50
+ // Needs to happen before tuple expansion, so the newly generated WHERE-conditions have it applied
51
+ handleExists(csn, options, messageFunctions.error, csnUtils.inspectRef, csnUtils.initDefinition, csnUtils.dropDefinitionCache);
52
+
53
+ // Check if structured elements and managed associations are compared in an expression
54
+ // and expand these structured elements. This tuple expansion allows all other
55
+ // subsequent procession steps to see plain paths in expressions.
56
+ // If errors are detected, throwWithAnyError() will return from further processing
57
+ expandStructsInExpression(csn, { drillRef: true });
58
+
59
+ messageFunctions.throwWithAnyError();
60
+
61
+ const resolveTypesInActionsAfterFlattening = types.resolve(csn, csnUtils);
62
+
63
+ // Expand a structured thing in: keys, columns, order by, group by
64
+ expansion.expandStructureReferences(csn, options, '_', messageFunctions, csnUtils);
65
+
66
+ csnUtils = getUtils(csn, 'init-all');
67
+
68
+ // Remove properties attached by validator - they do not "grow" as the model grows.
69
+ cleanup();
70
+
71
+ flattening.flattenAllStructStepsInRefs(csn, options, new WeakMap(), '_');
72
+ flattening.flattenElements(csn, options, '_', messageFunctions.error);
73
+
74
+ resolveTypesInActionsAfterFlattening();
75
+
76
+ processCalculatedElementsInEntities(csn);
77
+ associations.managedToUnmanaged(csn, options, csnUtils, messageFunctions);
78
+ associations.transformBacklinks(csn, options, csnUtils, messageFunctions);
79
+ generateDrafts(csn, options, '_', messageFunctions);
80
+ misc.attachPersistenceName(csn, options, csnUtils);
81
+ misc.removeDefinitionsAndProperties(csn);
82
+
83
+ messageFunctions.throwWithError();
84
+
85
+ return csn;
86
+ }
87
+
88
+ module.exports = { effectiveCsn };
@@ -0,0 +1,61 @@
1
+ 'use strict';
2
+
3
+ const {
4
+ forEachDefinition, forEachMemberRecursively, getArtifactDatabaseNameOf, getElementDatabaseNameOf, forEachMember,
5
+ } = require('../../model/csnUtils');
6
+ /**
7
+ * Attach @cds.persistence.name to all artifacts and "things".
8
+ * We could also do it more selectively like we do in forRelationalDb, but: Why? Space maybe?
9
+ * @param {CSN.Model} csn
10
+ * @param {CSN.Options} options
11
+ * @param {object} csnUtils
12
+ */
13
+ function attachPersistenceName( csn, options, csnUtils ) {
14
+ const { addStringAnnotationTo } = csnUtils;
15
+
16
+ forEachDefinition(csn, (artifact, artifactName) => {
17
+ if (artifact.kind === 'entity') {
18
+ addStringAnnotationTo('@cds.persistence.name', getArtifactDatabaseNameOf(artifactName, options.sqlMapping, csn, options.sqlDialect), artifact);
19
+
20
+ forEachMemberRecursively(artifact, (member, memberName) => addStringAnnotationTo('@cds.persistence.name', getElementDatabaseNameOf(memberName, options.sqlMapping, options.sqlDialect), member), [ 'definitions', artifactName ]);
21
+ }
22
+ });
23
+ }
24
+
25
+ const artifactPropertiesToRemove = [ 'includes' ];
26
+ const memberPropertiesToRemove = [ 'localized', 'enum', 'keys' ];
27
+
28
+ /**
29
+ * Remove definitions from the CSN:
30
+ * - types
31
+ * - aspects
32
+ *
33
+ * Remove properties from artifacts:
34
+ * - includes
35
+ * - localized
36
+ * @param {CSN.Model} csn
37
+ * @todo Callback-like architecture and merge with persistence name?
38
+ */
39
+ function removeDefinitionsAndProperties( csn ) {
40
+ forEachDefinition(csn, (artifact, artifactName) => {
41
+ if (artifact.kind === 'aspect' || artifact.kind === 'type') {
42
+ delete csn.definitions[artifactName];
43
+ }
44
+ else {
45
+ if (artifact['@cds.persistence.skip'] === 'if-unused')
46
+ artifact['@cds.persistence.skip'] = false;
47
+ for (const prop of artifactPropertiesToRemove)
48
+ delete artifact[prop];
49
+
50
+ forEachMember(artifact, (member) => {
51
+ for (const prop of memberPropertiesToRemove)
52
+ delete member[prop];
53
+ });
54
+ }
55
+ });
56
+ }
57
+
58
+ module.exports = {
59
+ attachPersistenceName,
60
+ removeDefinitionsAndProperties,
61
+ };
@@ -0,0 +1,42 @@
1
+ 'use strict';
2
+
3
+ const { forEachDefinition, applyTransformationsOnNonDictionary } = require('../../model/csnUtils');
4
+
5
+ /**
6
+ * - Make .projections look like simple SELECTS
7
+ * - ensure we always have a .columns by adding a .columns = ['*'] if none is present
8
+ * @param {CSN.Model} csn
9
+ * @returns {Function[]} Callbacks to re-add the .projection
10
+ */
11
+ function projectionToSELECTAndAddColumns( csn ) {
12
+ const redoProjections = [];
13
+ forEachDefinition(csn, (artifact) => {
14
+ if (artifact.kind === 'entity') {
15
+ if (artifact.projection) {
16
+ if (!artifact.projection.columns)
17
+ artifact.projection.columns = [ '*' ];
18
+ artifact.query = { SELECT: artifact.projection };
19
+ delete artifact.projection;
20
+ redoProjections.push(() => {
21
+ if (artifact.query) {
22
+ artifact.projection = artifact.query.SELECT;
23
+ delete artifact.query;
24
+ if (artifact.$syntax === 'projection')
25
+ delete artifact.$syntax;
26
+ }
27
+ });
28
+ }
29
+ else if (artifact.query) {
30
+ applyTransformationsOnNonDictionary(artifact, 'query', {
31
+ SELECT: (parent, prop, SELECT) => {
32
+ SELECT.columns ??= [ '*' ];
33
+ },
34
+ });
35
+ }
36
+ }
37
+ });
38
+
39
+ return redoProjections;
40
+ }
41
+
42
+ module.exports = { projectionToSELECTAndAddColumns };
@@ -0,0 +1,121 @@
1
+ 'use strict';
2
+
3
+ const {
4
+ cloneCsnNonDict, applyTransformations, applyTransformationsOnNonDictionary, cloneCsnDictionary, applyTransformationsOnDictionary,
5
+ } = require('../../model/csnUtils');
6
+
7
+ /**
8
+ * Resolve all references to structured types in entities to the underlying elements.
9
+ * Resolve all simple type refs to their cds builtin type.
10
+ *
11
+ * When setting the elements, we deeply clone them from the type to avoid accidental changes
12
+ * since they are linked by a reference.
13
+ * @todo What about annotations on the type?
14
+ * @param {CSN.Model} csn will be transformed
15
+ * @param {object} csnUtils
16
+ * @returns {Function} Callback to resolve things (actions and their returns) later - as for them, $self would lead to unresolvable constructs at this point
17
+ * so we can call this callback after flattening is done - then we can safely resolve their types.
18
+ */
19
+ function resolveTypes( csn, csnUtils ) {
20
+ const { getFinalTypeInfo } = csnUtils;
21
+ const later = [];
22
+ applyTransformations(csn, {
23
+ type: (parent, prop, type, path) => {
24
+ const artifact = csn.definitions[path[1]];
25
+ // TODO: What about events?
26
+ if (!(artifact.kind === 'action' || artifact.kind === 'function'))
27
+ resolveType(parent);
28
+ },
29
+ }, [ (definitions, artifactName, artifact) => {
30
+ // In a non-flat model, replacing types with some $self inside causes issues for actions (bound or unbound)
31
+ // we remember them and replace them after flattening.
32
+ if (artifact.kind === 'action' || artifact.kind === 'function')
33
+ later.push({ [artifactName]: artifact });
34
+ else if (artifact.actions)
35
+ later.push(artifact.actions);
36
+ } ], { skipDict: { actions: true } });
37
+
38
+ return function resolveTypesInActions() {
39
+ later.forEach(action => applyTransformationsOnDictionary(action, { type: parent => resolveType(parent) }));
40
+ };
41
+
42
+ /**
43
+ * Resolve a type to its
44
+ * - elements
45
+ * - items
46
+ * - basic builtin
47
+ *
48
+ * Drill down into .elements and .items
49
+ *
50
+ * @param {object} parent Object with a .type propertie
51
+ */
52
+ function resolveType( parent ) {
53
+ // TODO: I assume there can be cases with a type ref but still having .elements already? Subelement anno?
54
+ const final = getFinalTypeInfo(parent.type);
55
+ if (final?.elements) {
56
+ // We do full clones so users don't get unexpected linkage later
57
+ if (!parent.elements)
58
+ parent.elements = cloneCsnDictionary(final.elements);
59
+ delete parent.type;
60
+ }
61
+ else if (final && final.items) {
62
+ if (!parent.items)
63
+ parent.items = cloneCsnNonDict(final.items);
64
+ delete parent.type;
65
+ }
66
+ else if (final?.type) {
67
+ parent.type = final.type;
68
+ }
69
+
70
+ // Drill down - there might be other type references
71
+ const stack = [ ];
72
+ if (parent.elements || parent.items)
73
+ stack.push(parent);
74
+ while (stack.length > 0) {
75
+ const obj = stack.pop();
76
+ if (obj.elements) {
77
+ applyTransformationsOnDictionary(obj.elements, {
78
+ type: getTypeTransformer(stack),
79
+ }, { skipDict: { actions: true } });
80
+ }
81
+ else if (obj.items) {
82
+ applyTransformationsOnNonDictionary(obj, 'items', {
83
+ type: getTypeTransformer(stack),
84
+ }, { skipDict: { actions: true } });
85
+ }
86
+ }
87
+ }
88
+
89
+ /**
90
+ * Function to transform a type to its most basic thing
91
+ * @param {object[]} stack
92
+ * @returns {Function}
93
+ */
94
+ function getTypeTransformer( stack ) {
95
+ return function typeTransformer( _parent, _prop, _type ) {
96
+ const finalSub = getFinalTypeInfo(_type);
97
+
98
+ if (finalSub?.elements) {
99
+ // We do full clones so users don't get unexpected linkage later
100
+ if (!_parent.elements)
101
+ _parent.elements = cloneCsnDictionary(finalSub.elements);
102
+ delete _parent.type;
103
+ stack.push( _parent );
104
+ }
105
+ else if (finalSub?.items) {
106
+ if (!_parent.items)
107
+ _parent.items = cloneCsnNonDict(finalSub.items);
108
+ delete _parent.type;
109
+ stack.push( _parent );
110
+ }
111
+ else if (finalSub?.type) {
112
+ _parent.type = finalSub.type;
113
+ }
114
+ };
115
+ }
116
+ }
117
+
118
+
119
+ module.exports = {
120
+ resolve: resolveTypes,
121
+ };