@sap/cds-compiler 3.1.2 → 3.4.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 +101 -3
- package/bin/cdsc.js +4 -2
- package/doc/CHANGELOG_BETA.md +35 -0
- package/lib/api/main.js +153 -29
- package/lib/api/validate.js +8 -3
- package/lib/base/dictionaries.js +6 -6
- package/lib/base/error.js +2 -2
- package/lib/base/keywords.js +106 -24
- package/lib/base/message-registry.js +177 -79
- package/lib/base/messages.js +78 -57
- package/lib/base/model.js +2 -1
- package/lib/checks/actionsFunctions.js +1 -1
- package/lib/checks/annotationsOData.js +2 -2
- package/lib/checks/arrayOfs.js +15 -7
- package/lib/checks/cdsPersistence.js +1 -1
- package/lib/checks/checkForTypes.js +53 -0
- package/lib/checks/defaultValues.js +4 -2
- package/lib/checks/elements.js +81 -6
- package/lib/checks/foreignKeys.js +12 -13
- package/lib/checks/invalidTarget.js +10 -11
- package/lib/checks/managedInType.js +21 -15
- package/lib/checks/nullableKeys.js +1 -1
- package/lib/checks/onConditions.js +9 -9
- package/lib/checks/parameters.js +23 -0
- package/lib/checks/queryNoDbArtifacts.js +1 -1
- package/lib/checks/selectItems.js +1 -1
- package/lib/checks/sql-snippets.js +12 -10
- package/lib/checks/types.js +2 -2
- package/lib/checks/utils.js +17 -7
- package/lib/checks/validator.js +36 -14
- package/lib/compiler/assert-consistency.js +21 -13
- package/lib/compiler/builtins.js +8 -0
- package/lib/compiler/checks.js +57 -40
- package/lib/compiler/define.js +139 -69
- package/lib/compiler/extend.js +319 -50
- package/lib/compiler/finalize-parse-cdl.js +14 -9
- package/lib/compiler/kick-start.js +2 -35
- package/lib/compiler/populate.js +111 -68
- package/lib/compiler/propagator.js +5 -3
- package/lib/compiler/resolve.js +71 -108
- package/lib/compiler/shared.js +82 -54
- package/lib/compiler/tweak-assocs.js +26 -14
- package/lib/compiler/utils.js +13 -2
- package/lib/edm/annotations/genericTranslation.js +10 -7
- package/lib/edm/csn2edm.js +11 -11
- package/lib/edm/edm.js +17 -9
- package/lib/edm/edmPreprocessor.js +53 -30
- package/lib/edm/edmUtils.js +7 -2
- package/lib/gen/Dictionary.json +14 -0
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +3 -2
- package/lib/gen/languageParser.js +4312 -4186
- package/lib/inspect/inspectModelStatistics.js +1 -1
- package/lib/inspect/inspectPropagation.js +23 -9
- package/lib/json/csnVersion.js +13 -13
- package/lib/json/from-csn.js +161 -172
- package/lib/json/to-csn.js +70 -10
- package/lib/language/.eslintrc.json +4 -0
- package/lib/language/antlrParser.js +8 -11
- package/lib/language/docCommentParser.js +1 -2
- package/lib/language/errorStrategy.js +54 -27
- package/lib/language/genericAntlrParser.js +140 -93
- package/lib/language/language.g4 +57 -33
- package/lib/language/multiLineStringParser.js +75 -63
- package/lib/main.d.ts +3 -6
- package/lib/main.js +1 -0
- package/lib/model/.eslintrc.json +13 -0
- package/lib/model/api.js +4 -2
- package/lib/model/csnRefs.js +78 -50
- package/lib/model/csnUtils.js +272 -222
- package/lib/model/enrichCsn.js +41 -31
- package/lib/model/revealInternalProperties.js +61 -57
- package/lib/model/sortViews.js +35 -31
- package/lib/modelCompare/compare.js +52 -18
- package/lib/modelCompare/filter.js +83 -0
- package/lib/optionProcessor.js +10 -1
- package/lib/render/manageConstraints.js +11 -7
- package/lib/render/toCdl.js +151 -106
- package/lib/render/toHdbcds.js +8 -6
- package/lib/render/toRename.js +4 -4
- package/lib/render/toSql.js +17 -7
- package/lib/render/utils/common.js +27 -9
- package/lib/render/utils/sql.js +5 -5
- package/lib/sql-identifier.js +7 -0
- package/lib/transform/db/applyTransformations.js +32 -3
- package/lib/transform/db/assertUnique.js +27 -38
- package/lib/transform/db/expansion.js +92 -41
- package/lib/transform/db/flattening.js +1 -1
- package/lib/transform/db/temporal.js +3 -1
- package/lib/transform/db/transformExists.js +8 -2
- package/lib/transform/db/views.js +42 -13
- package/lib/transform/draft/db.js +2 -2
- package/lib/transform/forOdataNew.js +10 -7
- package/lib/transform/{forHanaNew.js → forRelationalDB.js} +18 -12
- package/lib/transform/localized.js +29 -20
- package/lib/transform/odata/toFinalBaseType.js +8 -11
- package/lib/transform/odata/typesExposure.js +2 -1
- package/lib/transform/parseExpr.js +245 -0
- package/lib/transform/transformUtilsNew.js +122 -51
- package/lib/transform/translateAssocsToJoins.js +17 -16
- package/lib/utils/moduleResolve.js +5 -5
- package/lib/utils/objectUtils.js +3 -3
- package/lib/utils/term.js +5 -5
- package/package.json +2 -2
- package/share/messages/anno-duplicate-unrelated-layer.md +6 -6
- package/share/messages/check-proper-type-of.md +4 -4
- package/share/messages/check-proper-type.md +2 -2
- package/share/messages/duplicate-autoexposed.md +4 -4
- package/share/messages/extend-repeated-intralayer.md +4 -5
- package/share/messages/extend-unrelated-layer.md +4 -4
- package/share/messages/message-explanations.json +3 -1
- package/share/messages/redirected-to-ambiguous.md +7 -6
- package/share/messages/redirected-to-complex.md +63 -0
- package/share/messages/redirected-to-unrelated.md +6 -5
- package/share/messages/rewrite-not-supported.md +4 -4
- package/share/messages/{syntax-expected-integer.md → syntax-expecting-integer.md} +4 -4
- package/share/messages/wildcard-excluding-one.md +37 -0
|
@@ -242,6 +242,10 @@ const cdsToSqlTypes = {
|
|
|
242
242
|
'cds.DecimalFloat': 'DECIMAL',
|
|
243
243
|
'cds.Integer64': 'BIGINT',
|
|
244
244
|
'cds.Integer': 'INTEGER',
|
|
245
|
+
'cds.Int64': 'BIGINT',
|
|
246
|
+
'cds.Int32': 'INTEGER',
|
|
247
|
+
'cds.Int16': 'SMALLINT',
|
|
248
|
+
'cds.UInt8': 'TINYINT',
|
|
245
249
|
'cds.hana.SMALLINT': 'SMALLINT',
|
|
246
250
|
'cds.hana.TINYINT': 'TINYINT', // not a Standard SQL type
|
|
247
251
|
'cds.Double': 'DOUBLE',
|
|
@@ -279,20 +283,33 @@ const cdsToSqlTypes = {
|
|
|
279
283
|
'cds.hana.BINARY': 'BINARY',
|
|
280
284
|
'cds.hana.SMALLDECIMAL': 'DECIMAL',
|
|
281
285
|
},
|
|
286
|
+
h2: {
|
|
287
|
+
'cds.Binary': 'VARBINARY', // same as for plain
|
|
288
|
+
'cds.LargeBinary': 'BINARY LARGE OBJECT', // BLOB would require a length!
|
|
289
|
+
'cds.DecimalFloat': 'DECFLOAT', // Decimal and Decimal(p) is mapped to cds.DecimalFloat
|
|
290
|
+
'cds.DateTime': 'TIMESTAMP(0)',
|
|
291
|
+
'cds.Timestamp': 'TIMESTAMP(7)',
|
|
292
|
+
},
|
|
282
293
|
postgres: {
|
|
283
|
-
//
|
|
284
|
-
// We can't use text types for binary on PostgreSQL due to NUL!
|
|
294
|
+
// See <https://www.postgresql.org/docs/current/datatype.html>
|
|
285
295
|
'cds.String': 'VARCHAR',
|
|
286
|
-
'cds.LargeString': '
|
|
287
|
-
'cds.
|
|
288
|
-
'cds.
|
|
289
|
-
'cds.
|
|
290
|
-
'cds.
|
|
291
|
-
'cds.Double': 'double precision',
|
|
292
|
-
'cds.hana.TINYINT': 'INTEGER',
|
|
296
|
+
'cds.LargeString': 'TEXT',
|
|
297
|
+
'cds.LargeBinary': 'BYTEA',
|
|
298
|
+
'cds.Binary': 'BYTEA',
|
|
299
|
+
'cds.Double': 'FLOAT8',
|
|
300
|
+
'cds.UInt8': 'INTEGER', // Not equivalent
|
|
293
301
|
},
|
|
294
302
|
};
|
|
295
303
|
|
|
304
|
+
// Type mapping from cds type names to HDBCDS type names:
|
|
305
|
+
// Only those types, that need mapping, are listed.
|
|
306
|
+
const cdsToHdbcdsTypes = {
|
|
307
|
+
'cds.UInt8': 'cds.hana.TINYINT',
|
|
308
|
+
'cds.Int16': 'cds.hana.SMALLINT',
|
|
309
|
+
'cds.Int32': 'cds.Integer',
|
|
310
|
+
'cds.Int64': 'cds.Integer64',
|
|
311
|
+
};
|
|
312
|
+
|
|
296
313
|
/**
|
|
297
314
|
* Get the element matching the column
|
|
298
315
|
*
|
|
@@ -521,6 +538,7 @@ module.exports = {
|
|
|
521
538
|
addIntermediateContexts,
|
|
522
539
|
addContextMarkers,
|
|
523
540
|
cdsToSqlTypes,
|
|
541
|
+
cdsToHdbcdsTypes,
|
|
524
542
|
hasHanaComment,
|
|
525
543
|
getHanaComment,
|
|
526
544
|
findElement,
|
package/lib/render/utils/sql.js
CHANGED
|
@@ -43,8 +43,7 @@ function renderReferentialConstraint(constraint, indent, toUpperCase, csn, optio
|
|
|
43
43
|
options.src === 'hdi' ||
|
|
44
44
|
(options.manageConstraints && options.manageConstraints.src === 'hdi');
|
|
45
45
|
|
|
46
|
-
const { sqlMapping } = options;
|
|
47
|
-
const forSqlite = options.sqlDialect === 'sqlite';
|
|
46
|
+
const { sqlMapping, sqlDialect } = options;
|
|
48
47
|
let result = '';
|
|
49
48
|
result += `${indent}CONSTRAINT ${quoteId(constraint.identifier)}\n`;
|
|
50
49
|
if (renderAsHdbconstraint)
|
|
@@ -55,7 +54,7 @@ function renderReferentialConstraint(constraint, indent, toUpperCase, csn, optio
|
|
|
55
54
|
const onDeleteRemark = constraint.onDeleteRemark ? ` -- ${constraint.onDeleteRemark}` : '';
|
|
56
55
|
|
|
57
56
|
// omit 'RESTRICT' action for ON UPDATE / ON DELETE, because it interferes with deferred constraint check
|
|
58
|
-
if (
|
|
57
|
+
if (sqlDialect === 'sqlite') {
|
|
59
58
|
if (constraint.onDelete === 'CASCADE' )
|
|
60
59
|
result += `${indent}ON DELETE ${constraint.onDelete}${onDeleteRemark}\n`;
|
|
61
60
|
}
|
|
@@ -65,13 +64,14 @@ function renderReferentialConstraint(constraint, indent, toUpperCase, csn, optio
|
|
|
65
64
|
}
|
|
66
65
|
}
|
|
67
66
|
// constraint enforcement / validation must be switched off using sqlite pragma statement
|
|
67
|
+
// constraint enforcement / validation not supported by postgres
|
|
68
68
|
// Does not include HDBCDS.
|
|
69
|
-
if (options.toSql &&
|
|
69
|
+
if (options.toSql && sqlDialect !== 'sqlite' && sqlDialect !== 'postgres') {
|
|
70
70
|
result += `${indent}${!constraint.validated ? 'NOT ' : ''}VALIDATED\n`;
|
|
71
71
|
result += `${indent}${!constraint.enforced ? 'NOT ' : ''}ENFORCED\n`;
|
|
72
72
|
}
|
|
73
73
|
// for sqlite, the DEFERRABLE keyword is required
|
|
74
|
-
result += `${indent}${
|
|
74
|
+
result += `${indent}${sqlDialect === 'sqlite' ? 'DEFERRABLE ' : ''}INITIALLY DEFERRED`;
|
|
75
75
|
return result;
|
|
76
76
|
}
|
|
77
77
|
|
package/lib/sql-identifier.js
CHANGED
|
@@ -39,6 +39,13 @@ const keywords = require( './base/keywords' );
|
|
|
39
39
|
|
|
40
40
|
const sqlDialects = {
|
|
41
41
|
plain: {},
|
|
42
|
+
h2: {
|
|
43
|
+
// See http://www.h2database.com/html/grammar.html#name
|
|
44
|
+
regularRegex: /^[A-Za-z_][A-Za-z_0-9]*$/,
|
|
45
|
+
reservedWords: keywords.h2,
|
|
46
|
+
effectiveName: name => name.toUpperCase(),
|
|
47
|
+
asDelimitedId: name => `"${ name.replace(/"/g, '""')}"`,
|
|
48
|
+
},
|
|
42
49
|
sqlite: {
|
|
43
50
|
regularRegex: /^[A-Za-z_][A-Za-z_$0-9]*$/,
|
|
44
51
|
reservedWords: keywords.sqlite,
|
|
@@ -35,10 +35,16 @@ function applyTransformationsInternal(parent, prop, customTransformers, artifact
|
|
|
35
35
|
};
|
|
36
36
|
|
|
37
37
|
const csnPath = [ ...path ];
|
|
38
|
-
if (prop === 'definitions')
|
|
38
|
+
if (prop === 'definitions') {
|
|
39
39
|
definitions( parent, 'definitions', parent.definitions );
|
|
40
|
-
|
|
40
|
+
}
|
|
41
|
+
else if (options.directDict) {
|
|
42
|
+
for (const name of Object.getOwnPropertyNames( parent ))
|
|
43
|
+
standard( parent, name, parent[name] );
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
41
46
|
standard(parent, prop, parent[prop]);
|
|
47
|
+
}
|
|
42
48
|
return parent;
|
|
43
49
|
|
|
44
50
|
/**
|
|
@@ -84,7 +90,7 @@ function applyTransformationsInternal(parent, prop, customTransformers, artifact
|
|
|
84
90
|
* @param {object} dict The value of node[_prop]
|
|
85
91
|
*/
|
|
86
92
|
function dictionary( node, _prop, dict ) {
|
|
87
|
-
// Allow skipping dicts like actions in
|
|
93
|
+
// Allow skipping dicts like actions in forRelationalDB
|
|
88
94
|
if (options.skipDict && options.skipDict[_prop] || dict === null || dict === undefined) // with universal CSN, dicts might be null
|
|
89
95
|
return;
|
|
90
96
|
csnPath.push( _prop );
|
|
@@ -194,9 +200,31 @@ function applyTransformationsOnNonDictionary(parent, prop, customTransformers =
|
|
|
194
200
|
return applyTransformationsInternal(parent, prop, customTransformers, [], options, path)[prop];
|
|
195
201
|
}
|
|
196
202
|
|
|
203
|
+
/**
|
|
204
|
+
* Instead of looping through the whole model, start at a given thing (like .elements),
|
|
205
|
+
* as long as it is a dictionary.
|
|
206
|
+
*
|
|
207
|
+
* Each transformer gets:
|
|
208
|
+
* - the parent having the property
|
|
209
|
+
* - the name of the property
|
|
210
|
+
* - the value of the property
|
|
211
|
+
* - the path to the property
|
|
212
|
+
*
|
|
213
|
+
*
|
|
214
|
+
* @param {object} dictionary Dictionary to enrich in-place
|
|
215
|
+
* @param {object} customTransformers Map of prop to transform and function to apply
|
|
216
|
+
* @param {applyTransformationsOptions} [options={}]
|
|
217
|
+
* @param {CSN.Path} path Path pointing to parent
|
|
218
|
+
* @returns {object} dictionary with transformations applied
|
|
219
|
+
*/
|
|
220
|
+
function applyTransformationsOnDictionary(dictionary, customTransformers = {}, options = {}, path = []) {
|
|
221
|
+
return applyTransformationsInternal(dictionary, null, customTransformers, [], { directDict: true, ...options }, path);
|
|
222
|
+
}
|
|
223
|
+
|
|
197
224
|
module.exports = {
|
|
198
225
|
applyTransformations,
|
|
199
226
|
applyTransformationsOnNonDictionary,
|
|
227
|
+
applyTransformationsOnDictionary,
|
|
200
228
|
};
|
|
201
229
|
|
|
202
230
|
|
|
@@ -209,4 +237,5 @@ module.exports = {
|
|
|
209
237
|
* @property {object} [skipStandard] stop drill-down on certain "standard" props
|
|
210
238
|
* @property {object} [skipDict] stop drill-down on certain "dictionary" props
|
|
211
239
|
* @property {boolean} [skipIgnore=true] Whether to skip _ignore elements or not
|
|
240
|
+
* @property {boolean} [directDict=false] Implicitly set via applyTransformationsOnDictionary
|
|
212
241
|
*/
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
const { forEachDefinition, hasAnnotationValue } = require('../../model/csnUtils');
|
|
4
4
|
const { getTransformers } = require('../transformUtilsNew');
|
|
5
5
|
const { setProp } = require('../../base/model');
|
|
6
|
+
const { pathName } = require('../../compiler/utils');
|
|
6
7
|
|
|
7
8
|
|
|
8
9
|
/**
|
|
@@ -38,13 +39,14 @@ function processAssertUnique(csn, options, error, info) {
|
|
|
38
39
|
// filter unique constraints from annotations
|
|
39
40
|
for (const propName in artifact) {
|
|
40
41
|
if (propName.startsWith('@assert.unique') && artifact[propName] !== null) {
|
|
42
|
+
const anno = propName;
|
|
41
43
|
// Constraint Name check
|
|
42
44
|
const constraintName = propName.split('.').splice(2);
|
|
43
45
|
if (constraintName.length === 0)
|
|
44
|
-
|
|
46
|
+
error(null, [ 'definitions', artifactName ], { anno }, '$(ANNO): Table constraint can\'t be anonymous');
|
|
45
47
|
if (constraintName.length > 1)
|
|
46
48
|
// Neither HANA CDS nor HANA SQL allow dots in index names
|
|
47
|
-
|
|
49
|
+
error(null, [ 'definitions', artifactName ], { anno }, '$(ANNO): Illegal character \'.\' in constraint name');
|
|
48
50
|
|
|
49
51
|
const propValue = artifact[propName];
|
|
50
52
|
// Constraint value check, returns array of path values
|
|
@@ -79,10 +81,12 @@ function processAssertUnique(csn, options, error, info) {
|
|
|
79
81
|
pathxrefs[pstr]++;
|
|
80
82
|
});
|
|
81
83
|
Object.keys(pathxrefs).forEach((k) => {
|
|
82
|
-
if (pathxrefs[k] > 1)
|
|
83
|
-
|
|
84
|
+
if (pathxrefs[k] > 1) {
|
|
85
|
+
error(null, [ 'definitions', artifactName ], { anno, id: k },
|
|
86
|
+
'$(ANNO): Final path $(ID) can only be specified once');
|
|
87
|
+
}
|
|
84
88
|
});
|
|
85
|
-
// 9) Add into constraint cross
|
|
89
|
+
// 9) Add into constraint cross-reference
|
|
86
90
|
if (constraintKey.length) {
|
|
87
91
|
if (constraintXrefs[constraintKey])
|
|
88
92
|
constraintXrefs[constraintKey].push(propName);
|
|
@@ -99,7 +103,7 @@ function processAssertUnique(csn, options, error, info) {
|
|
|
99
103
|
for (const key in constraintXrefs) {
|
|
100
104
|
const val = constraintXrefs[key];
|
|
101
105
|
if (val.length > 1)
|
|
102
|
-
|
|
106
|
+
error(null, [ 'definitions', artifactName ], { annos: val }, '$(ANNOS): Constraint can only be specified once');
|
|
103
107
|
}
|
|
104
108
|
// preserve dictionary in '$tableConstraints' on the artifact for path rewriting and rendering
|
|
105
109
|
if (Object.keys(constraintDict).length) {
|
|
@@ -119,17 +123,18 @@ function processAssertUnique(csn, options, error, info) {
|
|
|
119
123
|
*/
|
|
120
124
|
function checkVal(val, propName) {
|
|
121
125
|
const paths = [];
|
|
126
|
+
const loc = [ 'definitions', artifactName ];
|
|
122
127
|
if (!Array.isArray(val)) {
|
|
123
|
-
|
|
128
|
+
error(null, loc, { anno: propName, value: JSON.stringify(unref(val)) }, '$(ANNO): Value $(VALUE) is not an array');
|
|
124
129
|
}
|
|
125
130
|
else {
|
|
126
131
|
if (val.length === 0)
|
|
127
|
-
|
|
132
|
+
info(null, loc, { anno: propName }, '$(ANNO): Empty annotation is ignored');
|
|
128
133
|
|
|
129
134
|
val.forEach((v) => {
|
|
130
135
|
const p = v['='];
|
|
131
136
|
if (!p)
|
|
132
|
-
|
|
137
|
+
error(null, loc, { anno: propName, value: JSON.stringify(unref(v)) }, '$(ANNO): Value $(VALUE) is not a path');
|
|
133
138
|
else
|
|
134
139
|
paths.push(p);
|
|
135
140
|
});
|
|
@@ -185,61 +190,45 @@ function processAssertUnique(csn, options, error, info) {
|
|
|
185
190
|
return;
|
|
186
191
|
path.isChecked = true;
|
|
187
192
|
let foundErr = false;
|
|
193
|
+
const name = pathName(path.ref);
|
|
194
|
+
const loc = [ 'definitions', artifactName ];
|
|
188
195
|
for (let i = 0; i < path.ref.length && !foundErr; i++) {
|
|
189
196
|
const art = path.ref[i]._art;
|
|
197
|
+
const elemref = path.ref[i].id;
|
|
190
198
|
if (art) {
|
|
191
199
|
if (art.items) {
|
|
192
|
-
|
|
200
|
+
error(null, loc, { elemref, name, anno: constraintName },
|
|
201
|
+
'$(ANNO): \'Array of/many\' element $(ELEMREF) is not allowed in $(NAME)');
|
|
193
202
|
delete path._art;
|
|
194
203
|
foundErr = true;
|
|
195
204
|
}
|
|
196
205
|
if (art.target) {
|
|
197
206
|
if (art.on) {
|
|
198
|
-
|
|
207
|
+
error(null, loc, { elemref, name, anno: constraintName },
|
|
208
|
+
'$(ANNO): Unmanaged association $(ELEMREF) is not allowed in $(NAME)');
|
|
199
209
|
delete path._art;
|
|
200
210
|
foundErr = true;
|
|
201
211
|
}
|
|
202
212
|
if (art.keys && i < path.ref.length - 1) {
|
|
203
|
-
|
|
213
|
+
error(null, loc, { elemref, name, anno: constraintName },
|
|
214
|
+
'$(ANNO): Element access via managed association $(ELEMREF) is not allowed in $(NAME)');
|
|
204
215
|
delete path._art;
|
|
205
216
|
foundErr = true;
|
|
206
217
|
}
|
|
207
218
|
}
|
|
208
219
|
}
|
|
209
220
|
else {
|
|
210
|
-
|
|
221
|
+
error(null, loc, { elemref, anno: constraintName }, '$(ANNO): $(ELEMREF) has not been found');
|
|
211
222
|
foundErr = true;
|
|
212
223
|
}
|
|
213
224
|
}
|
|
214
225
|
|
|
215
226
|
if (!foundErr && path._art && [ 'cds.LargeBinary', 'cds.LargeString',
|
|
216
|
-
'cds.hana.CLOB', 'cds.hana.ST_POINT', 'cds.hana.ST_GEOMETRY' ].includes(path._art.type))
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
/**
|
|
221
|
-
* @param {string} message
|
|
222
|
-
*/
|
|
223
|
-
function msg(message) {
|
|
224
|
-
err(constraintName, `${message} in "${path.ref.map(p => p.id).join('.')}"`);
|
|
227
|
+
'cds.hana.CLOB', 'cds.hana.ST_POINT', 'cds.hana.ST_GEOMETRY' ].includes(path._art.type)) {
|
|
228
|
+
error(null, loc, { type: path._art.type, name, anno: constraintName },
|
|
229
|
+
'$(ANNO): Type $(TYPE) not allowed in $(NAME)');
|
|
225
230
|
}
|
|
226
231
|
}
|
|
227
|
-
|
|
228
|
-
// message macros for unified messaging
|
|
229
|
-
/**
|
|
230
|
-
* @param {string} propName
|
|
231
|
-
* @param {string} message
|
|
232
|
-
*/
|
|
233
|
-
function err(propName, message) {
|
|
234
|
-
error(null, [ 'definitions', artifactName ], `${propName}: ${message}`);
|
|
235
|
-
}
|
|
236
|
-
/**
|
|
237
|
-
* @param {string} propName
|
|
238
|
-
* @param {string} message
|
|
239
|
-
*/
|
|
240
|
-
function inf(propName, message) {
|
|
241
|
-
info(null, [ 'definitions', artifactName ], `${propName}: ${message}`);
|
|
242
|
-
}
|
|
243
232
|
}
|
|
244
233
|
}
|
|
245
234
|
|
|
@@ -7,7 +7,7 @@ const {
|
|
|
7
7
|
walkCsnPath,
|
|
8
8
|
} = require('../../model/csnUtils');
|
|
9
9
|
const { csnRefs, implicitAs } = require('../../model/csnRefs');
|
|
10
|
-
const { setProp
|
|
10
|
+
const { setProp } = require('../../base/model');
|
|
11
11
|
const { forEach } = require('../../utils/objectUtils');
|
|
12
12
|
|
|
13
13
|
/**
|
|
@@ -26,12 +26,11 @@ const { forEach } = require('../../utils/objectUtils');
|
|
|
26
26
|
function expandStructureReferences(csn, options, pathDelimiter, { error, info, throwWithAnyError }, iterateOptions = {}) {
|
|
27
27
|
const csnUtils = getUtils(csn);
|
|
28
28
|
const {
|
|
29
|
-
isStructured, get$combined, getFinalBaseTypeWithProps,
|
|
29
|
+
isStructured, get$combined, getFinalBaseTypeWithProps,
|
|
30
30
|
} = csnUtils;
|
|
31
31
|
let { effectiveType, inspectRef } = csnUtils;
|
|
32
32
|
|
|
33
|
-
|
|
34
|
-
rewriteExpandInline();
|
|
33
|
+
rewriteExpandInline();
|
|
35
34
|
|
|
36
35
|
|
|
37
36
|
applyTransformations(csn, {
|
|
@@ -63,7 +62,8 @@ function expandStructureReferences(csn, options, pathDelimiter, { error, info, t
|
|
|
63
62
|
* For such skipped things, error for usage of assoc pointing to them and and ignore publishing of assoc pointing to them.
|
|
64
63
|
*/
|
|
65
64
|
function rewriteExpandInline() {
|
|
66
|
-
|
|
65
|
+
let cleanup;
|
|
66
|
+
let _dependents;
|
|
67
67
|
|
|
68
68
|
const entity = findAnEntity();
|
|
69
69
|
const toDummyfy = [];
|
|
@@ -77,14 +77,12 @@ function expandStructureReferences(csn, options, pathDelimiter, { error, info, t
|
|
|
77
77
|
const root = get$combined({ SELECT: parent });
|
|
78
78
|
if (!hasAnnotationValue(artifact, '@cds.persistence.table')) {
|
|
79
79
|
const rewritten = rewrite(root, parent.columns, parent.excluding);
|
|
80
|
-
parent.columns = rewritten.columns;
|
|
81
80
|
/*
|
|
82
81
|
* Do not remove unexpandable many columns in OData
|
|
83
82
|
*/
|
|
84
83
|
if (rewritten.toMany.length > 0 && !options.toOdata) {
|
|
85
84
|
markAsToDummyfy(artifact, path[1]);
|
|
86
|
-
|
|
87
|
-
error( null, [ 'definitions', path[1] ], { name: path[1] }, 'Unexpected .expand with to-many association in entity $(NAME), which is outside any service');
|
|
85
|
+
error( null, [ 'definitions', path[1] ], { name: path[1] }, 'Unexpected .expand with to-many association in entity $(NAME)');
|
|
88
86
|
}
|
|
89
87
|
else {
|
|
90
88
|
parent.columns = rewritten.columns;
|
|
@@ -98,7 +96,8 @@ function expandStructureReferences(csn, options, pathDelimiter, { error, info, t
|
|
|
98
96
|
if (!options.toOdata)
|
|
99
97
|
dummyfy();
|
|
100
98
|
|
|
101
|
-
|
|
99
|
+
if (cleanup)
|
|
100
|
+
cleanup.forEach(fn => fn());
|
|
102
101
|
|
|
103
102
|
({ effectiveType, inspectRef } = csnRefs(csn));
|
|
104
103
|
|
|
@@ -106,7 +105,7 @@ function expandStructureReferences(csn, options, pathDelimiter, { error, info, t
|
|
|
106
105
|
const publishing = [];
|
|
107
106
|
// OData must allow navigations to @cds.persistence.skip targets
|
|
108
107
|
// as valid navigations in the API
|
|
109
|
-
if (
|
|
108
|
+
if (options.transformation !== 'odata') {
|
|
110
109
|
applyTransformations(csn, {
|
|
111
110
|
target: (parent, name, target, path) => {
|
|
112
111
|
if (toDummyfy.indexOf(target) !== -1) {
|
|
@@ -209,6 +208,9 @@ function expandStructureReferences(csn, options, pathDelimiter, { error, info, t
|
|
|
209
208
|
* @param {string} name
|
|
210
209
|
*/
|
|
211
210
|
function markAsToDummyfy(artifact, name) {
|
|
211
|
+
if (!_dependents && !cleanup)
|
|
212
|
+
({ cleanup, _dependents } = setDependencies(csn, csnUtils));
|
|
213
|
+
|
|
212
214
|
const stack = [ [ artifact, name ] ];
|
|
213
215
|
while (stack.length > 0) {
|
|
214
216
|
const [ a, n ] = stack.pop();
|
|
@@ -347,14 +349,21 @@ function expandStructureReferences(csn, options, pathDelimiter, { error, info, t
|
|
|
347
349
|
}
|
|
348
350
|
else if (current.xpr || current.args) {
|
|
349
351
|
// We need to re-write refs in the .xpr/.args so they stay resolvable - we need to prepend the currentRef
|
|
350
|
-
|
|
352
|
+
rewriteExpressionArrays(current, currentRef);
|
|
351
353
|
expanded.push(Object.assign({}, current, { as: currentAlias.join(pathDelimiter) } ));
|
|
352
354
|
}
|
|
355
|
+
else if (current.on || current.cast?.on) {
|
|
356
|
+
rewriteOn(current, [ currentAlias.slice(0, -1).join(pathDelimiter) ]);
|
|
357
|
+
expanded.push(Object.assign({}, current, { ref: currentRef, as: currentAlias.join(pathDelimiter) } ));
|
|
358
|
+
}
|
|
353
359
|
else if (current.val !== undefined || current.func !== undefined) {
|
|
354
360
|
expanded.push(Object.assign(current, { as: currentAlias.join(pathDelimiter) }));
|
|
355
361
|
}
|
|
356
|
-
else {
|
|
357
|
-
expanded.push({
|
|
362
|
+
else if (current.$scope === '$magic' || current.$scope === '$self') {
|
|
363
|
+
expanded.push(Object.assign({}, current, { as: currentAlias.join(pathDelimiter) } ));
|
|
364
|
+
}
|
|
365
|
+
else { // preserve stuff like .cast for redirection
|
|
366
|
+
expanded.push(Object.assign({}, current, { ref: currentRef, as: currentAlias.join(pathDelimiter) } ));
|
|
358
367
|
}
|
|
359
368
|
}
|
|
360
369
|
|
|
@@ -367,37 +376,79 @@ function expandStructureReferences(csn, options, pathDelimiter, { error, info, t
|
|
|
367
376
|
* @param {object} parent Thing that has an .xpr/.args
|
|
368
377
|
* @param {string[]} ref Ref so far
|
|
369
378
|
*/
|
|
370
|
-
function
|
|
379
|
+
function rewriteExpressionArrays(parent, ref) {
|
|
371
380
|
const stack = [ [ parent, ref ] ];
|
|
372
381
|
while (stack.length > 0) {
|
|
373
382
|
const [ current, currentRef ] = stack.pop();
|
|
374
|
-
if (current.xpr)
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
383
|
+
if (current.xpr)
|
|
384
|
+
rewriteSingleExpressionArray(current.xpr, currentRef, stack);
|
|
385
|
+
if (current.args)
|
|
386
|
+
rewriteSingleExpressionArray(current.args, currentRef, stack);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* With a .cast.on or .on in a .expand/.inline, we need to change the references,
|
|
392
|
+
* since we change the overall scope of things (by "heaving" them up into "normal refs").
|
|
393
|
+
*
|
|
394
|
+
* So anything that does not have a $self/$projection infron get's the so-far-traveled alias,
|
|
395
|
+
* since after the transformation it will basically be in "top-level".
|
|
396
|
+
*
|
|
397
|
+
* @param {object} parent
|
|
398
|
+
* @param {Array} ref The so-far effective name (basically the will-be alias), as an array to treat like a ref
|
|
399
|
+
*/
|
|
400
|
+
function rewriteOn(parent, ref) {
|
|
401
|
+
const stack = [ [ parent, ref ] ];
|
|
402
|
+
while (stack.length > 0) {
|
|
403
|
+
const [ current, currentRef ] = stack.pop();
|
|
404
|
+
if (current.on)
|
|
405
|
+
rewriteOnCondition(current.on, currentRef, stack);
|
|
406
|
+
if (current.cast?.on)
|
|
407
|
+
rewriteOnCondition(current.cast.on, currentRef, stack);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
/**
|
|
412
|
+
* Actually rewrite the given oncondition. Once we find something to rewrite,
|
|
413
|
+
* we preprend the currentRef.
|
|
414
|
+
*
|
|
415
|
+
* All stuff is pushed to the stack.
|
|
416
|
+
*
|
|
417
|
+
* @param {Array} on
|
|
418
|
+
* @param {Array} currentRef
|
|
419
|
+
* @param {Array} stack
|
|
420
|
+
*/
|
|
421
|
+
function rewriteOnCondition(on, currentRef, stack) {
|
|
422
|
+
for (let i = 0; i < on.length; i++) {
|
|
423
|
+
const part = on[i];
|
|
424
|
+
if (part.ref && part.ref[0] !== '$self' && part.ref[0] !== '$projection') {
|
|
425
|
+
part.ref = currentRef[0] ? [ currentRef[0], ...part.ref ] : part.ref;
|
|
426
|
+
on[i] = part;
|
|
427
|
+
stack.push([ part, part.ref ]);
|
|
387
428
|
}
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
429
|
+
else {
|
|
430
|
+
stack.push([ part, currentRef ]);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
/**
|
|
436
|
+
* Rewrite the given expressionArray, prefixing currentRef to all refs
|
|
437
|
+
*
|
|
438
|
+
* @param {Array} expressionArray
|
|
439
|
+
* @param {Array} currentRef
|
|
440
|
+
* @param {Array} stack
|
|
441
|
+
*/
|
|
442
|
+
function rewriteSingleExpressionArray(expressionArray, currentRef, stack) {
|
|
443
|
+
for (let i = 0; i < expressionArray.length; i++) {
|
|
444
|
+
const part = expressionArray[i];
|
|
445
|
+
if (part.ref) {
|
|
446
|
+
part.ref = currentRef.concat(part.ref);
|
|
447
|
+
expressionArray[i] = part;
|
|
448
|
+
stack.push([ part, part.ref ]);
|
|
449
|
+
}
|
|
450
|
+
else {
|
|
451
|
+
stack.push([ part, currentRef ]);
|
|
401
452
|
}
|
|
402
453
|
}
|
|
403
454
|
}
|
|
@@ -409,7 +460,7 @@ function expandStructureReferences(csn, options, pathDelimiter, { error, info, t
|
|
|
409
460
|
*/
|
|
410
461
|
function findAnEntity() {
|
|
411
462
|
for (const name in csn.definitions) {
|
|
412
|
-
if (Object.hasOwnProperty.call(csn.definitions, name) && csn.definitions[name].kind === 'entity' && !csn.definitions[name].query)
|
|
463
|
+
if (Object.prototype.hasOwnProperty.call(csn.definitions, name) && csn.definitions[name].kind === 'entity' && !csn.definitions[name].query)
|
|
413
464
|
return name;
|
|
414
465
|
}
|
|
415
466
|
return null;
|
|
@@ -124,7 +124,7 @@ function resolveTypeReferences(csn, options, resolved, pathDelimiter, iterateOpt
|
|
|
124
124
|
}
|
|
125
125
|
},
|
|
126
126
|
}, [ (definitions, artifactName, artifact) => {
|
|
127
|
-
// Replace events, actions and functions with simple dummies - they don't have effect on
|
|
127
|
+
// Replace events, actions and functions with simple dummies - they don't have effect on forRelationalDB stuff
|
|
128
128
|
// and that way they contain no references and don't hurt.
|
|
129
129
|
|
|
130
130
|
// Do not do for OData
|
|
@@ -81,7 +81,9 @@ function getViewDecorator(csn, messageFunctions) {
|
|
|
81
81
|
}
|
|
82
82
|
}
|
|
83
83
|
else {
|
|
84
|
-
info(null, [ 'definitions', artifactName ],
|
|
84
|
+
info(null, [ 'definitions', artifactName ],
|
|
85
|
+
{ source: `${from[0].errorParent}.${from[0].name}`, target: `${to[0].errorParent}.${to[0].name}` },
|
|
86
|
+
'No temporal WHERE clause added as $(SOURCE) and $(TARGET) are not of same origin');
|
|
85
87
|
}
|
|
86
88
|
}
|
|
87
89
|
else if (from.length > 0 || to.length > 0) {
|
|
@@ -428,6 +428,11 @@ function handleExists(csn, options, error) {
|
|
|
428
428
|
* @returns {object[]} The stuff to add to the where
|
|
429
429
|
*/
|
|
430
430
|
function translateManagedAssocToWhere(root, target, isPrefixedWithTableAlias, base, current) {
|
|
431
|
+
if (current.$scope === '$self') {
|
|
432
|
+
error('ref-unexpected-exists-self', current.$path, { id: current.ref[0], name: 'exists' }, 'With $(NAME), path steps must not start with $(ID)');
|
|
433
|
+
return [];
|
|
434
|
+
}
|
|
435
|
+
|
|
431
436
|
const whereExtension = [];
|
|
432
437
|
for (let j = 0; j < root.keys.length; j++) {
|
|
433
438
|
const lop = { ref: [ target, ...root.keys[j].ref ] }; // target side
|
|
@@ -799,8 +804,9 @@ function handleExists(csn, options, error) {
|
|
|
799
804
|
where.push({ ref: [ base, ...part.ref.slice(1) ] });
|
|
800
805
|
}
|
|
801
806
|
else if (part.$scope === '$self') { // source side - "absolute" scope
|
|
802
|
-
// Same message as in
|
|
803
|
-
error(null, part.$path,
|
|
807
|
+
// Same message as in forRelationalDB/transformDollarSelfComparisonWithUnmanagedAssoc
|
|
808
|
+
error(null, part.$path, { name: '$self' },
|
|
809
|
+
'An association that uses $(NAME) in its ON-condition can\'t be compared to "$self"');
|
|
804
810
|
}
|
|
805
811
|
else if (partInspect.art) { // source side - with local scope
|
|
806
812
|
where.push({ ref: [ target, ...assoc.ref.slice(1, -1), ...part.ref ] });
|