@sap/cds-compiler 2.4.4 → 2.10.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 (106) hide show
  1. package/CHANGELOG.md +241 -1
  2. package/bin/.eslintrc.json +17 -0
  3. package/bin/cds_update_identifiers.js +8 -7
  4. package/bin/cdsc.js +180 -132
  5. package/bin/cdshi.js +18 -11
  6. package/bin/cdsse.js +38 -32
  7. package/bin/cdsv2m.js +8 -7
  8. package/doc/CHANGELOG_BETA.md +36 -1
  9. package/lib/api/main.js +81 -100
  10. package/lib/api/options.js +17 -11
  11. package/lib/api/validate.js +12 -8
  12. package/lib/backends.js +0 -81
  13. package/lib/base/keywords.js +32 -2
  14. package/lib/base/location.js +2 -2
  15. package/lib/base/message-registry.js +66 -4
  16. package/lib/base/messages.js +84 -27
  17. package/lib/base/model.js +2 -61
  18. package/lib/checks/arrayOfs.js +0 -1
  19. package/lib/checks/defaultValues.js +27 -2
  20. package/lib/checks/elements.js +1 -6
  21. package/lib/checks/enricher.js +8 -2
  22. package/lib/checks/foreignKeys.js +0 -6
  23. package/lib/checks/managedWithoutKeys.js +17 -0
  24. package/lib/checks/nonexpandableStructured.js +38 -0
  25. package/lib/checks/onConditions.js +9 -45
  26. package/lib/checks/queryNoDbArtifacts.js +27 -9
  27. package/lib/checks/selectItems.js +25 -2
  28. package/lib/checks/types.js +26 -2
  29. package/lib/checks/unknownMagic.js +38 -0
  30. package/lib/checks/utils.js +61 -0
  31. package/lib/checks/validator.js +66 -13
  32. package/lib/compiler/assert-consistency.js +24 -12
  33. package/lib/compiler/builtins.js +2 -0
  34. package/lib/compiler/checks.js +6 -4
  35. package/lib/compiler/definer.js +101 -39
  36. package/lib/compiler/index.js +88 -59
  37. package/lib/compiler/resolver.js +455 -209
  38. package/lib/compiler/shared.js +57 -33
  39. package/lib/edm/annotations/genericTranslation.js +183 -187
  40. package/lib/edm/csn2edm.js +128 -99
  41. package/lib/edm/edm.js +18 -21
  42. package/lib/edm/edmPreprocessor.js +361 -127
  43. package/lib/edm/edmUtils.js +103 -33
  44. package/lib/gen/Dictionary.json +74 -28
  45. package/lib/gen/language.checksum +1 -1
  46. package/lib/gen/language.interp +18 -4
  47. package/lib/gen/language.tokens +124 -118
  48. package/lib/gen/languageLexer.interp +13 -1
  49. package/lib/gen/languageLexer.js +870 -839
  50. package/lib/gen/languageLexer.tokens +116 -111
  51. package/lib/gen/languageParser.js +5894 -5614
  52. package/lib/json/from-csn.js +152 -67
  53. package/lib/json/to-csn.js +334 -135
  54. package/lib/language/antlrParser.js +4 -3
  55. package/lib/language/errorStrategy.js +1 -0
  56. package/lib/language/genericAntlrParser.js +24 -14
  57. package/lib/language/language.g4 +188 -128
  58. package/lib/main.d.ts +435 -0
  59. package/lib/main.js +31 -7
  60. package/lib/model/api.js +78 -0
  61. package/lib/model/csnRefs.js +463 -187
  62. package/lib/model/csnUtils.js +280 -136
  63. package/lib/model/enrichCsn.js +75 -4
  64. package/lib/model/revealInternalProperties.js +2 -1
  65. package/lib/modelCompare/compare.js +70 -25
  66. package/lib/optionProcessor.js +13 -10
  67. package/lib/render/.eslintrc.json +4 -1
  68. package/lib/render/DuplicateChecker.js +8 -5
  69. package/lib/render/toCdl.js +123 -40
  70. package/lib/render/toHdbcds.js +156 -65
  71. package/lib/render/toSql.js +87 -11
  72. package/lib/render/utils/common.js +55 -9
  73. package/lib/render/utils/sql.js +3 -3
  74. package/lib/sql-identifier.js +6 -1
  75. package/lib/transform/{sql → db}/.eslintrc.json +0 -0
  76. package/lib/transform/{sql → db}/assertUnique.js +7 -8
  77. package/lib/transform/{sql → db}/constraints.js +35 -20
  78. package/lib/transform/db/draft.js +353 -0
  79. package/lib/transform/db/expansion.js +582 -0
  80. package/lib/transform/db/flattening.js +325 -0
  81. package/lib/transform/{sql → db}/groupByOrderBy.js +8 -16
  82. package/lib/transform/{sql → db}/helpers.js +0 -0
  83. package/lib/transform/{sql → db}/transformExists.js +256 -60
  84. package/lib/transform/forHanaNew.js +216 -765
  85. package/lib/transform/forOdataNew.js +60 -56
  86. package/lib/transform/localized.js +48 -26
  87. package/lib/transform/odata/attachPath.js +19 -4
  88. package/lib/transform/odata/expandStructKeysInAssociations.js +2 -2
  89. package/lib/transform/odata/generateForeignKeyElements.js +13 -12
  90. package/lib/transform/odata/referenceFlattener.js +60 -36
  91. package/lib/transform/odata/sortByAssociationDependency.js +4 -4
  92. package/lib/transform/odata/structuralPath.js +76 -0
  93. package/lib/transform/odata/structureFlattener.js +21 -22
  94. package/lib/transform/odata/toFinalBaseType.js +5 -5
  95. package/lib/transform/odata/typesExposure.js +27 -17
  96. package/lib/transform/odata/utils.js +2 -2
  97. package/lib/transform/transformUtilsNew.js +141 -77
  98. package/lib/transform/translateAssocsToJoins.js +17 -14
  99. package/lib/transform/universalCsnEnricher.js +67 -0
  100. package/lib/utils/file.js +0 -11
  101. package/lib/utils/moduleResolve.js +6 -8
  102. package/lib/utils/timetrace.js +6 -1
  103. package/package.json +2 -1
  104. package/lib/base/deepCopy.js +0 -66
  105. package/lib/json/walker.js +0 -26
  106. package/lib/utils/string.js +0 -17
