@sap/cds-compiler 3.5.2 → 3.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +63 -1
- package/bin/cdsc.js +14 -6
- package/doc/CHANGELOG_ARCHIVE.md +10 -10
- package/doc/CHANGELOG_DEPRECATED.md +2 -2
- package/lib/api/main.js +32 -55
- package/lib/api/options.js +1 -0
- package/lib/api/validate.js +5 -0
- package/lib/base/message-registry.js +104 -32
- package/lib/base/messages.js +277 -212
- package/lib/base/model.js +33 -22
- package/lib/base/optionProcessorHelper.js +9 -2
- package/lib/base/shuffle.js +50 -0
- package/lib/checks/actionsFunctions.js +37 -20
- package/lib/checks/foreignKeys.js +13 -6
- package/lib/checks/nonexpandableStructured.js +1 -2
- package/lib/checks/onConditions.js +21 -19
- package/lib/checks/parameters.js +1 -1
- package/lib/checks/queryNoDbArtifacts.js +2 -0
- package/lib/checks/types.js +16 -22
- package/lib/compiler/assert-consistency.js +31 -28
- package/lib/compiler/builtins.js +20 -4
- package/lib/compiler/checks.js +72 -63
- package/lib/compiler/define.js +396 -314
- package/lib/compiler/extend.js +55 -49
- package/lib/compiler/index.js +5 -0
- package/lib/compiler/populate.js +28 -11
- package/lib/compiler/propagator.js +2 -1
- package/lib/compiler/resolve.js +29 -20
- package/lib/compiler/shared.js +15 -10
- package/lib/compiler/utils.js +7 -7
- package/lib/edm/annotations/genericTranslation.js +51 -46
- package/lib/edm/annotations/preprocessAnnotations.js +39 -42
- package/lib/edm/csn2edm.js +69 -21
- package/lib/edm/edm.js +2 -2
- package/lib/edm/edmInboundChecks.js +6 -8
- package/lib/edm/edmPreprocessor.js +88 -80
- package/lib/edm/edmUtils.js +6 -15
- package/lib/gen/Dictionary.json +81 -13
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +2 -1
- package/lib/gen/languageParser.js +4680 -4484
- package/lib/inspect/inspectModelStatistics.js +2 -1
- package/lib/inspect/inspectPropagation.js +2 -1
- package/lib/json/from-csn.js +131 -78
- package/lib/json/to-csn.js +39 -23
- package/lib/language/antlrParser.js +0 -3
- package/lib/language/docCommentParser.js +7 -3
- package/lib/language/errorStrategy.js +3 -2
- package/lib/language/genericAntlrParser.js +96 -41
- package/lib/language/language.g4 +112 -128
- package/lib/language/multiLineStringParser.js +2 -1
- package/lib/main.d.ts +115 -2
- package/lib/main.js +16 -3
- package/lib/model/csnRefs.js +3 -3
- package/lib/model/csnUtils.js +109 -179
- package/lib/model/enrichCsn.js +13 -8
- package/lib/model/revealInternalProperties.js +4 -3
- package/lib/optionProcessor.js +23 -3
- package/lib/render/manageConstraints.js +11 -15
- package/lib/render/toCdl.js +144 -47
- package/lib/render/toHdbcds.js +22 -22
- package/lib/render/toRename.js +3 -4
- package/lib/render/toSql.js +29 -20
- package/lib/render/utils/delta.js +3 -1
- package/lib/render/utils/sql.js +3 -16
- package/lib/transform/db/associations.js +6 -6
- package/lib/transform/db/cdsPersistence.js +3 -3
- package/lib/transform/db/constraints.js +8 -8
- package/lib/transform/db/expansion.js +4 -4
- package/lib/transform/db/flattening.js +12 -15
- package/lib/transform/db/temporal.js +4 -3
- package/lib/transform/db/transformExists.js +2 -1
- package/lib/transform/draft/db.js +7 -7
- package/lib/transform/forOdataNew.js +15 -4
- package/lib/transform/forRelationalDB.js +53 -39
- package/lib/transform/odata/toFinalBaseType.js +106 -82
- package/lib/transform/odata/typesExposure.js +26 -17
- package/lib/transform/odata/utils.js +1 -1
- package/lib/transform/parseExpr.js +1 -1
- package/lib/transform/transformUtilsNew.js +33 -10
- package/lib/transform/translateAssocsToJoins.js +8 -7
- package/lib/transform/universalCsn/coreComputed.js +7 -5
- package/lib/transform/universalCsn/universalCsnEnricher.js +12 -4
- package/lib/utils/timetrace.js +2 -2
- package/package.json +1 -2
package/lib/optionProcessor.js
CHANGED
|
@@ -15,6 +15,7 @@ optionProcessor
|
|
|
15
15
|
.option('-w, --warning <level>', ['0', '1', '2', '3'])
|
|
16
16
|
.option(' --quiet')
|
|
17
17
|
.option(' --show-message-id')
|
|
18
|
+
.option(' --no-message-id')
|
|
18
19
|
.option(' --no-message-context')
|
|
19
20
|
.option(' --color <mode>', ['auto', 'always', 'never'])
|
|
20
21
|
.option('-o, --out <dir>')
|
|
@@ -33,6 +34,7 @@ optionProcessor
|
|
|
33
34
|
.option(' --deprecated <list>')
|
|
34
35
|
.option(' --direct-backend')
|
|
35
36
|
.option(' --fallback-parser <type>', ['cdl', 'csn', 'csn!'])
|
|
37
|
+
.option(' --shuffle <seed>') // 0 | 1..4294967296
|
|
36
38
|
.option(' --test-mode')
|
|
37
39
|
.option(' --test-sort-csn')
|
|
38
40
|
.option(' --doc-comment')
|
|
@@ -64,7 +66,8 @@ optionProcessor
|
|
|
64
66
|
--options <file> Use the given JSON file as input options.
|
|
65
67
|
The key 'cdsc' of 'cds' is used. If not present 'cdsc' is used.
|
|
66
68
|
Otherwise, the JSON as-is is used as options.
|
|
67
|
-
--
|
|
69
|
+
--no-message-id Don't show message IDs in errors, warnings, info and debug messages
|
|
70
|
+
--show-message-id DEPRECATED: Showing the message ID is now the default.
|
|
68
71
|
--no-message-context Print messages as single lines without code context (useful for
|
|
69
72
|
redirecting output to other processes). Default is to print human-
|
|
70
73
|
readable text similar to Rust's compiler with a code excerpt.
|
|
@@ -106,7 +109,7 @@ optionProcessor
|
|
|
106
109
|
postgres
|
|
107
110
|
aspectWithoutElements
|
|
108
111
|
odataOpenType
|
|
109
|
-
|
|
112
|
+
odataTerms
|
|
110
113
|
optionalActionFunctionParameters
|
|
111
114
|
--deprecated <list> Comma separated list of deprecated options.
|
|
112
115
|
Valid values are:
|
|
@@ -121,6 +124,11 @@ optionProcessor
|
|
|
121
124
|
Can only be used with certain new CSN based backends. Combination with
|
|
122
125
|
other flags is limited, e.g. --test-mode will not run a consistency check.
|
|
123
126
|
No recompilation is triggered in case of errors. cdsc will dump.
|
|
127
|
+
--shuffle <seed> If provided, some internal processing sequences are changed, most notably by
|
|
128
|
+
using a shuffled version of ‹model›.definitions. <seed> should be a number
|
|
129
|
+
between 1 and 4294967296, the compiler uses a random number in that range if the
|
|
130
|
+
provided argument is 0 or not a number. The same number always produces the same
|
|
131
|
+
shuffled version of ‹model›.definitions. This option also enables --test-mode.
|
|
124
132
|
--test-mode Produce extra-stable output for automated tests (normalize filenames
|
|
125
133
|
in errors, sort properties in CSN, omit version in CSN)
|
|
126
134
|
--test-sort-csn Sort the generated CSN by definitions. This impacts the order of EDMX,
|
|
@@ -150,7 +158,8 @@ optionProcessor
|
|
|
150
158
|
Environment variables
|
|
151
159
|
NO_COLOR If set, compiler messages (/output) will not be colored.
|
|
152
160
|
Can be overwritten by '--color'
|
|
153
|
-
|
|
161
|
+
CDSC_TRACE_TIME If set, additional timing information is printed to stderr.
|
|
162
|
+
CDSC_TRACE_API If set, additional API calling information is printed to stderr.
|
|
154
163
|
`);
|
|
155
164
|
|
|
156
165
|
// ----------- toHana -----------
|
|
@@ -166,6 +175,7 @@ optionProcessor.command('H, toHana')
|
|
|
166
175
|
.option(' --integrity-not-enforced')
|
|
167
176
|
.option(' --assert-integrity <mode>', ['true', 'false', 'individual'])
|
|
168
177
|
.option(' --assert-integrity-type <type>', ['RT', 'DB'], { ignoreCase: true })
|
|
178
|
+
.option(' --pre2134ReferentialConstraintNames')
|
|
169
179
|
.option(' --disable-hana-comments')
|
|
170
180
|
.help(`
|
|
171
181
|
Usage: cdsc toHana [options] <files...>
|
|
@@ -201,6 +211,7 @@ optionProcessor.command('H, toHana')
|
|
|
201
211
|
RT : (default) No database constraint for an association
|
|
202
212
|
if not explicitly demanded via annotation
|
|
203
213
|
DB : Create database constraints for associations
|
|
214
|
+
--pre2134ReferentialConstraintNames Do not prefix the constraint identifier with "c__"
|
|
204
215
|
--disable-hana-comments Disable rendering of doc comments as SAP HANA comments.
|
|
205
216
|
`);
|
|
206
217
|
|
|
@@ -280,6 +291,7 @@ optionProcessor.command('Q, toSql')
|
|
|
280
291
|
.option(' --assert-integrity <mode>', ['true', 'false', 'individual'])
|
|
281
292
|
.option(' --assert-integrity-type <type>', ['RT', 'DB'], { ignoreCase: true })
|
|
282
293
|
.option(' --constraints-in-create-table')
|
|
294
|
+
.option(' --pre2134ReferentialConstraintNames')
|
|
283
295
|
.option(' --disable-hana-comments')
|
|
284
296
|
.help(`
|
|
285
297
|
Usage: cdsc toSql [options] <files...>
|
|
@@ -329,6 +341,7 @@ optionProcessor.command('Q, toSql')
|
|
|
329
341
|
--constraints-in-create-table If set, the foreign key constraints will be rendered as
|
|
330
342
|
part of the "CREATE TABLE" statements rather than as separate
|
|
331
343
|
"ALTER TABLE ADD CONSTRAINT" statements
|
|
344
|
+
--pre2134ReferentialConstraintNames Do not prefix the constraint identifier with "c__"
|
|
332
345
|
--disable-hana-comments Disable rendering of doc comments as SAP HANA comments.
|
|
333
346
|
`);
|
|
334
347
|
|
|
@@ -363,6 +376,7 @@ optionProcessor.command('manageConstraints')
|
|
|
363
376
|
.option(' --violations')
|
|
364
377
|
.option(' --integrity-not-validated')
|
|
365
378
|
.option(' --integrity-not-enforced')
|
|
379
|
+
.option('-d, --sql-dialect <dialect>', ['hana', 'sqlite', 'plain', 'postgres', 'h2'], { aliases: [ '--dialect' ] })
|
|
366
380
|
.help(`
|
|
367
381
|
Usage: cdsc manageConstraints [options] <files...>
|
|
368
382
|
|
|
@@ -391,6 +405,12 @@ optionProcessor.command('manageConstraints')
|
|
|
391
405
|
referential integrity violations on the existing data
|
|
392
406
|
--integrity-not-enforced If this option is supplied, referential constraints are NOT ENFORCED.
|
|
393
407
|
--integrity-not-validated If this option is supplied, referential constraints are NOT VALIDATED.
|
|
408
|
+
-d, --sql-dialect <dialect> SQL dialect to be generated:
|
|
409
|
+
plain : (default) Common SQL - no assumptions about DB restrictions (constraints not supported in this dialect)
|
|
410
|
+
hana : SQL with HANA specific language features
|
|
411
|
+
sqlite : Common SQL for sqlite
|
|
412
|
+
postgres : Common SQL for postgres - beta-feature
|
|
413
|
+
h2 : Common SQL for h2 (constraints not supported in this dialect)
|
|
394
414
|
`);
|
|
395
415
|
|
|
396
416
|
optionProcessor.command('toCsn')
|
|
@@ -13,6 +13,7 @@ const { transformForRelationalDBWithCsn } = require('../transform/forRelationalD
|
|
|
13
13
|
const {
|
|
14
14
|
renderReferentialConstraint, getIdentifierUtils,
|
|
15
15
|
} = require('./utils/sql');
|
|
16
|
+
const { sortCsn } = require('../json/to-csn');
|
|
16
17
|
|
|
17
18
|
/**
|
|
18
19
|
* Used only by `cdsc manageConstraints`.
|
|
@@ -25,17 +26,15 @@ function alterConstraintsWithCsn( csn, options ) {
|
|
|
25
26
|
const { error } = makeMessageFunction(csn, options, 'manageConstraints');
|
|
26
27
|
|
|
27
28
|
const {
|
|
28
|
-
drop, alter,
|
|
29
|
-
} = options
|
|
29
|
+
drop, alter, src, violations,
|
|
30
|
+
} = options || {};
|
|
30
31
|
|
|
31
32
|
if (drop && alter)
|
|
32
33
|
error(null, null, 'Option “--drop” can\'t be combined with “--alter”');
|
|
33
34
|
|
|
34
|
-
options.sqlDialect = 'hana';
|
|
35
|
-
options.sqlMapping = names || 'plain';
|
|
36
35
|
|
|
37
36
|
// Of course we want the database constraints
|
|
38
|
-
options.assertIntegrityType = 'DB';
|
|
37
|
+
options.assertIntegrityType = options.assertIntegrityType || 'DB';
|
|
39
38
|
|
|
40
39
|
const transformedOptions = _transformSqlOptions(csn, options);
|
|
41
40
|
const forSqlCsn = transformForRelationalDBWithCsn(csn, transformedOptions, 'to.sql');
|
|
@@ -49,7 +48,7 @@ function alterConstraintsWithCsn( csn, options ) {
|
|
|
49
48
|
if (violations)
|
|
50
49
|
intermediateResult = listReferentialIntegrityViolations(forSqlCsn, transformedOptions);
|
|
51
50
|
else
|
|
52
|
-
intermediateResult = manageConstraints(forSqlCsn, transformedOptions);
|
|
51
|
+
intermediateResult = manageConstraints(options.testMode ? sortCsn(forSqlCsn) : forSqlCsn, transformedOptions);
|
|
53
52
|
|
|
54
53
|
return intermediateResult;
|
|
55
54
|
}
|
|
@@ -92,19 +91,16 @@ function _transformSqlOptions( model, options ) {
|
|
|
92
91
|
* @returns a map holding the constraint identifier as key and the corresponding, rendered SQL statement / hdbconstraint artifact as value.
|
|
93
92
|
*/
|
|
94
93
|
function manageConstraints( csn, options ) {
|
|
95
|
-
const
|
|
96
|
-
drop, alter, src,
|
|
97
|
-
} = options.manageConstraints || {};
|
|
98
|
-
const indent = '';
|
|
94
|
+
const indent = options.src === 'hdi' ? ' ' : ''; // indent `.hdbconstraint`
|
|
99
95
|
// either ALTER TABLE statements or .hdbconstraint artifacts
|
|
100
96
|
const resultArtifacts = {};
|
|
101
97
|
const { quoteSqlId } = getIdentifierUtils(csn, options);
|
|
102
98
|
forEachDefinition(csn, (artifact) => {
|
|
103
|
-
if (artifact.$tableConstraints
|
|
99
|
+
if (artifact.$tableConstraints?.referential) {
|
|
104
100
|
forEach(artifact.$tableConstraints.referential, (fileName, constraint) => {
|
|
105
|
-
const renderAlterConstraintStatement = alter && src !== 'hdi';
|
|
101
|
+
const renderAlterConstraintStatement = options.alter && options.src !== 'hdi';
|
|
106
102
|
const renderedConstraint = renderReferentialConstraint(constraint, indent, false, csn, options, renderAlterConstraintStatement);
|
|
107
|
-
if (src === 'hdi') {
|
|
103
|
+
if (options.src === 'hdi' && !options.drop) {
|
|
108
104
|
resultArtifacts[fileName] = renderedConstraint;
|
|
109
105
|
return;
|
|
110
106
|
}
|
|
@@ -112,7 +108,7 @@ function manageConstraints( csn, options ) {
|
|
|
112
108
|
alterTableStatement += `${indent}ALTER TABLE ${quoteSqlId(getResultingName(csn, options.sqlMapping, constraint.dependentTable))}`;
|
|
113
109
|
if (renderAlterConstraintStatement)
|
|
114
110
|
alterTableStatement += `\n${indent}ALTER ${renderedConstraint};`;
|
|
115
|
-
else if (drop)
|
|
111
|
+
else if (options.drop)
|
|
116
112
|
alterTableStatement += `${indent} DROP CONSTRAINT ${quoteSqlId(constraint.identifier)};`;
|
|
117
113
|
else
|
|
118
114
|
alterTableStatement += `\n${indent}ADD ${renderedConstraint};`;
|
|
@@ -121,7 +117,7 @@ function manageConstraints( csn, options ) {
|
|
|
121
117
|
});
|
|
122
118
|
}
|
|
123
119
|
});
|
|
124
|
-
return resultArtifacts;
|
|
120
|
+
return options.src === 'hdi' ? resultArtifacts : Object.values(resultArtifacts);
|
|
125
121
|
}
|
|
126
122
|
|
|
127
123
|
/**
|
package/lib/render/toCdl.js
CHANGED
|
@@ -5,7 +5,7 @@ const { findElement, createExpressionRenderer, withoutCast } = require('./utils/
|
|
|
5
5
|
const { escapeString, hasUnpairedUnicodeSurrogate } = require('./utils/stringEscapes');
|
|
6
6
|
const { checkCSNVersion } = require('../json/csnVersion');
|
|
7
7
|
const { timetrace } = require('../utils/timetrace');
|
|
8
|
-
const { forEachDefinition } = require('../model/csnUtils');
|
|
8
|
+
const { forEachDefinition, normalizeTypeRef } = require('../model/csnUtils');
|
|
9
9
|
const enrichUniversalCsn = require('../transform/universalCsn/universalCsnEnricher');
|
|
10
10
|
const { isBetaEnabled } = require('../base/model');
|
|
11
11
|
const { ModelError } = require('../base/error');
|
|
@@ -19,6 +19,7 @@ const {
|
|
|
19
19
|
} = require('../model/csnUtils');
|
|
20
20
|
|
|
21
21
|
const identifierRegex = /^[$_a-zA-Z][$_a-zA-Z0-9]*$/;
|
|
22
|
+
const specialFunctionKeywords = Object.create(null);
|
|
22
23
|
|
|
23
24
|
/**
|
|
24
25
|
* Render the CSN model 'model' to CDS source text.
|
|
@@ -30,6 +31,7 @@ const identifierRegex = /^[$_a-zA-Z][$_a-zA-Z0-9]*$/;
|
|
|
30
31
|
* @param {CSN.Options} [options]
|
|
31
32
|
*/
|
|
32
33
|
function csnToCdl( csn, options ) {
|
|
34
|
+
const special$self = !csn?.definitions?.$self && '$self';
|
|
33
35
|
timetrace.start('CDL rendering');
|
|
34
36
|
|
|
35
37
|
if (options.csnFlavor === 'universal' && isBetaEnabled(options, 'enableUniversalCsn')) {
|
|
@@ -48,7 +50,7 @@ function csnToCdl( csn, options ) {
|
|
|
48
50
|
addIfRequired(name) {
|
|
49
51
|
// RegEx is at least twice as fast as .split()[0]
|
|
50
52
|
const first = name.match(/^[^.]+/)[0];
|
|
51
|
-
if (!this.available.includes(first) && !this.list.includes(first))
|
|
53
|
+
if (name !== special$self && !this.available.includes(first) && !this.list.includes(first))
|
|
52
54
|
this.list.push(first);
|
|
53
55
|
},
|
|
54
56
|
renderUsings() {
|
|
@@ -197,11 +199,11 @@ function csnToCdl( csn, options ) {
|
|
|
197
199
|
// The former one can not only extend (sub-)elements but also actions in the same statement whereas
|
|
198
200
|
// the latter cannot.
|
|
199
201
|
// If there are actions, check if there are also elements/columns, and if so, use the prefix notation.
|
|
200
|
-
const usePrefixNotation =
|
|
202
|
+
const usePrefixNotation = ext.actions && (ext.columns || ext.elements);
|
|
201
203
|
if (usePrefixNotation)
|
|
202
204
|
result += `${env.indent}extend ${getExtendPrefixVariant(ext)} ${extName} with {\n`;
|
|
203
205
|
else
|
|
204
|
-
result += `${env.indent}extend ${extName} with ${getExtendPostfixVariant(ext)}
|
|
206
|
+
result += `${env.indent}extend ${extName} with ${getExtendPostfixVariant(ext)}{\n`;
|
|
205
207
|
|
|
206
208
|
if (ext.columns)
|
|
207
209
|
result += renderViewColumns(ext.columns, increaseIndent(env));
|
|
@@ -252,13 +254,20 @@ function csnToCdl( csn, options ) {
|
|
|
252
254
|
*/
|
|
253
255
|
function getExtendPostfixVariant( ext ) {
|
|
254
256
|
if (ext.columns)
|
|
255
|
-
return 'columns';
|
|
257
|
+
return 'columns ';
|
|
256
258
|
if (ext.actions)
|
|
257
|
-
return 'actions';
|
|
258
|
-
if (ext.elements)
|
|
259
|
-
return 'elements';
|
|
259
|
+
return 'actions ';
|
|
260
260
|
if (ext.enum)
|
|
261
|
-
return 'enum';
|
|
261
|
+
return 'enum ';
|
|
262
|
+
if (ext.elements) { // enum/elements ambiguity -> look into elements
|
|
263
|
+
// TODO: Check for `type` as well once this is supported by the parser (and identified as elements):
|
|
264
|
+
// `extend E with { a = 'string'; b: String }`
|
|
265
|
+
const isLikelyElement = Object.keys(ext.elements)
|
|
266
|
+
.find(name => ext.elements[name].value !== undefined);
|
|
267
|
+
if (isLikelyElement)
|
|
268
|
+
return 'elements ';
|
|
269
|
+
}
|
|
270
|
+
// ambiguity; no postfix, i.e. `extend … with { … }`.s
|
|
262
271
|
return '';
|
|
263
272
|
}
|
|
264
273
|
|
|
@@ -544,16 +553,25 @@ function csnToCdl( csn, options ) {
|
|
|
544
553
|
// TODO(v4): Remove once deprecated flag for `masked` is removed.
|
|
545
554
|
result += elm.masked ? 'masked ' : '';
|
|
546
555
|
result += quoteIdIfRequired(elementName);
|
|
547
|
-
if (elm.val !== undefined) {
|
|
556
|
+
if (elm.val !== undefined) { // enum value
|
|
548
557
|
result += ` = ${exprRenderer.renderExpr(elm, env)}`;
|
|
549
558
|
}
|
|
550
|
-
else if (elm['#'] !== undefined) {
|
|
559
|
+
else if (elm['#'] !== undefined) { // enum symbol reference
|
|
551
560
|
result += ` = #${elm['#']}`;
|
|
552
561
|
}
|
|
553
562
|
else {
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
563
|
+
// TODO: Maybe push the .value check into renderTypeReferenceAndProps?
|
|
564
|
+
const type = normalizeTypeRef(elm.items?.type || elm.type);
|
|
565
|
+
const isAssoc = type === 'cds.Association' || type === 'cds.Composition';
|
|
566
|
+
if (!isAssoc || elm.value === undefined) {
|
|
567
|
+
const props = renderTypeReferenceAndProps(elm, env);
|
|
568
|
+
if (props !== '')
|
|
569
|
+
result += ` : ${props}`;
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
if (elm.value !== undefined) { // calculated element // @ts-ignore
|
|
574
|
+
result += ` = ${exprRenderer.renderExpr(elm.value, env)}`;
|
|
557
575
|
}
|
|
558
576
|
|
|
559
577
|
return `${result};\n`;
|
|
@@ -632,7 +650,8 @@ function csnToCdl( csn, options ) {
|
|
|
632
650
|
* @return {boolean} True, if there were annotations, false otherwise.
|
|
633
651
|
*/
|
|
634
652
|
function collectAnnos( annotateObj, art ) {
|
|
635
|
-
if (!art
|
|
653
|
+
if (!Object.hasOwnProperty.call(art, 'elements') &&
|
|
654
|
+
!Object.hasOwnProperty.call(art, 'enum'))
|
|
636
655
|
return false;
|
|
637
656
|
|
|
638
657
|
const dictKey = art.elements ? 'elements' : 'enum';
|
|
@@ -680,7 +699,7 @@ function csnToCdl( csn, options ) {
|
|
|
680
699
|
if (source.SELECT || source.SET) {
|
|
681
700
|
let result = `(${renderQuery(source, false, 'view', increaseIndent(env))})`;
|
|
682
701
|
if (source.as)
|
|
683
|
-
result +=
|
|
702
|
+
result += renderAlias(source.as);
|
|
684
703
|
|
|
685
704
|
return result;
|
|
686
705
|
}
|
|
@@ -770,7 +789,7 @@ function csnToCdl( csn, options ) {
|
|
|
770
789
|
let result = renderAbsolutePath(path, env);
|
|
771
790
|
if (path.as) {
|
|
772
791
|
// Source had an alias - render it
|
|
773
|
-
result +=
|
|
792
|
+
result += renderAlias(path.as);
|
|
774
793
|
}
|
|
775
794
|
return result;
|
|
776
795
|
}
|
|
@@ -825,7 +844,7 @@ function csnToCdl( csn, options ) {
|
|
|
825
844
|
// A new association (cast with `type` and `target`) uses `as` as its primary name, not alias.
|
|
826
845
|
const isNewAssociation = col.cast?.type && col.cast.target;
|
|
827
846
|
if (!isNewAssociation && col.as && !col.inline && !col.expand)
|
|
828
|
-
result +=
|
|
847
|
+
result += renderAlias(col.as);
|
|
829
848
|
|
|
830
849
|
// Explicit type provided for the view element?
|
|
831
850
|
if (col.cast) {
|
|
@@ -852,7 +871,7 @@ function csnToCdl( csn, options ) {
|
|
|
852
871
|
|
|
853
872
|
// s as alias { * }
|
|
854
873
|
if (obj.as && (obj.ref || obj.xpr || obj.val !== undefined || obj.func !== undefined))
|
|
855
|
-
result +=
|
|
874
|
+
result += renderAlias(obj.as);
|
|
856
875
|
|
|
857
876
|
// We found a leaf - no further drilling
|
|
858
877
|
if (!obj.inline && !obj.expand) {
|
|
@@ -882,7 +901,7 @@ function csnToCdl( csn, options ) {
|
|
|
882
901
|
|
|
883
902
|
// { * } as expand
|
|
884
903
|
if (!obj.ref && obj.as)
|
|
885
|
-
result +=
|
|
904
|
+
result += renderAlias(obj.as);
|
|
886
905
|
|
|
887
906
|
return result;
|
|
888
907
|
}
|
|
@@ -900,12 +919,16 @@ function csnToCdl( csn, options ) {
|
|
|
900
919
|
else if (obj && obj.doc === null) // empty doc comment needs to be rendered
|
|
901
920
|
return `\n${env.indent}/** */\n`;
|
|
902
921
|
|
|
903
|
-
|
|
922
|
+
let { doc } = obj;
|
|
923
|
+
if (/[^\\][*]\//.test(doc))
|
|
924
|
+
doc = doc.replace(/([^\\])[*]\//g, '$1*\\/');
|
|
925
|
+
|
|
926
|
+
// Smaller comment for single-line comments. If the comment starts or ends with whitespace
|
|
904
927
|
// we must use a block comment, or it will be lost when compiling the source again.
|
|
905
|
-
if (!obj.doc.includes('\n') &&
|
|
906
|
-
return `${env.indent}/** ${
|
|
928
|
+
if (!obj.doc.includes('\n') && !/(^\s)|(\s$)/.test(obj.doc))
|
|
929
|
+
return `${env.indent}/** ${doc} */\n`;
|
|
907
930
|
|
|
908
|
-
const comment =
|
|
931
|
+
const comment = doc.split('\n').map(line => `${env.indent} * ${line}`).join('\n');
|
|
909
932
|
return `${env.indent}/**\n${comment}\n${env.indent} */\n`;
|
|
910
933
|
}
|
|
911
934
|
|
|
@@ -984,8 +1007,7 @@ function csnToCdl( csn, options ) {
|
|
|
984
1007
|
result += ` excluding {\n${select.excluding.map(id => `${childEnv.indent}${quoteIdIfRequired(id)}`).join(',\n')}\n`;
|
|
985
1008
|
result += `${env.indent}}`;
|
|
986
1009
|
}
|
|
987
|
-
|
|
988
|
-
// a projection from a view any more
|
|
1010
|
+
|
|
989
1011
|
if (isLeadingQuery)
|
|
990
1012
|
result += renderActionsAndFunctions(query, env);
|
|
991
1013
|
|
|
@@ -1215,7 +1237,9 @@ function csnToCdl( csn, options ) {
|
|
|
1215
1237
|
// "many many" does not work in CDL, so we don't check for it.
|
|
1216
1238
|
}
|
|
1217
1239
|
|
|
1218
|
-
|
|
1240
|
+
const type = normalizeTypeRef(artifact.type);
|
|
1241
|
+
|
|
1242
|
+
if (!type && artifact.elements) {
|
|
1219
1243
|
result += renderElements(artifact, env);
|
|
1220
1244
|
if (!isTypeDef)
|
|
1221
1245
|
result += renderNullability(artifact);
|
|
@@ -1224,8 +1248,8 @@ function csnToCdl( csn, options ) {
|
|
|
1224
1248
|
}
|
|
1225
1249
|
|
|
1226
1250
|
// Association type
|
|
1227
|
-
if (
|
|
1228
|
-
const isComp =
|
|
1251
|
+
if (type === 'cds.Association' || type === 'cds.Composition') {
|
|
1252
|
+
const isComp = type === 'cds.Composition';
|
|
1229
1253
|
// Type, cardinality and target; CAPire uses CamelCase
|
|
1230
1254
|
result += isComp ? 'Composition' : 'Association';
|
|
1231
1255
|
result += renderCardinality(artifact);
|
|
@@ -1276,11 +1300,11 @@ function csnToCdl( csn, options ) {
|
|
|
1276
1300
|
}
|
|
1277
1301
|
|
|
1278
1302
|
// Reference to another artifact
|
|
1279
|
-
if (typeof
|
|
1303
|
+
if (typeof type === 'string') {
|
|
1280
1304
|
// If we get here, it must be a named type
|
|
1281
1305
|
result += renderNamedTypeWithParameters(artifact);
|
|
1282
1306
|
}
|
|
1283
|
-
else if (
|
|
1307
|
+
else if (type?.ref) {
|
|
1284
1308
|
result += renderAbsolutePath(artifact.type, env);
|
|
1285
1309
|
}
|
|
1286
1310
|
|
|
@@ -1316,21 +1340,22 @@ function csnToCdl( csn, options ) {
|
|
|
1316
1340
|
* @return {string}
|
|
1317
1341
|
*/
|
|
1318
1342
|
function renderNamedTypeWithParameters( artWithType ) {
|
|
1343
|
+
const type = normalizeTypeRef(artWithType.type);
|
|
1319
1344
|
let result = '';
|
|
1320
1345
|
|
|
1321
|
-
if (isBuiltinType(
|
|
1346
|
+
if (isBuiltinType(type)) {
|
|
1322
1347
|
// If there is a user-defined type that starts with the same short name
|
|
1323
1348
|
// (cds.Integer -> Integer), we render the full name, including the leading "cds."
|
|
1324
|
-
const shortHand =
|
|
1349
|
+
const shortHand = type.slice(4);
|
|
1325
1350
|
if (shortHand.startsWith('hana.') && hanaRequiresAbsolutePath)
|
|
1326
|
-
result +=
|
|
1351
|
+
result += type;
|
|
1327
1352
|
else if (usings.available.includes(shortHand))
|
|
1328
|
-
result +=
|
|
1353
|
+
result += type;
|
|
1329
1354
|
else
|
|
1330
1355
|
result += shortHand;
|
|
1331
1356
|
}
|
|
1332
1357
|
else {
|
|
1333
|
-
result += renderDefinitionReference(
|
|
1358
|
+
result += renderDefinitionReference(type);
|
|
1334
1359
|
}
|
|
1335
1360
|
|
|
1336
1361
|
result += renderTypeParameters(artWithType);
|
|
@@ -1341,7 +1366,7 @@ function csnToCdl( csn, options ) {
|
|
|
1341
1366
|
/**
|
|
1342
1367
|
* Render the 'enum { ... } part of a type declaration
|
|
1343
1368
|
*
|
|
1344
|
-
* @param {CSN.
|
|
1369
|
+
* @param {CSN.EnumList} enumPart
|
|
1345
1370
|
* @param {CdlRenderEnvironment} env
|
|
1346
1371
|
* @return {string}
|
|
1347
1372
|
*/
|
|
@@ -1391,7 +1416,8 @@ function csnToCdl( csn, options ) {
|
|
|
1391
1416
|
const values = keys.map(key => `${quoteAnnotationPathIfRequired(key)}: ${renderAnnotationValue(x[key], childEnv)}`);
|
|
1392
1417
|
if (values.length <= 1)
|
|
1393
1418
|
return `{ ${values.join(', ')} }`;
|
|
1394
|
-
|
|
1419
|
+
const valueList = values.join(`,\n${childEnv.indent}`);
|
|
1420
|
+
return `{\n${childEnv.indent}${valueList}\n${env.indent}}`;
|
|
1395
1421
|
}
|
|
1396
1422
|
// Null
|
|
1397
1423
|
else if (x === null) {
|
|
@@ -1539,7 +1565,7 @@ function csnToCdl( csn, options ) {
|
|
|
1539
1565
|
* @return {string}
|
|
1540
1566
|
*/
|
|
1541
1567
|
function renderBracketCardinality( art ) {
|
|
1542
|
-
const isComp = art.type === 'cds.Composition';
|
|
1568
|
+
const isComp = normalizeTypeRef(art.type) === 'cds.Composition';
|
|
1543
1569
|
const suffix = (isComp ? ' of ' : ' to ');
|
|
1544
1570
|
const card = art.cardinality;
|
|
1545
1571
|
|
|
@@ -1583,7 +1609,7 @@ function csnToCdl( csn, options ) {
|
|
|
1583
1609
|
* @return {string}
|
|
1584
1610
|
*/
|
|
1585
1611
|
function renderSimpleCardinality( elem ) {
|
|
1586
|
-
let result = (elem.type === 'cds.Association' ? ' to ' : ' of ');
|
|
1612
|
+
let result = (normalizeTypeRef(elem.type) === 'cds.Association' ? ' to ' : ' of ');
|
|
1587
1613
|
if (!elem.cardinality)
|
|
1588
1614
|
return result;
|
|
1589
1615
|
if (elem.cardinality.max === '*')
|
|
@@ -1610,10 +1636,20 @@ function csnToCdl( csn, options ) {
|
|
|
1610
1636
|
* @return {string}
|
|
1611
1637
|
*/
|
|
1612
1638
|
function renderForeignKey( fKey, env ) {
|
|
1613
|
-
const alias = fKey.as ? (
|
|
1639
|
+
const alias = fKey.as ? renderAlias(fKey.as) : '';
|
|
1614
1640
|
return exprRenderer.renderExpr(fKey, env) + alias;
|
|
1615
1641
|
}
|
|
1616
1642
|
|
|
1643
|
+
/**
|
|
1644
|
+
* Render an explicit alias, e.g. for columns.
|
|
1645
|
+
*
|
|
1646
|
+
* @param {string} alias
|
|
1647
|
+
* @return {string}
|
|
1648
|
+
*/
|
|
1649
|
+
function renderAlias( alias ) {
|
|
1650
|
+
return ` as ${quoteIdIfRequired(alias)}`;
|
|
1651
|
+
}
|
|
1652
|
+
|
|
1617
1653
|
/**
|
|
1618
1654
|
* Render (primitive) type parameters of artifact 'artWithType', i.e.
|
|
1619
1655
|
* length, precision and scale (even if incomplete), plus any other unknown ones.
|
|
@@ -1695,7 +1731,7 @@ function csnToCdl( csn, options ) {
|
|
|
1695
1731
|
result += '(';
|
|
1696
1732
|
|
|
1697
1733
|
if (annoRequiresQuoting || variantRequiresQuoting)
|
|
1698
|
-
result +=
|
|
1734
|
+
result += delimitedId(name);
|
|
1699
1735
|
else
|
|
1700
1736
|
result += name;
|
|
1701
1737
|
|
|
@@ -1775,9 +1811,9 @@ function csnToCdl( csn, options ) {
|
|
|
1775
1811
|
return `${funcDef} ${this.renderExpr(x.xpr)}`; // xpr[0] is 'over'
|
|
1776
1812
|
},
|
|
1777
1813
|
func(x) {
|
|
1778
|
-
if (keywords.cdl_functions.includes(x.func.toUpperCase()))
|
|
1814
|
+
if (keywords.cdl_functions.includes(x.func.toUpperCase()) && !x.args)
|
|
1779
1815
|
return x.func;
|
|
1780
|
-
const name =
|
|
1816
|
+
const name = smartFunctionId(x.func);
|
|
1781
1817
|
return `${name}(${renderArguments( x, '=>', this.env )})`;
|
|
1782
1818
|
},
|
|
1783
1819
|
xpr(x) {
|
|
@@ -1863,7 +1899,7 @@ function quotePathIfRequired( path ) {
|
|
|
1863
1899
|
function quoteIdIfRequired( id, additionalKeywords ) {
|
|
1864
1900
|
// Quote if required for CDL
|
|
1865
1901
|
if (requiresQuotingForCdl(id, additionalKeywords || []))
|
|
1866
|
-
return
|
|
1902
|
+
return delimitedId(id);
|
|
1867
1903
|
return id;
|
|
1868
1904
|
}
|
|
1869
1905
|
|
|
@@ -1890,7 +1926,7 @@ function quoteAnnotationPathIfRequired( anno ) {
|
|
|
1890
1926
|
* @param id
|
|
1891
1927
|
* @returns {string}
|
|
1892
1928
|
*/
|
|
1893
|
-
function
|
|
1929
|
+
function delimitedId( id ) {
|
|
1894
1930
|
return `![${id.replace(/]/g, ']]')}]`;
|
|
1895
1931
|
}
|
|
1896
1932
|
|
|
@@ -1980,6 +2016,31 @@ function getKeywordsForSpecialFunctionArgument( funcName, argumentIndex ) {
|
|
|
1980
2016
|
return additionalKeywords;
|
|
1981
2017
|
}
|
|
1982
2018
|
|
|
2019
|
+
/**
|
|
2020
|
+
* Get a list of all special keywords for the given function.
|
|
2021
|
+
*
|
|
2022
|
+
* @param {string} funcName
|
|
2023
|
+
* @return {undefined|string[]}
|
|
2024
|
+
*/
|
|
2025
|
+
function getAllKeywordsForSpecialFunction( funcName ) {
|
|
2026
|
+
if (specialFunctionKeywords[funcName])
|
|
2027
|
+
return specialFunctionKeywords[funcName];
|
|
2028
|
+
else if (!specialFunctions[funcName])
|
|
2029
|
+
return undefined;
|
|
2030
|
+
|
|
2031
|
+
const additionalKeywords = [];
|
|
2032
|
+
for (const arg of specialFunctions[funcName]) {
|
|
2033
|
+
if (arg.intro)
|
|
2034
|
+
additionalKeywords.push(...arg.intro);
|
|
2035
|
+
if (arg.expr)
|
|
2036
|
+
additionalKeywords.push(...arg.expr);
|
|
2037
|
+
if (arg.separator)
|
|
2038
|
+
additionalKeywords.push(...arg.separator);
|
|
2039
|
+
}
|
|
2040
|
+
specialFunctionKeywords[funcName] = additionalKeywords;
|
|
2041
|
+
return additionalKeywords;
|
|
2042
|
+
}
|
|
2043
|
+
|
|
1983
2044
|
/**
|
|
1984
2045
|
* Render the given string. Uses back-tick strings.
|
|
1985
2046
|
* env is used for indentation of three-back-tick strings.
|
|
@@ -2068,6 +2129,37 @@ function availableFirstPathSteps( csn ) {
|
|
|
2068
2129
|
return Array.from(unique);
|
|
2069
2130
|
}
|
|
2070
2131
|
|
|
2132
|
+
/**
|
|
2133
|
+
* Returns a delimited identifier if the given identifier needs quoting.
|
|
2134
|
+
* Because "special functions" such as SAP HANA RegEx functions have local keywords that
|
|
2135
|
+
* are not default CDL keywords, specify a function name to take care of that.
|
|
2136
|
+
*
|
|
2137
|
+
* @param {string} id
|
|
2138
|
+
* @param {null|string} insideFunction
|
|
2139
|
+
* @return {string}
|
|
2140
|
+
*/
|
|
2141
|
+
function smartId( id, insideFunction = null ) {
|
|
2142
|
+
insideFunction = insideFunction?.toUpperCase();
|
|
2143
|
+
if (!insideFunction || !specialFunctions[insideFunction])
|
|
2144
|
+
return quoteIdIfRequired(id);
|
|
2145
|
+
return quoteIdIfRequired(id, getAllKeywordsForSpecialFunction(insideFunction));
|
|
2146
|
+
}
|
|
2147
|
+
|
|
2148
|
+
/**
|
|
2149
|
+
* Quote the given function name if required.
|
|
2150
|
+
*
|
|
2151
|
+
* @param {string} funcName
|
|
2152
|
+
* @return {string}
|
|
2153
|
+
*/
|
|
2154
|
+
function smartFunctionId( funcName ) {
|
|
2155
|
+
const funcId = funcName.toUpperCase();
|
|
2156
|
+
const requiresQuoting = !identifierRegex.test(funcName) ||
|
|
2157
|
+
(keywords.cdl.includes(funcId) && !specialFunctions[funcId]);
|
|
2158
|
+
if (requiresQuoting)
|
|
2159
|
+
return delimitedId(funcName);
|
|
2160
|
+
return funcName;
|
|
2161
|
+
}
|
|
2162
|
+
|
|
2071
2163
|
/**
|
|
2072
2164
|
* @typedef CdlRenderEnvironment Rendering environment used throughout the render process.
|
|
2073
2165
|
*
|
|
@@ -2078,4 +2170,9 @@ function availableFirstPathSteps( csn ) {
|
|
|
2078
2170
|
* @property {string[]} [additionalKeywords] For function rendering: Words that are also keywords.
|
|
2079
2171
|
*/
|
|
2080
2172
|
|
|
2081
|
-
module.exports = {
|
|
2173
|
+
module.exports = {
|
|
2174
|
+
csnToCdl,
|
|
2175
|
+
smartId,
|
|
2176
|
+
smartFunctionId,
|
|
2177
|
+
delimitedId,
|
|
2178
|
+
};
|