@sap/cds-compiler 2.10.4 → 2.12.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.
Files changed (103) hide show
  1. package/CHANGELOG.md +136 -0
  2. package/bin/.eslintrc.json +1 -2
  3. package/bin/cds_update_identifiers.js +10 -8
  4. package/bin/cdsc.js +58 -35
  5. package/bin/cdsse.js +1 -0
  6. package/bin/cdsv2m.js +3 -2
  7. package/doc/CHANGELOG_ARCHIVE.md +1 -1
  8. package/doc/CHANGELOG_BETA.md +16 -0
  9. package/lib/api/.eslintrc.json +2 -0
  10. package/lib/api/main.js +10 -36
  11. package/lib/api/options.js +17 -8
  12. package/lib/api/validate.js +30 -3
  13. package/lib/backends.js +12 -13
  14. package/lib/base/dictionaries.js +2 -1
  15. package/lib/base/keywords.js +3 -2
  16. package/lib/base/message-registry.js +64 -11
  17. package/lib/base/messages.js +38 -18
  18. package/lib/base/model.js +6 -4
  19. package/lib/base/optionProcessorHelper.js +148 -86
  20. package/lib/checks/.eslintrc.json +2 -0
  21. package/lib/checks/actionsFunctions.js +2 -1
  22. package/lib/checks/emptyOrOnlyVirtual.js +2 -2
  23. package/lib/checks/foreignKeys.js +4 -4
  24. package/lib/checks/managedInType.js +4 -4
  25. package/lib/checks/queryNoDbArtifacts.js +1 -3
  26. package/lib/checks/selectItems.js +4 -0
  27. package/lib/checks/sql-snippets.js +93 -0
  28. package/lib/checks/unknownMagic.js +6 -3
  29. package/lib/checks/validator.js +8 -0
  30. package/lib/compiler/assert-consistency.js +14 -5
  31. package/lib/compiler/base.js +64 -0
  32. package/lib/compiler/builtins.js +62 -16
  33. package/lib/compiler/checks.js +34 -10
  34. package/lib/compiler/definer.js +91 -112
  35. package/lib/compiler/index.js +30 -30
  36. package/lib/compiler/propagator.js +8 -4
  37. package/lib/compiler/resolver.js +279 -63
  38. package/lib/compiler/shared.js +65 -230
  39. package/lib/compiler/utils.js +191 -0
  40. package/lib/edm/annotations/genericTranslation.js +35 -18
  41. package/lib/edm/annotations/preprocessAnnotations.js +1 -1
  42. package/lib/edm/csn2edm.js +4 -3
  43. package/lib/edm/edm.js +8 -8
  44. package/lib/edm/edmPreprocessor.js +61 -59
  45. package/lib/edm/edmUtils.js +14 -15
  46. package/lib/gen/Dictionary.json +82 -40
  47. package/lib/gen/language.checksum +1 -1
  48. package/lib/gen/language.interp +19 -1
  49. package/lib/gen/language.tokens +80 -73
  50. package/lib/gen/languageLexer.interp +27 -1
  51. package/lib/gen/languageLexer.js +925 -826
  52. package/lib/gen/languageLexer.tokens +72 -65
  53. package/lib/gen/languageParser.js +4817 -4102
  54. package/lib/json/from-csn.js +57 -26
  55. package/lib/json/to-csn.js +244 -51
  56. package/lib/language/antlrParser.js +12 -1
  57. package/lib/language/docCommentParser.js +1 -1
  58. package/lib/language/errorStrategy.js +26 -8
  59. package/lib/language/genericAntlrParser.js +106 -30
  60. package/lib/language/language.g4 +200 -70
  61. package/lib/language/multiLineStringParser.js +536 -0
  62. package/lib/main.d.ts +220 -21
  63. package/lib/main.js +6 -3
  64. package/lib/model/api.js +2 -2
  65. package/lib/model/csnRefs.js +218 -86
  66. package/lib/model/csnUtils.js +99 -178
  67. package/lib/model/enrichCsn.js +84 -43
  68. package/lib/model/revealInternalProperties.js +25 -8
  69. package/lib/model/sortViews.js +8 -1
  70. package/lib/modelCompare/compare.js +2 -1
  71. package/lib/optionProcessor.js +33 -18
  72. package/lib/render/.eslintrc.json +1 -2
  73. package/lib/render/DuplicateChecker.js +2 -2
  74. package/lib/render/manageConstraints.js +1 -1
  75. package/lib/render/toCdl.js +202 -82
  76. package/lib/render/toHdbcds.js +194 -135
  77. package/lib/render/toRename.js +7 -10
  78. package/lib/render/toSql.js +91 -51
  79. package/lib/render/utils/common.js +24 -5
  80. package/lib/render/utils/sql.js +6 -4
  81. package/lib/transform/braceExpression.js +4 -2
  82. package/lib/transform/db/applyTransformations.js +189 -0
  83. package/lib/transform/db/associations.js +389 -0
  84. package/lib/transform/db/cdsPersistence.js +150 -0
  85. package/lib/transform/db/constraints.js +275 -119
  86. package/lib/transform/db/draft.js +6 -4
  87. package/lib/transform/db/expansion.js +10 -9
  88. package/lib/transform/db/flattening.js +23 -8
  89. package/lib/transform/db/temporal.js +236 -0
  90. package/lib/transform/db/transformExists.js +106 -25
  91. package/lib/transform/db/views.js +485 -0
  92. package/lib/transform/forHanaNew.js +90 -1036
  93. package/lib/transform/forOdataNew.js +11 -3
  94. package/lib/transform/localized.js +5 -14
  95. package/lib/transform/odata/generateForeignKeyElements.js +2 -2
  96. package/lib/transform/transformUtilsNew.js +34 -20
  97. package/lib/transform/translateAssocsToJoins.js +15 -23
  98. package/lib/transform/universalCsnEnricher.js +217 -47
  99. package/lib/utils/file.js +13 -6
  100. package/lib/utils/term.js +65 -42
  101. package/lib/utils/timetrace.js +55 -27
  102. package/package.json +1 -1
  103. package/lib/transform/db/helpers.js +0 -58