@@ -0,0 +1,353 @@
1
+ 'use strict';
2
+
3
+ const {
4
+ hasAnnotationValue, getUtils, getServiceNames, forEachDefinition,
5
+ getResultingName, forEachMemberRecursively,
6
+ } = require('../../model/csnUtils');
7
+ const { setProp, isDeprecatedEnabled } = require('../../base/model');
8
+ const { getTransformers } = require('../transformUtilsNew');
9
+ const draftAnnotation = '@odata.draft.enabled';
10
+ const booleanBuiltin = 'cds.Boolean';
11
+
12
+ /**
13
+ * Generate all the different entities/views/fields required for DRAFT.
14
+ *
15
+ * @param {CSN.Model} csn
16
+ * @param {CSN.Options} options
17
+ * @param {string} pathDelimiter
18
+ * @param {object} messageFunctions
19
+ */
20
+ function generateDrafts(csn, options, pathDelimiter, messageFunctions) {
21
+ const draftSuffix = isDeprecatedEnabled(options, 'generatedEntityNameWithUnderscore') ? '_drafts' : '.drafts';
22
+ // All services of the model - needed for drafts
23
+ const allServices = getServiceNames(csn);
24
+ const {
25
+ createForeignKeyElement, createAndAddDraftAdminDataProjection, createScalarElement, createAssociationElement,
26
+ addElement, copyAndAddElement, createAssociationPathComparison,
27
+ } = getTransformers(csn, options, pathDelimiter);
28
+ const { getCsnDef, isComposition } = getUtils(csn);
29
+ const { error, warning } = messageFunctions;
30
+
31
+ forEachDefinition(csn, generateDraft);
32
+
33
+ /**
34
+ * Generate the draft stuff for a given artifact
35
+ *
36
+ * @param {CSN.Artifact} artifact
37
+ * @param {string} artifactName
38
+ */
39
+ function generateDraft(artifact, artifactName) {
40
+ if ((artifact.kind === 'entity' || artifact.kind === 'view') &&
41
+ hasAnnotationValue(artifact, draftAnnotation) &&
42
+ isPartOfService(artifactName)) {
43
+ // Determine the set of target draft nodes belonging to this draft root (the draft root
44
+ // itself plus all its transitively composition-reachable targets)
45
+ const draftNodes = Object.create(null);
46
+ collectDraftNodesInto(artifact, artifactName, artifact, draftNodes);
47
+ // Draft-enable all of them
48
+ for (const name in draftNodes)
49
+ generateDraftForHana(draftNodes[name], name, artifactName);
50
+
51
+ // Redirect associations/compositions between draft shadow nodes
52
+ for (const name in draftNodes) {
53
+ const shadowNode = csn.definitions[`${name}${draftSuffix}`];
54
+ // Might not exist because of previous errors
55
+ if (shadowNode)
56
+ redirectDraftTargets(csn.definitions[`${name}${draftSuffix}`], draftNodes);
57
+ }
58
+ }
59
+ }
60
+
61
+ /**
62
+ * Collect all artifacts that are transitively reachable via compositions from 'artifact' into 'draftNodes'.
63
+ * Check that no artifact other than the root node has '@odata.draft.enabled'
64
+ *
65
+ * @param {CSN.Artifact} artifact
66
+ * @param {string} artifactName
67
+ * @param {CSN.Artifact} rootArtifact root artifact where composition traversal started.
68
+ * @param {object} draftNodes Dictionary of artifacts
69
+ */
70
+ function collectDraftNodesInto(artifact, artifactName, rootArtifact, draftNodes) {
71
+ // Collect the artifact itself
72
+ draftNodes[artifactName] = artifact;
73
+ // Follow all composition targets in elements of 'artifact'
74
+ for (const elemName in artifact.elements) {
75
+ const elem = artifact.elements[elemName];
76
+ if (elem.target && isComposition(elem.type)) {
77
+ const draftNode = getCsnDef(elem.target);
78
+ const draftNodeName = elem.target;
79
+ // Sanity check
80
+ if (!draftNode)
81
+ throw new Error(`Expecting target to be resolved: ${JSON.stringify(elem, null, 2)}`);
82
+
83
+ // Ignore composition if not part of a service
84
+ if (!isPartOfService(draftNodeName)) {
85
+ warning(null, [ 'definitions', artifactName, 'elements', elemName ], { target: draftNodeName },
86
+ 'Ignoring draft node for composition target $(TARGET) because it is not part of a service');
87
+ continue;
88
+ }
89
+ // Barf if a draft node other than the root has @odata.draft.enabled itself
90
+ if (draftNode !== rootArtifact && hasAnnotationValue(draftNode, draftAnnotation)) {
91
+ error(null, [ 'definitions', artifactName, 'elements', elemName ], 'Composition in draft-enabled entity can\'t lead to another entity with “@odata.draft.enabled”');
92
+ delete draftNodes[draftNodeName];
93
+ continue;
94
+ }
95
+ // Recurse unless already known
96
+ if (!hasAnnotationValue(draftNode, draftAnnotation, false) && !draftNodes[draftNodeName])
97
+ collectDraftNodesInto(draftNode, draftNodeName, rootArtifact, draftNodes);
98
+ }
99
+ }
100
+ }
101
+
102
+ /**
103
+ * Generate all that is required in HANA CDS for draft enablement of 'artifact'.
104
+ *
105
+ * @param {CSN.Artifact} artifact
106
+ * @param {string} artifactName
107
+ * @param {string} draftRootName
108
+ */
109
+ function generateDraftForHana(artifact, artifactName, draftRootName) {
110
+ // Sanity check
111
+ if (!isPartOfService(artifactName))
112
+ throw new Error(`Expecting artifact to be part of a service: ${JSON.stringify(artifact)}`);
113
+
114
+
115
+ // The name of the draft shadow entity we should generate
116
+ const draftsArtifactName = `${artifactName}${draftSuffix}`;
117
+
118
+ // extract keys for UUID inspection
119
+ const keys = [];
120
+ forEachMemberRecursively(artifact, (elt, name, prop, path) => {
121
+ if (!elt.elements && !elt.type && !elt.virtual) // only check leafs
122
+ error(null, path, 'Expecting element to have a type when used in a draft-enabled artifact');
123
+ if (elt.key && elt.key === true && !elt.virtual)
124
+ keys.push(elt);
125
+ }, [ 'definitions', artifactName ], true, { elementsOnly: true });
126
+
127
+ // In contrast to EDM, the DB entity may have more than one technical keys but should have idealy exactly one key of type cds.UUID
128
+ if (keys.length !== 1)
129
+ warning(null, [ 'definitions', artifactName ], 'Entity annotated with “@odata.draft.enabled” should have exactly one key element');
130
+
131
+ const uuidCount = keys.reduce((acc, k) => ((k.type === 'cds.String' && k.$renamed === 'cds.UUID' && k.length === 36) ? acc + 1 : acc), 0);
132
+ if (uuidCount === 0)
133
+ warning(null, [ 'definitions', artifactName ], 'Entity annotated with “@odata.draft.enabled” should have one key element of type “cds.UUID”');
134
+
135
+
136
+ const matchingService = getMatchingService(artifactName);
137
+ // Generate the DraftAdministrativeData projection into the service, unless there is already one
138
+ const draftAdminDataProjectionName = `${matchingService}.DraftAdministrativeData`;
139
+ let draftAdminDataProjection = csn.definitions[draftAdminDataProjectionName];
140
+ if (!draftAdminDataProjection) {
141
+ draftAdminDataProjection = createAndAddDraftAdminDataProjection(matchingService, true);
142
+
143
+ if (!draftAdminDataProjection.projection.columns && draftAdminDataProjection.elements.DraftUUID)
144
+ draftAdminDataProjection.projection.columns = Object.keys(draftAdminDataProjection.elements).map(e => (e === 'DraftUUID' ? { key: true, ref: [ 'DraftAdministrativeData', e ] } : { ref: [ 'DraftAdministrativeData', e ] }));
145
+ }
146
+
147
+ // Barf if it is not an entity or not what we expect
148
+ if (draftAdminDataProjection.kind !== 'entity' || !draftAdminDataProjection.elements.DraftUUID) {
149
+ // See draftAdminDataProjection which is defined in `csn.definitions`.
150
+ const path = [ 'definitions', draftAdminDataProjectionName ];
151
+ error(null, path, { name: draftAdminDataProjectionName },
152
+ 'Generated entity $(NAME) conflicts with existing artifact');
153
+ }
154
+
155
+ const persistenceName = getResultingName(csn, options.forHana.names, draftsArtifactName);
156
+ // Duplicate the artifact as a draft shadow entity
157
+ if (csn.definitions[persistenceName]) {
158
+ const definingDraftRoot = csn.definitions[persistenceName].$draftRoot;
159
+ if (!definingDraftRoot) {
160
+ error(null, [ 'definitions', artifactName ], { name: persistenceName },
161
+ 'Generated entity name $(NAME) conflicts with existing entity');
162
+ }
163
+
164
+ else {
165
+ error(null, [ 'definitions', draftRootName ], { name: persistenceName },
166
+ `Entity $(NAME) already generated by draft root "${definingDraftRoot}"`);
167
+ }
168
+
169
+ return;
170
+ }
171
+ const draftsArtifact = {
172
+ kind: 'entity',
173
+ elements: Object.create(null),
174
+ };
175
+
176
+ // Add draft shadow entity to the csn
177
+ csn.definitions[draftsArtifactName] = draftsArtifact;
178
+
179
+ setProp(draftsArtifact, '$draftRoot', draftRootName);
180
+ if (artifact.$location)
181
+ setProp(draftsArtifact, '$location', artifact.$location);
182
+
183
+ // Copy all elements
184
+ for (const elemName in artifact.elements) {
185
+ const origElem = artifact.elements[elemName];
186
+ let elem;
187
+ if ((isDeprecatedEnabled(options, 'renderVirtualElements') && origElem.virtual) || !origElem.virtual)
188
+ elem = copyAndAddElement(origElem, draftsArtifact, draftsArtifactName, elemName)[elemName];
189
+ if (elem) {
190
+ // Remove "virtual" - cap/issues 4956
191
+ if (elem.virtual)
192
+ delete elem.virtual;
193
+
194
+ // explicitly set nullable if not key and not unmanaged association
195
+ if (!elem.key && !elem.on)
196
+ elem.notNull = false;
197
+ }
198
+ }
199
+
200
+ // Generate the additional elements into the draft-enabled artifact
201
+
202
+ // key IsActiveEntity : Boolean default true
203
+ const isActiveEntity = createScalarElement('IsActiveEntity', booleanBuiltin, false);
204
+ // Use artifactName and not draftsArtifactName because otherwise we may point to the generated
205
+ // entity in CSN and won't get a proper location (draftsArtifact has inherited all
206
+ // elements from the original artifact).
207
+ addElement(isActiveEntity, draftsArtifact, artifactName);
208
+
209
+ // HasActiveEntity : Boolean default false
210
+ const hasActiveEntity = createScalarElement('HasActiveEntity', booleanBuiltin, false);
211
+ addElement(hasActiveEntity, draftsArtifact, artifactName);
212
+
213
+ // HasDraftEntity : Boolean default false;
214
+ const hasDraftEntity = createScalarElement('HasDraftEntity', booleanBuiltin, false);
215
+ addElement(hasDraftEntity, draftsArtifact, artifactName);
216
+
217
+ // DraftAdministrativeData : Association to one DraftAdministrativeData not null;
218
+ const draftAdministrativeData = createAssociationElement('DraftAdministrativeData', draftAdminDataProjectionName, true);
219
+ draftAdministrativeData.DraftAdministrativeData.cardinality = {
220
+ max: 1,
221
+ };
222
+ draftAdministrativeData.DraftAdministrativeData.notNull = true;
223
+ addElement(draftAdministrativeData, draftsArtifact, artifactName);
224
+ // Note that we may need to do the HANA transformation steps for managed associations
225
+ // (foreign key field generation, generatedFieldName, creating ON-condition) by hand,
226
+ // because the corresponding transformation steps have already been done on all artifacts
227
+ // when we come here). Only for to.hdbcds with hdbcds names this is not required.
228
+ /**
229
+ * The given association has a key named DraftUUID
230
+ *
231
+ * @param {CSN.Association} association Assoc to check
232
+ * @returns {object}
233
+ */
234
+ function getDraftUUIDKey(association) {
235
+ if (association.keys) {
236
+ const filtered = association.keys.filter(o => (o.ref && !o.as && o.ref.length === 1 && o.ref[0] === 'DraftUUID') || (o.as && o.as === 'DraftUUID'));
237
+ if (filtered.length === 1)
238
+ return filtered[0];
239
+
240
+ else if (filtered.length > 1)
241
+ return filtered.filter(o => o.as && o.as === 'DraftUUID');
242
+ }
243
+
244
+ return undefined;
245
+ }
246
+
247
+ /**
248
+ * Get the resulting name for an obj - explicit or implicit alias
249
+ *
250
+ * @param {object} obj Any object with at least "ref"
251
+ * @returns {string}
252
+ */
253
+ function getNameForRef(obj) {
254
+ if (obj.as)
255
+ return obj.as;
256
+
257
+ return obj.ref[obj.ref.length - 1];
258
+ }
259
+
260
+ const draftUUIDKey = getDraftUUIDKey(draftAdministrativeData.DraftAdministrativeData);
261
+ if (!(options.transformation === 'hdbcds' && options.sqlMapping === 'hdbcds') && draftUUIDKey) {
262
+ const path = [ 'definitions', draftsArtifactName, 'elements', 'DraftAdministrativeData', 'keys', 0 ];
263
+ createForeignKeyElement(draftAdministrativeData.DraftAdministrativeData, 'DraftAdministrativeData', draftUUIDKey, draftsArtifact, draftsArtifactName, path);
264
+ draftAdministrativeData.DraftAdministrativeData.on = createAssociationPathComparison('DraftAdministrativeData',
265
+ getNameForRef(draftUUIDKey),
266
+ '=',
267
+ `DraftAdministrativeData${pathDelimiter}DraftUUID`);
268
+ // The notNull has been transferred to the foreign key field and must be removed on the association
269
+ delete draftAdministrativeData.DraftAdministrativeData.notNull;
270
+
271
+ // The association is now unmanaged, i.e. actually it should no longer have foreign keys
272
+ // at all. But the processing of backlink associations below expects to have them, so
273
+ // we don't delete them (but mark them as implicit so that toCdl does not render them)
274
+ // draftAdministrativeData.DraftAdministrativeData.implicitForeignKeys = true;
275
+ }
276
+ }
277
+
278
+ /**
279
+ * Redirect all association/composition targets in 'artifact' that point to targets in
280
+ * the dictionary 'draftNodes' to their corresponding draft shadow artifacts.
281
+ *
282
+ * @param {CSN.Artifact} artifact
283
+ * @param {CSN.Artifact[]} draftNodes
284
+ */
285
+ function redirectDraftTargets(artifact, draftNodes) {
286
+ for (const elemName in artifact.elements) {
287
+ const elem = artifact.elements[elemName];
288
+ if (elem.target) {
289
+ const targetArt = getCsnDef(elem.target);
290
+ // Nothing to do if target is not a draft node
291
+ if (!draftNodes[elem.target])
292
+ continue;
293
+
294
+ // Redirect the composition/association in this draft shadow entity to the target draft shadow entity
295
+ // console.error(`Redirecting target of ${elemName} in ${artifact.name.absolute} to ${target.name.absolute + '_drafts'}`);
296
+ const { shadowTarget, shadowTargetName } = getDraftShadowEntityFor(targetArt, elem.target);
297
+ // Might not exist because of previous errors
298
+ if (shadowTarget)
299
+ elem.target = shadowTargetName;
300
+ }
301
+ }
302
+
303
+ /**
304
+ * Returns the corresponding draft shadow artifact for draft node 'draftNode'.
305
+ *
306
+ * @param {CSN.Artifact} draftNode
307
+ * @param {string} draftNodeName
308
+ * @returns {object} Object with shadowTarget: definition and shadowTargetName: Name of the definition
309
+ */
310
+ function getDraftShadowEntityFor(draftNode, draftNodeName) {
311
+ // Sanity check
312
+ if (!draftNodes[draftNodeName])
313
+ throw new Error(`Not a draft node: ${draftNodeName}`);
314
+
315
+ return { shadowTarget: csn.definitions[`${draftNodeName}${draftSuffix}`], shadowTargetName: `${draftNodeName}${draftSuffix}` };
316
+ }
317
+ }
318
+
319
+ /**
320
+ * Check if the given artifact is part of a service.
321
+ *
322
+ * @param {string} artifactName Absolute name of the artifact
323
+ * @returns {boolean}
324
+ */
325
+ function isPartOfService(artifactName) {
326
+ for (const serviceName of allServices) {
327
+ if (artifactName.startsWith(`${serviceName}.`))
328
+ return true;
329
+ }
330
+
331
+ return false;
332
+ }
333
+
334
+ /**
335
+ * Get the service name containing the artifact.
336
+ *
337
+ * @param {string} artifactName Absolute name of the artifact
338
+ * @returns {boolean|string} Name of the service or false if no match is found.
339
+ */
340
+ function getMatchingService(artifactName) {
341
+ const matches = [];
342
+ for (const serviceName of allServices) {
343
+ if (artifactName.startsWith(`${serviceName}.`))
344
+ matches.push(serviceName);
345
+ }
346
+ if (matches.length === 0)
347
+ return false;
348
+ return matches.sort((a, b) => a.length - b.length)[0];
349
+ }
350
+ }
351
+
352
+
353
+ module.exports = generateDrafts;