@sap/cds-compiler 2.15.8 → 3.1.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 +102 -1590
- package/bin/.eslintrc.json +2 -1
- package/bin/cdsc.js +61 -46
- package/doc/API.md +11 -0
- package/doc/CHANGELOG_ARCHIVE.md +1592 -0
- package/doc/CHANGELOG_BETA.md +26 -5
- package/doc/CHANGELOG_DEPRECATED.md +55 -1
- package/doc/{DeprecatedOptions.md → DeprecatedOptions_v2.md} +3 -1
- package/doc/Versioning.md +20 -1
- package/lib/api/.eslintrc.json +2 -2
- package/lib/api/main.js +282 -156
- package/lib/api/options.js +17 -88
- package/lib/api/validate.js +6 -10
- package/lib/base/keywords.js +280 -110
- package/lib/base/message-registry.js +85 -25
- package/lib/base/messages.js +119 -89
- package/lib/base/model.js +46 -2
- package/lib/base/optionProcessorHelper.js +53 -21
- package/lib/checks/actionsFunctions.js +15 -12
- package/lib/checks/annotationsOData.js +1 -1
- package/lib/checks/cdsPersistence.js +1 -0
- package/lib/checks/elements.js +6 -6
- package/lib/checks/invalidTarget.js +1 -1
- package/lib/checks/nonexpandableStructured.js +1 -1
- package/lib/checks/queryNoDbArtifacts.js +2 -1
- package/lib/checks/selectItems.js +101 -15
- package/lib/checks/types.js +7 -8
- package/lib/checks/utils.js +2 -2
- package/lib/checks/validator.js +3 -3
- package/lib/compiler/assert-consistency.js +78 -21
- package/lib/compiler/base.js +6 -4
- package/lib/compiler/builtins.js +177 -10
- package/lib/compiler/checks.js +1 -1
- package/lib/compiler/define.js +28 -23
- package/lib/compiler/extend.js +75 -18
- package/lib/compiler/finalize-parse-cdl.js +25 -18
- package/lib/compiler/index.js +27 -11
- package/lib/compiler/moduleLayers.js +7 -0
- package/lib/compiler/populate.js +26 -39
- package/lib/compiler/propagator.js +12 -7
- package/lib/compiler/resolve.js +207 -236
- package/lib/compiler/shared.js +100 -93
- package/lib/compiler/tweak-assocs.js +13 -20
- package/lib/compiler/utils.js +20 -6
- package/lib/edm/annotations/preprocessAnnotations.js +12 -13
- package/lib/edm/csn2edm.js +35 -37
- package/lib/edm/edm.js +22 -13
- package/lib/edm/edmAnnoPreprocessor.js +349 -0
- package/lib/edm/edmInboundChecks.js +85 -0
- package/lib/edm/edmPreprocessor.js +338 -689
- package/lib/edm/edmUtils.js +97 -67
- package/lib/gen/Dictionary.json +29 -9
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +8 -31
- package/lib/gen/language.tokens +105 -114
- package/lib/gen/languageLexer.interp +1 -34
- package/lib/gen/languageLexer.js +892 -1007
- package/lib/gen/languageLexer.tokens +95 -106
- package/lib/gen/languageParser.js +20629 -22474
- package/lib/inspect/.eslintrc.json +4 -0
- package/lib/inspect/index.js +14 -0
- package/lib/inspect/inspectModelStatistics.js +81 -0
- package/lib/inspect/inspectPropagation.js +189 -0
- package/lib/inspect/inspectUtils.js +44 -0
- package/lib/json/from-csn.js +74 -69
- package/lib/json/to-csn.js +17 -14
- package/lib/language/antlrParser.js +2 -2
- package/lib/language/docCommentParser.js +61 -38
- package/lib/language/errorStrategy.js +52 -40
- package/lib/language/genericAntlrParser.js +424 -292
- package/lib/language/language.g4 +604 -687
- package/lib/language/multiLineStringParser.js +14 -42
- package/lib/language/textUtils.js +44 -0
- package/lib/main.d.ts +28 -42
- package/lib/main.js +104 -81
- package/lib/model/api.js +1 -1
- package/lib/model/csnRefs.js +57 -30
- package/lib/model/csnUtils.js +189 -287
- package/lib/model/revealInternalProperties.js +32 -10
- package/lib/model/sortViews.js +32 -31
- package/lib/modelCompare/compare.js +3 -0
- package/lib/optionProcessor.js +91 -57
- package/lib/render/.eslintrc.json +1 -1
- package/lib/render/DuplicateChecker.js +4 -7
- package/lib/render/manageConstraints.js +70 -2
- package/lib/render/toCdl.js +387 -367
- package/lib/render/toHdbcds.js +20 -16
- package/lib/render/toRename.js +44 -22
- package/lib/render/toSql.js +81 -59
- package/lib/render/utils/common.js +16 -3
- package/lib/render/utils/sql.js +20 -19
- package/lib/sql-identifier.js +6 -0
- package/lib/transform/db/.eslintrc.json +3 -2
- package/lib/transform/db/associations.js +43 -35
- package/lib/transform/db/cdsPersistence.js +5 -16
- package/lib/transform/db/constraints.js +1 -1
- package/lib/transform/db/expansion.js +7 -6
- package/lib/transform/db/flattening.js +16 -18
- package/lib/transform/db/transformExists.js +7 -5
- package/lib/transform/db/views.js +3 -3
- package/lib/transform/draft/.eslintrc.json +2 -2
- package/lib/transform/draft/db.js +6 -6
- package/lib/transform/draft/odata.js +6 -7
- package/lib/transform/forHanaNew.js +30 -24
- package/lib/transform/forOdataNew.js +14 -16
- package/lib/transform/localized.js +35 -25
- package/lib/transform/odata/toFinalBaseType.js +10 -10
- package/lib/transform/odata/typesExposure.js +17 -8
- package/lib/transform/odata/utils.js +1 -38
- package/lib/transform/transformUtilsNew.js +63 -77
- package/lib/transform/translateAssocsToJoins.js +2 -2
- package/lib/transform/universalCsn/.eslintrc.json +2 -2
- package/lib/transform/universalCsn/coreComputed.js +11 -6
- package/lib/transform/universalCsn/universalCsnEnricher.js +33 -5
- package/lib/utils/file.js +31 -21
- package/lib/utils/moduleResolve.js +0 -1
- package/lib/utils/timetrace.js +20 -21
- package/package.json +34 -4
- package/share/messages/syntax-expected-integer.md +9 -8
- package/doc/ApiMigration.md +0 -237
- package/doc/CommandLineMigration.md +0 -58
- package/doc/ErrorMessages.md +0 -175
- package/doc/FioriAnnotations.md +0 -94
- package/doc/ODataTransformation.md +0 -273
- package/lib/backends.js +0 -529
- package/lib/checks/unknownMagic.js +0 -41
- package/lib/fix_antlr4-8_warning.js +0 -56
|
@@ -279,6 +279,18 @@ const cdsToSqlTypes = {
|
|
|
279
279
|
'cds.hana.BINARY': 'BINARY',
|
|
280
280
|
'cds.hana.SMALLDECIMAL': 'DECIMAL',
|
|
281
281
|
},
|
|
282
|
+
postgres: {
|
|
283
|
+
// TODO: Type mapping for binary types is not correct, yet.
|
|
284
|
+
// We can't use text types for binary on PostgreSQL due to NUL!
|
|
285
|
+
'cds.String': 'VARCHAR',
|
|
286
|
+
'cds.LargeString': 'text',
|
|
287
|
+
'cds.hana.CLOB': 'text',
|
|
288
|
+
'cds.LargeBinary': 'bytea',
|
|
289
|
+
'cds.Binary': 'bytea',
|
|
290
|
+
'cds.hana.BINARY': 'bytea',
|
|
291
|
+
'cds.Double': 'double precision',
|
|
292
|
+
'cds.hana.TINYINT': 'INTEGER',
|
|
293
|
+
},
|
|
282
294
|
};
|
|
283
295
|
|
|
284
296
|
/**
|
|
@@ -346,11 +358,10 @@ function hasHanaComment(obj, options) {
|
|
|
346
358
|
* Return the comment of the given artifact or element.
|
|
347
359
|
* Uses the first block (everything up to the first empty line (double \n)).
|
|
348
360
|
* Remove leading/trailing whitespace.
|
|
349
|
-
* Does not escape any characters.
|
|
361
|
+
* Does not escape any characters, use e.g. `getEscapedHanaComment()` for HDBCDS.
|
|
350
362
|
*
|
|
351
363
|
* @param {CSN.Artifact|CSN.Element} obj
|
|
352
364
|
* @returns {string}
|
|
353
|
-
* @todo Warning/info to user?
|
|
354
365
|
*/
|
|
355
366
|
function getHanaComment(obj) {
|
|
356
367
|
return obj.doc.split('\n\n')[0].trim();
|
|
@@ -375,7 +386,7 @@ function getSqlSnippets(options, obj) {
|
|
|
375
386
|
* A function used to render a certain part of an expression object
|
|
376
387
|
*
|
|
377
388
|
* @callback renderPart
|
|
378
|
-
* @param {object
|
|
389
|
+
* @param {object|array} expression
|
|
379
390
|
* @param {CdlRenderEnvironment} env
|
|
380
391
|
* @this {{inline: Boolean, nestedExpr: Boolean}}
|
|
381
392
|
* @returns {string}
|
|
@@ -397,6 +408,8 @@ function getSqlSnippets(options, obj) {
|
|
|
397
408
|
* @property {renderPart} xpr
|
|
398
409
|
* @property {renderPart} SELECT
|
|
399
410
|
* @property {renderPart} SET
|
|
411
|
+
* @property {boolean} [inline]
|
|
412
|
+
* @property {boolean} [nestedExpr]
|
|
400
413
|
*/
|
|
401
414
|
|
|
402
415
|
/**
|
package/lib/render/utils/sql.js
CHANGED
|
@@ -40,18 +40,18 @@ function renderReferentialConstraint(constraint, indent, toUpperCase, csn, optio
|
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
const renderAsHdbconstraint = options.transformation === 'hdbcds' ||
|
|
43
|
-
|
|
43
|
+
options.src === 'hdi' ||
|
|
44
44
|
(options.manageConstraints && options.manageConstraints.src === 'hdi');
|
|
45
45
|
|
|
46
|
-
const {
|
|
47
|
-
const forSqlite = options.
|
|
46
|
+
const { sqlMapping } = options;
|
|
47
|
+
const forSqlite = options.sqlDialect === 'sqlite';
|
|
48
48
|
let result = '';
|
|
49
49
|
result += `${indent}CONSTRAINT ${quoteId(constraint.identifier)}\n`;
|
|
50
50
|
if (renderAsHdbconstraint)
|
|
51
|
-
result += `${indent}ON ${quoteId(getResultingName(csn,
|
|
51
|
+
result += `${indent}ON ${quoteId(getResultingName(csn, sqlMapping, constraint.dependentTable))}\n`;
|
|
52
52
|
if (!alterConstraint) {
|
|
53
53
|
result += `${indent}FOREIGN KEY(${constraint.foreignKey.map(quoteId).join(', ')})\n`;
|
|
54
|
-
result += `${indent}REFERENCES ${quoteId(getResultingName(csn,
|
|
54
|
+
result += `${indent}REFERENCES ${quoteId(getResultingName(csn, sqlMapping, constraint.parentTable))}(${constraint.parentKey.map(quoteId).join(', ')})\n`;
|
|
55
55
|
const onDeleteRemark = constraint.onDeleteRemark ? ` -- ${constraint.onDeleteRemark}` : '';
|
|
56
56
|
|
|
57
57
|
// omit 'RESTRICT' action for ON UPDATE / ON DELETE, because it interferes with deferred constraint check
|
|
@@ -65,12 +65,13 @@ function renderReferentialConstraint(constraint, indent, toUpperCase, csn, optio
|
|
|
65
65
|
}
|
|
66
66
|
}
|
|
67
67
|
// constraint enforcement / validation must be switched off using sqlite pragma statement
|
|
68
|
-
|
|
68
|
+
// Does not include HDBCDS.
|
|
69
|
+
if (options.toSql && !forSqlite) {
|
|
69
70
|
result += `${indent}${!constraint.validated ? 'NOT ' : ''}VALIDATED\n`;
|
|
70
71
|
result += `${indent}${!constraint.enforced ? 'NOT ' : ''}ENFORCED\n`;
|
|
71
72
|
}
|
|
72
73
|
// for sqlite, the DEFERRABLE keyword is required
|
|
73
|
-
result += `${indent}${
|
|
74
|
+
result += `${indent}${forSqlite ? 'DEFERRABLE ' : ''}INITIALLY DEFERRED`;
|
|
74
75
|
return result;
|
|
75
76
|
}
|
|
76
77
|
|
|
@@ -85,9 +86,9 @@ function getIdentifierUtils(options) {
|
|
|
85
86
|
/**
|
|
86
87
|
* Return 'name' with appropriate "-quotes.
|
|
87
88
|
* Additionally perform the following conversions on 'name'
|
|
88
|
-
* If 'options.
|
|
89
|
+
* If 'options.sqlMapping' is 'plain'
|
|
89
90
|
* - replace '.' or '::' by '_'
|
|
90
|
-
* else if 'options.
|
|
91
|
+
* else if 'options.sqlMapping' is 'quoted'
|
|
91
92
|
* - replace '::' by '.'
|
|
92
93
|
* Complain about names that collide with known SQL keywords or functions
|
|
93
94
|
*
|
|
@@ -97,13 +98,13 @@ function getIdentifierUtils(options) {
|
|
|
97
98
|
function quoteSqlId(name) {
|
|
98
99
|
name = prepareIdentifier(name);
|
|
99
100
|
|
|
100
|
-
switch (options.
|
|
101
|
+
switch (options.sqlMapping) {
|
|
101
102
|
case 'plain':
|
|
102
|
-
return smartId(name, options.
|
|
103
|
+
return smartId(name, options.sqlDialect);
|
|
103
104
|
case 'quoted':
|
|
104
|
-
return delimitedId(name, options.
|
|
105
|
+
return delimitedId(name, options.sqlDialect);
|
|
105
106
|
case 'hdbcds':
|
|
106
|
-
return delimitedId(name, options.
|
|
107
|
+
return delimitedId(name, options.sqlDialect);
|
|
107
108
|
default:
|
|
108
109
|
return undefined;
|
|
109
110
|
}
|
|
@@ -111,9 +112,9 @@ function getIdentifierUtils(options) {
|
|
|
111
112
|
|
|
112
113
|
/**
|
|
113
114
|
* Prepare an identifier:
|
|
114
|
-
* If 'options.
|
|
115
|
+
* If 'options.sqlMapping' is 'plain'
|
|
115
116
|
* - replace '.' or '::' by '_'
|
|
116
|
-
* else if 'options.
|
|
117
|
+
* else if 'options.sqlMapping' is 'quoted'
|
|
117
118
|
* - replace '::' by '.'
|
|
118
119
|
*
|
|
119
120
|
* @param {string} name Identifier to prepare
|
|
@@ -121,11 +122,11 @@ function getIdentifierUtils(options) {
|
|
|
121
122
|
*/
|
|
122
123
|
function prepareIdentifier(name) {
|
|
123
124
|
// Sanity check
|
|
124
|
-
if (options.
|
|
125
|
-
throw new ModelError(`Not expecting ${options.
|
|
125
|
+
if (options.sqlDialect === 'sqlite' && options.sqlMapping !== 'plain')
|
|
126
|
+
throw new ModelError(`Not expecting ${options.sqlMapping} names for 'sqlite' dialect`);
|
|
126
127
|
|
|
127
128
|
|
|
128
|
-
switch (options.
|
|
129
|
+
switch (options.sqlMapping) {
|
|
129
130
|
case 'plain':
|
|
130
131
|
return name.replace(/(\.|::)/g, '_');
|
|
131
132
|
case 'quoted':
|
|
@@ -133,7 +134,7 @@ function getIdentifierUtils(options) {
|
|
|
133
134
|
case 'hdbcds':
|
|
134
135
|
return name;
|
|
135
136
|
default:
|
|
136
|
-
throw new ModelError(`No matching rendering found for naming mode ${options.
|
|
137
|
+
throw new ModelError(`No matching rendering found for naming mode ${options.sqlMapping}`);
|
|
137
138
|
}
|
|
138
139
|
}
|
|
139
140
|
}
|
package/lib/sql-identifier.js
CHANGED
|
@@ -45,6 +45,12 @@ const sqlDialects = {
|
|
|
45
45
|
effectiveName: name => name,
|
|
46
46
|
asDelimitedId: name => `"${ name.replace(/"/g, '""')}"`,
|
|
47
47
|
},
|
|
48
|
+
postgres: {
|
|
49
|
+
regularRegex: /^[A-Za-z_][A-Za-z_$0-9]*$/,
|
|
50
|
+
reservedWords: keywords.postgres,
|
|
51
|
+
effectiveName: name => name.toLowerCase(),
|
|
52
|
+
asDelimitedId: name => `"${ name.replace(/"/g, '""')}"`,
|
|
53
|
+
},
|
|
48
54
|
hana: {
|
|
49
55
|
regularRegex: /^[A-Za-z_][A-Za-z_$#0-9]*$/,
|
|
50
56
|
reservedWords: keywords.hana,
|
|
@@ -17,15 +17,16 @@
|
|
|
17
17
|
"sonarjs/prefer-single-boolean-return": "off",
|
|
18
18
|
// Very whiny and nitpicky
|
|
19
19
|
"sonarjs/cognitive-complexity": "off",
|
|
20
|
+
"sonarjs/no-duplicate-string": "off",
|
|
20
21
|
// Does not recognize TS types
|
|
21
22
|
"jsdoc/no-undefined-types": "off"
|
|
22
23
|
},
|
|
23
24
|
"parserOptions": {
|
|
24
|
-
"ecmaVersion":
|
|
25
|
+
"ecmaVersion": 2020,
|
|
25
26
|
"sourceType": "script"
|
|
26
27
|
},
|
|
27
28
|
"env": {
|
|
28
|
-
"
|
|
29
|
+
"es2020": true,
|
|
29
30
|
"node": true
|
|
30
31
|
},
|
|
31
32
|
"settings": {
|
|
@@ -107,12 +107,12 @@ function attachOnConditions(csn, pathDelimiter) {
|
|
|
107
107
|
* @param {string} pathDelimiter
|
|
108
108
|
* @returns {(artifact: CSN.Artifact, artifactName: string) => void} Callback for forEachDefinition
|
|
109
109
|
*/
|
|
110
|
-
function
|
|
110
|
+
function getFKAccessFinalizer(csn, pathDelimiter) {
|
|
111
111
|
const {
|
|
112
112
|
inspectRef,
|
|
113
113
|
} = getUtils(csn);
|
|
114
114
|
|
|
115
|
-
return
|
|
115
|
+
return handleManagedAssocSteps;
|
|
116
116
|
|
|
117
117
|
/**
|
|
118
118
|
* Loop over all elements and for all unmanaged associations translate
|
|
@@ -123,45 +123,53 @@ function getManagedAssocStepsInOnConditionFinalizer(csn, pathDelimiter) {
|
|
|
123
123
|
* @param {CSN.Artifact} artifact Artifact to check
|
|
124
124
|
* @param {string} artifactName Name of the artifact
|
|
125
125
|
*/
|
|
126
|
-
function
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
//
|
|
134
|
-
|
|
135
|
-
const
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
for (let i = links.length - 1; i >= 0; i--) {
|
|
139
|
-
const link = links[i];
|
|
140
|
-
// We found the latest managed assoc path step
|
|
141
|
-
if (link.art && link.art.target && link.art.keys &&
|
|
126
|
+
function handleManagedAssocSteps(artifact, artifactName) {
|
|
127
|
+
const transformer = {
|
|
128
|
+
ref: (refOwner, prop, ref, path) => {
|
|
129
|
+
// [<assoc base>.]<managed assoc>.<field>
|
|
130
|
+
if (ref.length > 1) {
|
|
131
|
+
const { links } = inspectRef(path);
|
|
132
|
+
if (links) {
|
|
133
|
+
// eslint-disable-next-line for-direction
|
|
134
|
+
for (let i = links.length - 1; i >= 0; i--) {
|
|
135
|
+
const link = links[i];
|
|
136
|
+
// We found the latest managed assoc path step
|
|
137
|
+
if (link.art && link.art.target && link.art.keys &&
|
|
142
138
|
// Doesn't work when ref-target (filter condition) or similar is used
|
|
143
139
|
!ref.slice(i).some(refElement => typeof refElement !== 'string')) {
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
}
|
|
156
|
-
}
|
|
140
|
+
// We join the managed assoc with everything following it
|
|
141
|
+
const sourceElementName = ref.slice(i).join(pathDelimiter);
|
|
142
|
+
const source = findSource(links, i - 1) || artifact;
|
|
143
|
+
// allow specifying managed assoc on the source side
|
|
144
|
+
const fks = link.art.keys.filter(fk => ref[i] + pathDelimiter + fk.ref[0] === sourceElementName);
|
|
145
|
+
if (fks && fks.length >= 1) {
|
|
146
|
+
const fk = fks[0];
|
|
147
|
+
const managedAssocStepName = refOwner.ref[i];
|
|
148
|
+
const fkName = `${managedAssocStepName}${pathDelimiter}${fk.as}`;
|
|
149
|
+
if (source && source.elements[fkName])
|
|
150
|
+
refOwner.ref = [ ...ref.slice(0, i), fkName ];
|
|
157
151
|
}
|
|
158
152
|
}
|
|
159
153
|
}
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
},
|
|
157
|
+
};
|
|
158
|
+
for (const elemName in artifact.elements) {
|
|
159
|
+
const elem = artifact.elements[elemName];
|
|
160
|
+
// The association is an unmanaged one
|
|
161
|
+
if (!elem.keys && elem.target && elem.on)
|
|
162
|
+
applyTransformationsOnNonDictionary(elem, 'on', transformer, {}, [ 'definitions', artifactName, 'elements', elemName ]);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (artifact.query || artifact.projection) {
|
|
166
|
+
applyTransformationsOnNonDictionary(artifact, artifact.query ? 'query' : 'projection', {
|
|
167
|
+
orderBy: (parent, prop, thing, path) => applyTransformationsOnNonDictionary(parent, prop, transformer, {}, path),
|
|
168
|
+
groupBy: (parent, prop, thing, path) => applyTransformationsOnNonDictionary(parent, prop, transformer, {}, path),
|
|
169
|
+
}, {}, [ 'definitions', artifactName ]);
|
|
163
170
|
}
|
|
164
171
|
|
|
172
|
+
|
|
165
173
|
/**
|
|
166
174
|
* Find out where the managed association is
|
|
167
175
|
*
|
|
@@ -183,5 +191,5 @@ function getManagedAssocStepsInOnConditionFinalizer(csn, pathDelimiter) {
|
|
|
183
191
|
}
|
|
184
192
|
module.exports = {
|
|
185
193
|
attachOnConditions,
|
|
186
|
-
|
|
194
|
+
getFKAccessFinalizer,
|
|
187
195
|
};
|
|
@@ -78,29 +78,18 @@ function getAssocToSkippedIgnorer(csn, options, messageFunctions) {
|
|
|
78
78
|
* @param {string} memberName
|
|
79
79
|
* @param {string} prop
|
|
80
80
|
* @param {CSN.Path} path
|
|
81
|
-
* @todo Why do we check for @cds.persistence.exists here if the parent-function only calls this for skip/abstract?
|
|
82
81
|
*/
|
|
83
82
|
function ignore(member, memberName, prop, path) {
|
|
84
|
-
if (options.sqlDialect === 'hana' &&
|
|
85
|
-
|
|
83
|
+
if (options.sqlDialect === 'hana' &&
|
|
84
|
+
!member._ignore && member.target &&
|
|
85
|
+
isAssocOrComposition(member.type) &&
|
|
86
|
+
!isPersistedOnDatabase(csn.definitions[member.target])) {
|
|
86
87
|
info(null, path,
|
|
87
|
-
{ target: member.target, anno:
|
|
88
|
+
{ target: member.target, anno: '@cds.persistence.skip' },
|
|
88
89
|
'Association has been removed as it\'s target $(TARGET) is annotated with $(ANNO)');
|
|
89
90
|
member._ignore = true;
|
|
90
91
|
}
|
|
91
92
|
}
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* Check whether the given artifact is an unreachable association target because it will not "realy" hit the database:
|
|
95
|
-
* - @cds.persistence.skip/exists
|
|
96
|
-
* - abstract
|
|
97
|
-
*
|
|
98
|
-
* @param {CSN.Artifact} art
|
|
99
|
-
* @returns {boolean}
|
|
100
|
-
*/
|
|
101
|
-
function isUnreachableAssociationTarget(art) {
|
|
102
|
-
return !isPersistedOnDatabase(art) || hasAnnotationValue(art, exists);
|
|
103
|
-
}
|
|
104
93
|
}
|
|
105
94
|
|
|
106
95
|
/**
|
|
@@ -517,7 +517,7 @@ function createReferentialConstraints(csn, options) {
|
|
|
517
517
|
onDeleteRemark = `Up_ link for Composition "${$foreignKeyConstraint.upLinkFor}" implies existential dependency`;
|
|
518
518
|
referentialConstraints[`${getResultingName(csn, 'quoted', artifactName)}_${$foreignKeyConstraint.sourceAssociation}`] = {
|
|
519
519
|
// constraint identifier start with `c__` to avoid name clashes
|
|
520
|
-
identifier: `c__${getResultingName(csn, options.
|
|
520
|
+
identifier: `c__${getResultingName(csn, options.sqlMapping, artifactName)}_${$foreignKeyConstraint.sourceAssociation}`,
|
|
521
521
|
foreignKey: dependentKey,
|
|
522
522
|
parentKey,
|
|
523
523
|
dependentTable: artifactName,
|
|
@@ -24,10 +24,11 @@ const { forEach } = require('../../utils/objectUtils');
|
|
|
24
24
|
* @param {object} iterateOptions
|
|
25
25
|
*/
|
|
26
26
|
function expandStructureReferences(csn, options, pathDelimiter, { error, info, throwWithAnyError }, iterateOptions = {}) {
|
|
27
|
+
const csnUtils = getUtils(csn);
|
|
27
28
|
const {
|
|
28
|
-
isStructured, get$combined,
|
|
29
|
-
} =
|
|
30
|
-
let { effectiveType, inspectRef } =
|
|
29
|
+
isStructured, get$combined, getFinalBaseTypeWithProps, getServiceName,
|
|
30
|
+
} = csnUtils;
|
|
31
|
+
let { effectiveType, inspectRef } = csnUtils;
|
|
31
32
|
|
|
32
33
|
if (isBetaEnabled(options, 'nestedProjections'))
|
|
33
34
|
rewriteExpandInline();
|
|
@@ -239,13 +240,13 @@ function expandStructureReferences(csn, options, pathDelimiter, { error, info, t
|
|
|
239
240
|
*/
|
|
240
241
|
function nextBase(parent, base) {
|
|
241
242
|
if (parent.ref) {
|
|
242
|
-
const finalBaseType =
|
|
243
|
+
const finalBaseType = getFinalBaseTypeWithProps(parent._art.type);
|
|
243
244
|
const art = parent._art;
|
|
244
245
|
|
|
245
|
-
if (finalBaseType === 'cds.Association' || finalBaseType === 'cds.Composition')
|
|
246
|
+
if (finalBaseType && (finalBaseType.type === 'cds.Association' || finalBaseType.type === 'cds.Composition'))
|
|
246
247
|
return csn.definitions[art.target].elements;
|
|
247
248
|
|
|
248
|
-
return art.elements || finalBaseType
|
|
249
|
+
return art.elements || finalBaseType?.elements;
|
|
249
250
|
}
|
|
250
251
|
|
|
251
252
|
return base;
|
|
@@ -67,8 +67,8 @@ function resolveTypeReferences(csn, options, resolved, pathDelimiter, iterateOpt
|
|
|
67
67
|
stack.push(...Object.values(current.elements));
|
|
68
68
|
}
|
|
69
69
|
}
|
|
70
|
-
const { toFinalBaseType } = transformUtils.getTransformers(csn, options, pathDelimiter);
|
|
71
|
-
const { getServiceName,
|
|
70
|
+
const { toFinalBaseType, csnUtils } = transformUtils.getTransformers(csn, options, pathDelimiter);
|
|
71
|
+
const { getServiceName, getFinalBaseTypeWithProps } = csnUtils;
|
|
72
72
|
|
|
73
73
|
// We don't want to iterate over actions
|
|
74
74
|
if (iterateOptions.skipDict && !iterateOptions.skipDict.actions)
|
|
@@ -89,7 +89,7 @@ function resolveTypeReferences(csn, options, resolved, pathDelimiter, iterateOpt
|
|
|
89
89
|
if (options.toOdata && parent.kind && parent.kind in ignoreOdataKinds)
|
|
90
90
|
return;
|
|
91
91
|
if (!isBuiltinType(type) && (!options.toOdata || options.toOdata && !isODataV4BuiltinFromService(type, path) && !isODataItems(type))) {
|
|
92
|
-
toFinalBaseType(parent, resolved);
|
|
92
|
+
toFinalBaseType(parent, resolved, true);
|
|
93
93
|
// structured types might not have the child-types replaced.
|
|
94
94
|
// Drill down to ensure this.
|
|
95
95
|
if (parent.elements) {
|
|
@@ -98,7 +98,7 @@ function resolveTypeReferences(csn, options, resolved, pathDelimiter, iterateOpt
|
|
|
98
98
|
const elements = stack.pop();
|
|
99
99
|
for (const e of Object.values(elements)) {
|
|
100
100
|
if (e.type && !isBuiltinType(e.type))
|
|
101
|
-
toFinalBaseType(e, resolved);
|
|
101
|
+
toFinalBaseType(e, resolved, true);
|
|
102
102
|
|
|
103
103
|
if (e.elements)
|
|
104
104
|
stack.push(e.elements);
|
|
@@ -152,11 +152,11 @@ function resolveTypeReferences(csn, options, resolved, pathDelimiter, iterateOpt
|
|
|
152
152
|
* @returns {boolean}
|
|
153
153
|
*/
|
|
154
154
|
function isODataV4BuiltinFromService(typeName, path) {
|
|
155
|
-
if (!options.toOdata || (options.
|
|
155
|
+
if (!options.toOdata || (options.odataVersion === 'v2') || typeof typeName !== 'string')
|
|
156
156
|
return false;
|
|
157
157
|
|
|
158
158
|
const typeServiceName = getServiceName(typeName);
|
|
159
|
-
const finalBaseType =
|
|
159
|
+
const finalBaseType = getFinalBaseTypeWithProps(typeName)?.type;
|
|
160
160
|
// we need the service of the current definition
|
|
161
161
|
const currDefServiceName = getServiceName(path[1]);
|
|
162
162
|
|
|
@@ -266,9 +266,8 @@ function flattenAllStructStepsInRefs(csn, options, resolved, pathDelimiter, iter
|
|
|
266
266
|
* @param {object} iterateOptions
|
|
267
267
|
*/
|
|
268
268
|
function flattenElements(csn, options, pathDelimiter, error, iterateOptions = {}) {
|
|
269
|
-
const {
|
|
270
|
-
const {
|
|
271
|
-
const { effectiveType } = csnRefs(csn);
|
|
269
|
+
const { flattenStructuredElement, csnUtils } = transformUtils.getTransformers(csn, options, pathDelimiter);
|
|
270
|
+
const { isAssocOrComposition, effectiveType } = csnUtils;
|
|
272
271
|
const transformers = {
|
|
273
272
|
elements: flatten,
|
|
274
273
|
};
|
|
@@ -299,11 +298,13 @@ function flattenElements(csn, options, pathDelimiter, error, iterateOptions = {}
|
|
|
299
298
|
const flatElems = flattenStructuredElement(element, elementName, [], path.concat([ 'elements', elementName ]));
|
|
300
299
|
|
|
301
300
|
for (const flatElemName in flatElems) {
|
|
302
|
-
if (parent[prop][flatElemName])
|
|
301
|
+
if (parent[prop][flatElemName]) {
|
|
303
302
|
// TODO: combine message ID with generated FK duplicate
|
|
304
|
-
// do the duplicate check in the
|
|
303
|
+
// do the duplicate check in the construct callback, requires to mark generated flat elements,
|
|
305
304
|
// check: Error location should be the existing element like @odata.foreignKey4
|
|
306
|
-
error(
|
|
305
|
+
error('name-duplicate-element', path.concat([ 'elements', elementName ]),
|
|
306
|
+
{ '#': 'flatten-element-exist', name: flatElemName });
|
|
307
|
+
}
|
|
307
308
|
|
|
308
309
|
const flatElement = flatElems[flatElemName];
|
|
309
310
|
|
|
@@ -609,8 +610,7 @@ function handleManagedAssociationsAndCreateForeignKeys(csn, options, error, path
|
|
|
609
610
|
((options.toOdata && isDeepEqual(element, parent[prop][fk[0]], true)) ||
|
|
610
611
|
!options.toOdata)) {
|
|
611
612
|
// error location is the colliding element
|
|
612
|
-
error(
|
|
613
|
-
'Generated foreign key element $(NAME) for association $(ART) conflicts with existing element');
|
|
613
|
+
error('name-duplicate-element', eltPath, { '#': 'flatten-fkey-exists', name: fk[0], art: elementName });
|
|
614
614
|
}
|
|
615
615
|
// attach a proper $path
|
|
616
616
|
setProp(element, '$path', eltPath);
|
|
@@ -619,10 +619,8 @@ function handleManagedAssociationsAndCreateForeignKeys(csn, options, error, path
|
|
|
619
619
|
|
|
620
620
|
// check for duplicate foreign keys
|
|
621
621
|
Object.entries(refCount).forEach(([ name, occ ]) => {
|
|
622
|
-
if (occ > 1)
|
|
623
|
-
error(
|
|
624
|
-
'Duplicate definition of foreign key element $(NAME)');
|
|
625
|
-
}
|
|
622
|
+
if (occ > 1)
|
|
623
|
+
error('name-duplicate-element', eltPath, { '#': 'flatten-fkey-gen', name, art: elementName });
|
|
626
624
|
});
|
|
627
625
|
if (element.keys) {
|
|
628
626
|
element.keys.forEach((key, i) => {
|
|
@@ -349,7 +349,7 @@ function handleExists(csn, options, error) {
|
|
|
349
349
|
const newExpr = [];
|
|
350
350
|
const query = walkCsnPath(csn, queryPath);
|
|
351
351
|
const expr = walkCsnPath(csn, exprPath);
|
|
352
|
-
const queryBase = query.SELECT.from.ref ? (query.SELECT.from.as || query.SELECT.from.ref
|
|
352
|
+
const queryBase = query.SELECT.from.ref ? (query.SELECT.from.as || query.SELECT.from.ref) : null;
|
|
353
353
|
const sources = getQuerySources(query.SELECT);
|
|
354
354
|
|
|
355
355
|
for (let i = 0; i < expr.length; i++) {
|
|
@@ -457,7 +457,7 @@ function handleExists(csn, options, error) {
|
|
|
457
457
|
*
|
|
458
458
|
* A valid $self-backlink is handled in translateDollarSelfToWhere.
|
|
459
459
|
*
|
|
460
|
-
* For an ordinary unmanaged association, we do the
|
|
460
|
+
* For an ordinary unmanaged association, we do the following for each part of the on-condition:
|
|
461
461
|
* - target side: We prefix the real target and cut off the assoc-name from the ref
|
|
462
462
|
* - source side w/ leading $self: We remove the $self and add the source side entity/query source
|
|
463
463
|
* - source side w/o leading $self: We simply add the source side entity/query source in front of the ref
|
|
@@ -730,15 +730,17 @@ function handleExists(csn, options, error) {
|
|
|
730
730
|
/**
|
|
731
731
|
* Get the name of the source-side query source
|
|
732
732
|
*
|
|
733
|
-
* @param {string|null} queryBase
|
|
733
|
+
* @param {string | Array | null} queryBase
|
|
734
734
|
* @param {boolean} isPrefixedWithTableAlias
|
|
735
735
|
* @param {CSN.Column} current
|
|
736
736
|
* @param {CSN.Path} path
|
|
737
737
|
* @returns {string}
|
|
738
738
|
*/
|
|
739
739
|
function getBase(queryBase, isPrefixedWithTableAlias, current, path) {
|
|
740
|
-
if (queryBase)
|
|
741
|
-
return
|
|
740
|
+
if (typeof queryBase === 'string') // alias
|
|
741
|
+
return queryBase;
|
|
742
|
+
else if (queryBase) // ref
|
|
743
|
+
return queryBase.length > 1 ? queryBase[queryBase.length - 1] : getRealName(csn, queryBase[0]);
|
|
742
744
|
else if (isPrefixedWithTableAlias)
|
|
743
745
|
return current.ref[0];
|
|
744
746
|
return getParent(current, path);
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
const {
|
|
4
4
|
getUtils, cloneCsnNonDict, applyTransformationsOnNonDictionary,
|
|
5
5
|
} = require('../../model/csnUtils');
|
|
6
|
-
const { implicitAs
|
|
6
|
+
const { implicitAs } = require('../../model/csnRefs');
|
|
7
7
|
const { isBetaEnabled } = require('../../base/model');
|
|
8
8
|
const { ModelError } = require('../../base/error');
|
|
9
9
|
|
|
@@ -69,9 +69,9 @@ function usesMixinAssociation(query, association, associationName) {
|
|
|
69
69
|
function getViewTransformer(csn, options, messageFunctions, transformCommon) {
|
|
70
70
|
const {
|
|
71
71
|
get$combined, isAssocOrComposition,
|
|
72
|
+
inspectRef, queryOrMain, // csnRefs
|
|
72
73
|
} = getUtils(csn);
|
|
73
|
-
const
|
|
74
|
-
const pathDelimiter = (options.forHana.names === 'hdbcds') ? '.' : '_';
|
|
74
|
+
const pathDelimiter = options.forHana && (options.sqlMapping === 'hdbcds') ? '.' : '_';
|
|
75
75
|
const { error, info } = messageFunctions;
|
|
76
76
|
const doA2J = !(options.transformation === 'hdbcds' && options.sqlMapping === 'hdbcds');
|
|
77
77
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const {
|
|
4
|
-
hasAnnotationValue,
|
|
4
|
+
hasAnnotationValue, getServiceNames, forEachDefinition,
|
|
5
5
|
getResultingName, forEachMemberRecursively,
|
|
6
6
|
} = require('../../model/csnUtils');
|
|
7
7
|
const { setProp, isDeprecatedEnabled } = require('../../base/model');
|
|
@@ -19,15 +19,15 @@ const booleanBuiltin = 'cds.Boolean';
|
|
|
19
19
|
* @param {object} messageFunctions
|
|
20
20
|
*/
|
|
21
21
|
function generateDrafts(csn, options, pathDelimiter, messageFunctions) {
|
|
22
|
-
const draftSuffix =
|
|
22
|
+
const draftSuffix = '.drafts';
|
|
23
23
|
// All services of the model - needed for drafts
|
|
24
24
|
const allServices = getServiceNames(csn);
|
|
25
25
|
const draftRoots = new WeakMap();
|
|
26
26
|
const {
|
|
27
27
|
createForeignKeyElement, createAndAddDraftAdminDataProjection, createScalarElement, createAssociationElement,
|
|
28
|
-
addElement, copyAndAddElement, createAssociationPathComparison,
|
|
28
|
+
addElement, copyAndAddElement, createAssociationPathComparison, csnUtils,
|
|
29
29
|
} = getTransformers(csn, options, pathDelimiter);
|
|
30
|
-
const { getCsnDef, isComposition } =
|
|
30
|
+
const { getCsnDef, isComposition } = csnUtils;
|
|
31
31
|
const { error, warning } = messageFunctions;
|
|
32
32
|
|
|
33
33
|
forEachDefinition(csn, generateDraft);
|
|
@@ -155,7 +155,7 @@ function generateDrafts(csn, options, pathDelimiter, messageFunctions) {
|
|
|
155
155
|
'Generated entity $(NAME) conflicts with existing artifact');
|
|
156
156
|
}
|
|
157
157
|
|
|
158
|
-
const persistenceName = getResultingName(csn, options.
|
|
158
|
+
const persistenceName = getResultingName(csn, options.sqlMapping, draftsArtifactName);
|
|
159
159
|
// Duplicate the artifact as a draft shadow entity
|
|
160
160
|
if (csn.definitions[persistenceName]) {
|
|
161
161
|
const definingDraftRoot = draftRoots.get(csn.definitions[persistenceName]);
|
|
@@ -187,7 +187,7 @@ function generateDrafts(csn, options, pathDelimiter, messageFunctions) {
|
|
|
187
187
|
for (const elemName in artifact.elements) {
|
|
188
188
|
const origElem = artifact.elements[elemName];
|
|
189
189
|
let elem;
|
|
190
|
-
if ((isDeprecatedEnabled(options, '
|
|
190
|
+
if ((isDeprecatedEnabled(options, '_renderVirtualElements') && origElem.virtual) || !origElem.virtual)
|
|
191
191
|
elem = copyAndAddElement(origElem, draftsArtifact, draftsArtifactName, elemName)[elemName];
|
|
192
192
|
if (elem) {
|
|
193
193
|
// Remove "virtual" - cap/issues 4956
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const { forEachDefinition,
|
|
3
|
+
const { forEachDefinition, getServiceNames } = require('../../model/csnUtils');
|
|
4
4
|
const { forEach } = require('../../utils/objectUtils');
|
|
5
5
|
const { isArtifactInSomeService, getServiceOfArtifact } = require('../odata/utils');
|
|
6
6
|
const { getTransformers } = require('../transformUtilsNew');
|
|
@@ -30,15 +30,14 @@ function generateDrafts(csn, options, services) {
|
|
|
30
30
|
createAssociationElement, createAssociationPathComparison,
|
|
31
31
|
addElement, createAction, assignAction,
|
|
32
32
|
resetAnnotation,
|
|
33
|
+
csnUtils,
|
|
33
34
|
} = getTransformers(csn, options);
|
|
34
|
-
|
|
35
35
|
const {
|
|
36
36
|
getFinalType,
|
|
37
37
|
getServiceName,
|
|
38
38
|
hasAnnotationValue,
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
} = getUtils(csn);
|
|
39
|
+
getFinalBaseTypeWithProps,
|
|
40
|
+
} = csnUtils;
|
|
42
41
|
|
|
43
42
|
const { error, info } = makeMessageFunction(csn, options, 'for.odata');
|
|
44
43
|
|
|
@@ -197,8 +196,8 @@ function generateDrafts(csn, options, services) {
|
|
|
197
196
|
stack.push(elem);
|
|
198
197
|
}
|
|
199
198
|
else if (elem.type) { // types - possibly structured
|
|
200
|
-
const typeDef =
|
|
201
|
-
if (typeDef
|
|
199
|
+
const typeDef = getFinalBaseTypeWithProps(elem.type);
|
|
200
|
+
if (typeDef?.elements)
|
|
202
201
|
stack.push(typeDef);
|
|
203
202
|
}
|
|
204
203
|
});
|