@@ -0,0 +1,485 @@
1
+ 'use strict';
2
+
3
+ const {
4
+ getUtils, cloneCsn, applyTransformationsOnNonDictionary,
5
+ } = require('../../model/csnUtils');
6
+ const { implicitAs, csnRefs } = require('../../model/csnRefs');
7
+ const { isBetaEnabled } = require('../../base/model');
8
+
9
+ /**
10
+ * If a mixin association is published, return the mixin association.
11
+ *
12
+ * @param {CSN.Query} query Query of the artifact to check
13
+ * @param {object} association Association (Element) published by the view
14
+ * @param {string} associationName
15
+ * @returns {object} The mixin association
16
+ */
17
+ function getMixinAssocOfQueryIfPublished(query, association, associationName) {
18
+ if (query && query.SELECT && query.SELECT.mixin) {
19
+ const aliasedColumnsMap = Object.create(null);
20
+ if (query.SELECT.columns) {
21
+ for (const column of query.SELECT.columns) {
22
+ if (column.as && column.ref && column.ref.length === 1)
23
+ aliasedColumnsMap[column.as] = column;
24
+ }
25
+ }
26
+
27
+ for (const elem of Object.keys(query.SELECT.mixin)) {
28
+ const mixinElement = query.SELECT.mixin[elem];
29
+ let originalName = associationName;
30
+ if (aliasedColumnsMap[associationName])
31
+ originalName = aliasedColumnsMap[associationName].ref[0];
32
+
33
+ if (elem === originalName)
34
+ return { mixinElement, mixinName: originalName };
35
+ }
36
+ }
37
+ return {};
38
+ }
39
+
40
+ /**
41
+ * Check wether the given artifact uses the given mixin association.
42
+ *
43
+ * We can rely on the fact that there can be no usage starting with $self/$projection,
44
+ * since lib/checks/selectItems.js forbids that.
45
+ *
46
+ * @param {CSN.Query} query Query of the artifact to check
47
+ * @param {object} association Mixin association (Element) to check for
48
+ * @param {string} associationName
49
+ * @returns {boolean} True if used
50
+ */
51
+ function usesMixinAssociation(query, association, associationName) {
52
+ if (query && query.SELECT && query.SELECT.columns) {
53
+ for (const column of query.SELECT.columns) {
54
+ if (typeof column === 'object' && column.ref && column.ref.length > 1 && (column.ref[0] === associationName || column.ref[0].id === associationName))
55
+ return true;
56
+ }
57
+ }
58
+ return false;
59
+ }
60
+
61
+ /**
62
+ * @param {CSN.Model} csn
63
+ * @param {CSN.Options} options
64
+ * @param {{error: Function, info: Function}} messageFunctions
65
+ * @param {Function} transformCommon For the time being: Pass from outside
66
+ * @returns {(query: CSN.Query, artifact: CSN.Artifact, artName: string, path: CSN.Path) => void} Transformer function for views
67
+ */
68
+ function getViewTransformer(csn, options, messageFunctions, transformCommon) {
69
+ const {
70
+ get$combined, isAssocOrComposition,
71
+ } = getUtils(csn);
72
+ const { inspectRef, queryOrMain } = csnRefs(csn);
73
+ const pathDelimiter = (options.forHana.names === 'hdbcds') ? '.' : '_';
74
+ const { error, info } = messageFunctions;
75
+ const doA2J = !(options.transformation === 'hdbcds' && options.sqlMapping === 'hdbcds');
76
+
77
+ return transformViewOrEntity;
78
+
79
+ /**
80
+ *
81
+ * check all queries/subqueries for mixin publishing inside of unions -> forbidden in hdbcds
82
+ *
83
+ * @param {CSN.Query} query
84
+ * @param {CSN.Elements} elements
85
+ * @param {CSN.Path} path
86
+ */
87
+ function checkForMixinPublishing(query, elements, path) {
88
+ for (const elementName in elements) {
89
+ const element = elements[elementName];
90
+ if (element.target) {
91
+ let colLocation;
92
+ for (let i = 0; i < query.SELECT.columns.length; i++) {
93
+ const col = query.SELECT.columns[i];
94
+ if (col.ref && col.ref.length === 1) {
95
+ if (!colLocation && col.ref[0] === elementName)
96
+ colLocation = i;
97
+
98
+
99
+ if (col.as === elementName)
100
+ colLocation = i;
101
+ }
102
+ }
103
+ if (colLocation) {
104
+ const matchingCol = query.SELECT.columns[colLocation];
105
+ const possibleMixinName = matchingCol.ref[0];
106
+ const isMixin = query.SELECT.mixin[possibleMixinName] !== undefined;
107
+ if (element.target && isMixin) {
108
+ error(null, path.concat([ 'columns', colLocation ]), { id: elementName, name: possibleMixinName, '#': possibleMixinName === elementName ? 'std' : 'renamed' }, {
109
+ std: 'Element $(ID) is a mixin association and can\'t be published in a UNION',
110
+ renamed: 'Element $(ID) is a mixin association ($(NAME))and can\'t be published in a UNION',
111
+ });
112
+ }
113
+ }
114
+ }
115
+ }
116
+ }
117
+ /**
118
+ * Build a map of the resulting names (i.e. the element name of the column) and references to the respective columns
119
+ *
120
+ * This can later be used to match from elements to columns.
121
+ *
122
+ * @param {CSN.Query} query
123
+ * @returns {object}
124
+ */
125
+ function getColumnMap(query) {
126
+ const map = Object.create(null);
127
+ if (query && query.SELECT && query.SELECT.columns) {
128
+ query.SELECT.columns.forEach((col) => {
129
+ if (col === '*') {
130
+ // do nothing
131
+ }
132
+ else if (col.as) {
133
+ if (!map[col.as])
134
+ map[col.as] = col;
135
+ }
136
+ else if (col.ref) {
137
+ // .id on last path step can happen with hdbcds.hdbcds and malicious CSN input - maybe also with params?
138
+ // We made things right in the end with the second add of missing stuff, but why not do it
139
+ // right from the getgo
140
+ const last = getLastRefStepString(col.ref);
141
+ if (!map[last])
142
+ map[last] = col;
143
+ }
144
+ else if (col.func) {
145
+ map[col.func] = col;
146
+ }
147
+ else if (!map[col]) {
148
+ map[col] = col;
149
+ }
150
+ });
151
+ }
152
+
153
+ return map;
154
+ }
155
+ /**
156
+ * For things that are not explicitly found in the columns but still present in the elements, add them to the columnMap.
157
+ *
158
+ * This can happen for:
159
+ * - projections, as we might not have .columns at all
160
+ * - *, as we don't resolve it for hdbcds with hdbcds-naming
161
+ *
162
+ * We ensure that we attach a table alias before each column
163
+ *
164
+ * @param {CSN.Query} query
165
+ * @param {boolean} isProjection
166
+ * @param {boolean} isSelectStar
167
+ * @param {object} $combined
168
+ * @param {object} columnMap
169
+ * @param {string} elemName
170
+ */
171
+ function addProjectionOrStarElement(query, isProjection, isSelectStar, $combined, columnMap, elemName) {
172
+ // Prepend an alias if present
173
+ let alias = (isProjection || isSelectStar) &&
174
+ (query.SELECT.from.as || (query.SELECT.from.ref && implicitAs(query.SELECT.from.ref)));
175
+ // In case of * and no explicit alias
176
+ // find the source of the col by looking at $combined and prepend it
177
+ if (isSelectStar && !alias && !isProjection) {
178
+ if (!$combined)
179
+ $combined = get$combined(query);
180
+
181
+
182
+ const matchingCombined = $combined[elemName];
183
+ // Internal errors - this should never happen!
184
+ if (matchingCombined.length > 1) { // should already be caught by compiler
185
+ throw new Error(`Ambiguous name - can't be resolved: ${elemName}. Found in: ${matchingCombined.map(o => o.parent)}`);
186
+ }
187
+ else if (matchingCombined.length === 0) { // no clue how this could happen? Invalid CSN?
188
+ throw new Error(`No matching entry found in UNION of all elements for: ${elemName}`);
189
+ }
190
+ alias = matchingCombined[0].parent;
191
+ }
192
+ if (alias)
193
+ columnMap[elemName] = { ref: [ alias, elemName ] };
194
+ else
195
+ columnMap[elemName] = { ref: [ elemName ] };
196
+ }
197
+
198
+ /**
199
+ * So far, we only added foreign keys to elements - we also need to create corresponding columns
200
+ * and respect aliasing etc.
201
+ *
202
+ * @todo Maybe this can be done earlier, during flattening/expansion already?
203
+ * @param {object} columnMap
204
+ * @param {CSN.Element} elem
205
+ * @param {string} elemName
206
+ */
207
+ function addForeignKeysToColumns(columnMap, elem, elemName) {
208
+ const assocCol = columnMap[elemName];
209
+ if (assocCol && assocCol.ref) {
210
+ elem.keys.forEach((foreignKey) => {
211
+ const ref = cloneCsn(assocCol.ref, options);
212
+ ref[ref.length - 1] = [ getLastRefStepString(ref) ].concat(foreignKey.as).join(pathDelimiter);
213
+ const result = {
214
+ ref,
215
+ };
216
+ if (assocCol.as) {
217
+ const columnName = `${assocCol.as}${pathDelimiter}${foreignKey.as}`;
218
+ result.as = columnName;
219
+ }
220
+
221
+ if (assocCol.key)
222
+ result.key = true;
223
+
224
+ const colName = result.as || getLastRefStepString(ref);
225
+ columnMap[colName] = result;
226
+ });
227
+ }
228
+ }
229
+
230
+
231
+ /**
232
+ * Check for invalid association publishing (in Union or in Subquery) (for hdbcds) and
233
+ * create the __clone for publishing stuff.
234
+ *
235
+ * @todo Factor out the checks
236
+ * @param {CSN.Query} query
237
+ * @param {object} elements
238
+ * @param {object} columnMap
239
+ * @param {WeakMap} publishedMixins Map to collect the published mixins
240
+ * @param {CSN.Element} elem
241
+ * @param {string} elemName
242
+ * @param {CSN.Path} elementsPath Path pointing to elements
243
+ * @param {CSN.Path} queryPath Path pointing to the query
244
+ */
245
+ function handleAssociationElement(query, elements, columnMap, publishedMixins, elem, elemName, elementsPath, queryPath) {
246
+ if (isUnion(queryPath) && options.transformation === 'hdbcds') {
247
+ if (isBetaEnabled(options, 'ignoreAssocPublishingInUnion') && doA2J) {
248
+ if (elem.keys)
249
+ info(null, queryPath, `Managed association "${elemName}", published in a UNION, will be ignored`);
250
+ else
251
+ info(null, queryPath, `Association "${elemName}", published in a UNION, will be ignored`);
252
+
253
+ elem._ignore = true;
254
+ }
255
+ else {
256
+ error(null, queryPath, `Association "${elemName}" can't be published in a SAP HANA CDS UNION`);
257
+ }
258
+ }
259
+ else if (queryPath.length > 4 && options.transformation === 'hdbcds') { // path.length > 4 -> is a subquery
260
+ error(null, queryPath, { name: elemName },
261
+ 'Association $(NAME) can\'t be published in a subquery');
262
+ }
263
+ else {
264
+ const isNotMixinByItself = checkIsNotMixinByItself(query, columnMap, elemName);
265
+ const { mixinElement, mixinName } = getMixinAssocOfQueryIfPublished(query, elem, elemName);
266
+ if (isNotMixinByItself || mixinElement !== undefined) {
267
+ // If the mixin is only published and not used, only display the __ clone. Kill the "original".
268
+ if (mixinElement !== undefined && !usesMixinAssociation(query, elem, elemName))
269
+ delete query.SELECT.mixin[mixinName];
270
+
271
+
272
+ // Create an unused alias name for the MIXIN - use 3 _ to avoid collision with usings
273
+ let mixinElemName = `___${mixinName || elemName}`;
274
+ while (elements[mixinElemName])
275
+ mixinElemName = `_${mixinElemName}`;
276
+
277
+ // Copy the association element to the MIXIN clause under its alias name
278
+ // Needs to be a deep copy, as we transform the on-condition
279
+ const mixinElem = cloneCsn(elem, options);
280
+ // Perform common transformations on the newly generated MIXIN element (won't be reached otherwise)
281
+ transformCommon(mixinElem, mixinElemName);
282
+
283
+ if (query.SELECT && !query.SELECT.mixin)
284
+ query.SELECT.mixin = Object.create(null);
285
+
286
+ // Clone 'on'-condition, pre-pending '$projection' to paths where appropriate,
287
+ // and fixing the association alias just created
288
+
289
+ if (mixinElem.on) {
290
+ mixinElem.on = applyTransformationsOnNonDictionary(mixinElem, 'on', {
291
+ ref: (parent, prop, ref, refpath) => {
292
+ if (ref[0] === elemName) {
293
+ ref[0] = mixinElemName;
294
+ }
295
+ else if (!(ref[0] && ref[0].startsWith('$'))) {
296
+ ref.unshift('$projection');
297
+ }
298
+ else if (ref[0] && ref[0].startsWith('$')) {
299
+ // TODO: I think this is non-sense. Stuff with $ is either magic or must start with $self, right?
300
+ const { scope } = inspectRef(refpath);
301
+ if (scope !== '$magic' && scope !== '$self')
302
+ ref.unshift('$projection');
303
+ }
304
+ parent.ref = ref;
305
+ return ref;
306
+ },
307
+ }, elementsPath.concat(elemName));
308
+ }
309
+
310
+ if (!mixinElem._ignore)
311
+ columnMap[elemName] = { ref: [ mixinElemName ], as: elemName };
312
+
313
+ if (query.SELECT) {
314
+ query.SELECT.mixin[mixinElemName] = mixinElem;
315
+
316
+ publishedMixins.set(mixinElem, true);
317
+ }
318
+ }
319
+ }
320
+ }
321
+
322
+ /**
323
+ * If following an association, explicitly set the implicit alias
324
+ * due to an issue with HANA - only for hdbcds-hdbcds, I assume flattening
325
+ * takes care of this for the other cases already
326
+ *
327
+ * @param {CSN.Query} query
328
+ * @param {CSN.Path} path
329
+ */
330
+ function addImplicitAliasWithAssoc(query, path) {
331
+ for (let i = 0; i < query.SELECT.columns.length; i++) {
332
+ const col = query.SELECT.columns[i];
333
+ if (!col.as && col.ref && col.ref.length > 1) {
334
+ const { links } = inspectRef(path.concat([ 'columns', i ]));
335
+ if (links && links.slice(0, -1).some(({ art }) => isAssocOrComposition(art && art.type || '')))
336
+ col.as = getLastRefStepString(col.ref);
337
+ }
338
+ }
339
+ }
340
+
341
+ /**
342
+ * @param {CSN.Query} query
343
+ * @param {CSN.Artifact} artifact
344
+ * @param {string} artName
345
+ * @param {CSN.Path} path
346
+ */
347
+ // eslint-disable-next-line complexity
348
+ function transformViewOrEntity(query, artifact, artName, path) {
349
+ const { elements } = queryOrMain(query, artifact);
350
+ // We use the elements from the leading query/main artifact - adapt the path
351
+ const elementsPath = elements === artifact.elements ? path.slice(0, 2).concat('elements') : path.concat('elements');
352
+ const queryPath = path;
353
+
354
+ let hasNonAssocElements = false;
355
+ const isSelect = query && query.SELECT;
356
+ const isProjection = !!artifact.projection || query && query.SELECT && !query.SELECT.columns;
357
+ const columnMap = getColumnMap(query);
358
+ const isSelectStar = query && query.SELECT && query.SELECT.columns && query.SELECT.columns.indexOf('*') !== -1;
359
+
360
+ // check all queries/subqueries for mixin publishing inside of unions -> forbidden in hdbcds
361
+ if (query && options.transformation === 'hdbcds' && query.SELECT && query.SELECT.mixin && path.indexOf('SET') !== -1)
362
+ checkForMixinPublishing(query, elements, path);
363
+
364
+ // Second walk through the entity elements: Deal with associations (might also result in new elements)
365
+ // Will be initialized JIT inside the elements-loop
366
+ let $combined;
367
+
368
+ const publishedMixins = new WeakMap();
369
+
370
+ for (const elemName in elements) {
371
+ const elem = elements[elemName];
372
+ if (isSelect) {
373
+ if (!columnMap[elemName])
374
+ addProjectionOrStarElement(query, isProjection, isSelectStar, $combined, columnMap, elemName);
375
+
376
+ // For associations - make sure that the foreign keys have the same "style"
377
+ // If A.assoc => A.assoc_id, else if assoc => assoc_id or assoc as Assoc => Assoc_id
378
+ if (elem.keys && doA2J)
379
+ addForeignKeysToColumns(columnMap, elem, elemName);
380
+ }
381
+ // Views must have at least one element that is not an unmanaged assoc
382
+ if (!elem.on && !elem._ignore)
383
+ hasNonAssocElements = true;
384
+
385
+ // (180 b) Create MIXINs for association elements in projections or views (those that are not mixins by themselves)
386
+ // CDXCORE-585: Allow mixin associations to be used and published in parallel
387
+ if (query !== undefined && elem.target)
388
+ handleAssociationElement(query, elements, columnMap, publishedMixins, elem, elemName, elementsPath, queryPath);
389
+ }
390
+
391
+ if (query && !hasNonAssocElements) {
392
+ // Complain if there are no elements other than unmanaged associations
393
+ // Allow with plain
394
+ error(null, [ 'definitions', artName ], { $reviewed: true },
395
+ 'Expecting view or projection to have at least one element that is not an unmanaged association');
396
+ }
397
+
398
+ if (isSelect) {
399
+ // Build new columns from the column map - bring elements and columns back in sync basically
400
+ query.SELECT.columns = Object.keys(elements).filter(elem => !elements[elem]._ignore).map(key => stripLeadingSelf(columnMap[key]));
401
+ // If following an association, explicitly set the implicit alias
402
+ // due to an issue with HANA - this seems to only have an effect on ref files with hdbcds-hdbcds, so only run then
403
+ if (options.transformation === 'hdbcds' && options.sqlMapping === 'hdbcds')
404
+ addImplicitAliasWithAssoc(query, path);
405
+
406
+ delete query.SELECT.excluding; // just to make the output of the new transformer the same as the old
407
+
408
+ // A2J turned usages into JOINs, we must now remove all non-published mixins (i.e. only keep the clones)
409
+ if (query.SELECT.mixin && doA2J) {
410
+ for (const [ name, mixin ] of Object.entries(query.SELECT.mixin)) {
411
+ if (!publishedMixins.has(mixin))
412
+ delete query.SELECT.mixin[name];
413
+ }
414
+ }
415
+ }
416
+ }
417
+ }
418
+
419
+ /**
420
+ * Walk the given path and check if we are in a UNION.
421
+ * This will return true when it is called on the subquery inside of a SET.args property.
422
+ *
423
+ * @param {CSN.Path} path
424
+ * @returns {boolean}
425
+ */
426
+ function isUnion(path) {
427
+ const subquery = path[path.length - 1];
428
+ const queryIndex = path[path.length - 2];
429
+ const args = path[path.length - 3];
430
+ const unionOperator = path[path.length - 4];
431
+ return path.length > 3 && (subquery === 'SET' || subquery === 'SELECT') && typeof queryIndex === 'number' && queryIndex >= 0 && args === 'args' && unionOperator === 'SET';
432
+ }
433
+
434
+ /**
435
+ * Strip of leading $self of the ref
436
+ *
437
+ * @param {object} col A column
438
+ * @returns {object}
439
+ */
440
+ function stripLeadingSelf(col) {
441
+ if (col.ref && col.ref.length > 1 && col.ref[0] === '$self')
442
+ col.ref = col.ref.slice(1);
443
+
444
+ return col;
445
+ }
446
+
447
+ /**
448
+ * Check that the given element is not a simple mixin-publishing
449
+ *
450
+ * @param {CSN.Query} query
451
+ * @param {object} columnMap
452
+ * @param {string} elementName
453
+ * @returns {boolean}
454
+ */
455
+ function checkIsNotMixinByItself(query, columnMap, elementName) {
456
+ if (query && query.SELECT && query.SELECT.mixin) {
457
+ const col = columnMap[elementName];
458
+
459
+ // Use getLastRefStepString - with hdbcds.hdbcds and malicious CSN input we might have .id
460
+ const realName = getLastRefStepString(col.ref);
461
+ // If the element is not part of the mixin => True
462
+ return query.SELECT.mixin[realName] === undefined;
463
+ }
464
+ // the artifact does not define any mixins, the element cannot be a mixin
465
+ return true;
466
+ }
467
+
468
+ /**
469
+ * Return the string value of the last ref step - so either the .id or the last step.
470
+ *
471
+ * We cannot use implicitAs, as this causes problems for structured things with hdi-hdbcds naming
472
+ *
473
+ * @param {Array} ref
474
+ * @returns {string}
475
+ */
476
+ function getLastRefStepString(ref) {
477
+ const last = ref[ref.length - 1];
478
+ if (last.id)
479
+ return last.id;
480
+ return last;
481
+ }
482
+
483
+ module.exports = {
484
+ getViewTransformer,
485
+ };