@sap/cds-compiler 3.0.2 → 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 +65 -0
- package/bin/.eslintrc.json +2 -1
- package/bin/cdsc.js +19 -0
- package/doc/API.md +11 -0
- package/doc/CHANGELOG_ARCHIVE.md +1 -1
- package/doc/CHANGELOG_BETA.md +24 -2
- package/doc/CHANGELOG_DEPRECATED.md +21 -1
- package/lib/api/main.js +7 -7
- package/lib/api/options.js +2 -3
- package/lib/base/message-registry.js +17 -5
- package/lib/base/messages.js +18 -39
- package/lib/base/model.js +2 -0
- package/lib/checks/actionsFunctions.js +8 -7
- package/lib/checks/selectItems.js +96 -14
- package/lib/checks/types.js +5 -8
- package/lib/checks/validator.js +1 -2
- package/lib/compiler/assert-consistency.js +64 -12
- package/lib/compiler/base.js +6 -4
- package/lib/compiler/builtins.js +58 -8
- package/lib/compiler/checks.js +1 -1
- package/lib/compiler/define.js +25 -22
- package/lib/compiler/extend.js +16 -10
- package/lib/compiler/finalize-parse-cdl.js +5 -9
- package/lib/compiler/index.js +2 -0
- package/lib/compiler/populate.js +34 -31
- package/lib/compiler/propagator.js +11 -6
- package/lib/compiler/resolve.js +14 -15
- package/lib/compiler/shared.js +53 -26
- package/lib/compiler/tweak-assocs.js +5 -11
- package/lib/compiler/utils.js +13 -4
- package/lib/edm/annotations/preprocessAnnotations.js +8 -4
- package/lib/edm/csn2edm.js +3 -3
- package/lib/edm/edm.js +9 -1
- package/lib/edm/edmAnnoPreprocessor.js +349 -0
- package/lib/edm/edmInboundChecks.js +85 -0
- package/lib/edm/edmPreprocessor.js +295 -638
- package/lib/edm/edmUtils.js +85 -5
- package/lib/gen/Dictionary.json +29 -9
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +1 -2
- package/lib/gen/languageLexer.js +3 -0
- package/lib/gen/languageParser.js +4344 -4530
- 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 +3 -2
- package/lib/json/to-csn.js +8 -6
- package/lib/language/genericAntlrParser.js +121 -63
- package/lib/language/language.g4 +19 -57
- package/lib/main.d.ts +1 -0
- package/lib/model/api.js +1 -1
- package/lib/model/csnRefs.js +55 -29
- package/lib/model/csnUtils.js +11 -7
- package/lib/model/revealInternalProperties.js +2 -3
- package/lib/modelCompare/compare.js +3 -0
- package/lib/optionProcessor.js +27 -0
- package/lib/render/toCdl.js +57 -32
- package/lib/render/toSql.js +24 -8
- package/lib/render/utils/common.js +3 -4
- package/lib/transform/db/associations.js +43 -35
- package/lib/transform/db/cdsPersistence.js +0 -1
- package/lib/transform/db/flattening.js +3 -4
- package/lib/transform/db/transformExists.js +7 -5
- package/lib/transform/draft/db.js +1 -1
- package/lib/transform/forHanaNew.js +11 -2
- package/lib/transform/forOdataNew.js +1 -1
- package/lib/transform/odata/typesExposure.js +14 -5
- package/lib/utils/moduleResolve.js +0 -1
- package/package.json +2 -2
- package/lib/checks/unknownMagic.js +0 -41
package/lib/render/toSql.js
CHANGED
|
@@ -126,8 +126,9 @@ function toSqlDdl(csn, options) {
|
|
|
126
126
|
fromElementsObj(artifactName, tableName, elementsObj, env, duplicateChecker) {
|
|
127
127
|
// Only extend with 'ADD' for elements/associations
|
|
128
128
|
// TODO: May also include 'RENAME' at a later stage
|
|
129
|
+
const alterEnv = activateAlterMode(env);
|
|
129
130
|
const elements = Object.entries(elementsObj)
|
|
130
|
-
.map(([ name, elt ]) => renderElement(artifactName, name, elt, duplicateChecker, null,
|
|
131
|
+
.map(([ name, elt ]) => renderElement(artifactName, name, elt, duplicateChecker, null, alterEnv))
|
|
131
132
|
.filter(s => s !== '');
|
|
132
133
|
|
|
133
134
|
if (elements.length)
|
|
@@ -203,7 +204,7 @@ function toSqlDdl(csn, options) {
|
|
|
203
204
|
Render comment string.
|
|
204
205
|
*/
|
|
205
206
|
comment(comment) {
|
|
206
|
-
return comment && renderStringForSql(comment, options.sqlDialect) || 'NULL';
|
|
207
|
+
return comment && renderStringForSql(getHanaComment({ doc: comment }), options.sqlDialect) || 'NULL';
|
|
207
208
|
},
|
|
208
209
|
/*
|
|
209
210
|
Alter SQL snippet for entity.
|
|
@@ -437,7 +438,7 @@ function toSqlDdl(csn, options) {
|
|
|
437
438
|
function getEltStr(defVariant, eltName) {
|
|
438
439
|
return defVariant.target
|
|
439
440
|
? renderAssociationElement(eltName, defVariant, env)
|
|
440
|
-
: renderElement(artifactName, eltName, defVariant, null, null, env);
|
|
441
|
+
: renderElement(artifactName, eltName, defVariant, null, null, activateAlterMode(env));
|
|
441
442
|
}
|
|
442
443
|
function getEltStrNoProps(defVariant, eltName, ...props) {
|
|
443
444
|
const defNoProps = Object.assign({}, defVariant);
|
|
@@ -464,7 +465,7 @@ function toSqlDdl(csn, options) {
|
|
|
464
465
|
// Change entity properties
|
|
465
466
|
if (migration.properties) {
|
|
466
467
|
for (const [ prop, def ] of Object.entries(migration.properties)) {
|
|
467
|
-
if (prop === 'doc') {
|
|
468
|
+
if (prop === 'doc' && !options.disableHanaComments) { // def.new may be `null`
|
|
468
469
|
const alterComment = render.alterEntityComment(tableName, def.new);
|
|
469
470
|
addMigration(resultObj, artifactName, false, alterComment);
|
|
470
471
|
}
|
|
@@ -530,7 +531,7 @@ function toSqlDdl(csn, options) {
|
|
|
530
531
|
}
|
|
531
532
|
}
|
|
532
533
|
|
|
533
|
-
if (def.old.doc !== def.new.doc) {
|
|
534
|
+
if (!options.disableHanaComments && def.old.doc !== def.new.doc) {
|
|
534
535
|
const eltStrOldNoDoc = getEltStrNoProps(def.old, eltName, 'doc');
|
|
535
536
|
const eltStrNewNoDoc = getEltStrNoProps(def.new, eltName, 'doc');
|
|
536
537
|
if (eltStrOldNoDoc === eltStrNewNoDoc) { // only `doc` changed
|
|
@@ -756,7 +757,7 @@ function toSqlDdl(csn, options) {
|
|
|
756
757
|
duplicateChecker.addElement(quotedElementName, elm.$location, elementName);
|
|
757
758
|
|
|
758
759
|
let result = `${env.indent + quotedElementName} ${renderTypeReference(artifactName, elementName, elm)
|
|
759
|
-
}${renderNullability(elm, true)}`;
|
|
760
|
+
}${renderNullability(elm, true, env.alterMode)}`;
|
|
760
761
|
if (elm.default)
|
|
761
762
|
result += ` DEFAULT ${renderExpr(elm.default, env)}`;
|
|
762
763
|
|
|
@@ -1398,9 +1399,15 @@ function toSqlDdl(csn, options) {
|
|
|
1398
1399
|
*
|
|
1399
1400
|
* @param {object} obj Object to render for
|
|
1400
1401
|
* @param {boolean} treatKeyAsNotNull Whether to render KEY as not null
|
|
1402
|
+
* @param {boolean} deltaMode Look for a $notNull and use that with precedence over notNull
|
|
1401
1403
|
* @returns {string} NULL/NOT NULL or ''
|
|
1402
1404
|
*/
|
|
1403
|
-
function renderNullability(obj, treatKeyAsNotNull = false) {
|
|
1405
|
+
function renderNullability(obj, treatKeyAsNotNull = false, deltaMode = false) {
|
|
1406
|
+
if (deltaMode && obj.$notNull !== undefined) { // can be set via compare.js if it goes from "not null" to implicit "null"
|
|
1407
|
+
return obj.$notNull ? ' NOT NULL' : ' NULL';
|
|
1408
|
+
}
|
|
1409
|
+
|
|
1410
|
+
|
|
1404
1411
|
if (obj.notNull === undefined && !(obj.key && treatKeyAsNotNull)) {
|
|
1405
1412
|
// Attribute not set at all
|
|
1406
1413
|
return '';
|
|
@@ -1535,7 +1542,7 @@ function toSqlDdl(csn, options) {
|
|
|
1535
1542
|
return 'SESSION_CONTEXT(\'LOCALE\')';
|
|
1536
1543
|
return '\'en\''; // default language
|
|
1537
1544
|
}
|
|
1538
|
-
// Basically: Second path step was invalid, do nothing - should not happen
|
|
1545
|
+
// Basically: Second path step was invalid, do nothing - should not happen.
|
|
1539
1546
|
return null;
|
|
1540
1547
|
}
|
|
1541
1548
|
/**
|
|
@@ -1658,6 +1665,15 @@ function toSqlDdl(csn, options) {
|
|
|
1658
1665
|
function increaseIndent(env) {
|
|
1659
1666
|
return Object.assign({}, env, { indent: `${env.indent} ` });
|
|
1660
1667
|
}
|
|
1668
|
+
/**
|
|
1669
|
+
* Returns a copy of 'env' with alterMode set to true
|
|
1670
|
+
*
|
|
1671
|
+
* @param {object} env Render environment
|
|
1672
|
+
* @returns {object} Render environment with alterMode
|
|
1673
|
+
*/
|
|
1674
|
+
function activateAlterMode(env) {
|
|
1675
|
+
return Object.assign({ alterMode: true }, env);
|
|
1676
|
+
}
|
|
1661
1677
|
}
|
|
1662
1678
|
|
|
1663
1679
|
/**
|
|
@@ -286,8 +286,8 @@ const cdsToSqlTypes = {
|
|
|
286
286
|
'cds.LargeString': 'text',
|
|
287
287
|
'cds.hana.CLOB': 'text',
|
|
288
288
|
'cds.LargeBinary': 'bytea',
|
|
289
|
-
'cds.Binary': '
|
|
290
|
-
'cds.hana.BINARY': '
|
|
289
|
+
'cds.Binary': 'bytea',
|
|
290
|
+
'cds.hana.BINARY': 'bytea',
|
|
291
291
|
'cds.Double': 'double precision',
|
|
292
292
|
'cds.hana.TINYINT': 'INTEGER',
|
|
293
293
|
},
|
|
@@ -358,11 +358,10 @@ function hasHanaComment(obj, options) {
|
|
|
358
358
|
* Return the comment of the given artifact or element.
|
|
359
359
|
* Uses the first block (everything up to the first empty line (double \n)).
|
|
360
360
|
* Remove leading/trailing whitespace.
|
|
361
|
-
* Does not escape any characters.
|
|
361
|
+
* Does not escape any characters, use e.g. `getEscapedHanaComment()` for HDBCDS.
|
|
362
362
|
*
|
|
363
363
|
* @param {CSN.Artifact|CSN.Element} obj
|
|
364
364
|
* @returns {string}
|
|
365
|
-
* @todo Warning/info to user?
|
|
366
365
|
*/
|
|
367
366
|
function getHanaComment(obj) {
|
|
368
367
|
return obj.doc.split('\n\n')[0].trim();
|
|
@@ -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,7 +78,6 @@ 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
83
|
if (options.sqlDialect === 'hana' &&
|
|
@@ -47,7 +47,6 @@ function removeLeadingSelf(csn) {
|
|
|
47
47
|
* @param {object} iterateOptions
|
|
48
48
|
*/
|
|
49
49
|
function resolveTypeReferences(csn, options, resolved, pathDelimiter, iterateOptions = {}) {
|
|
50
|
-
const typeCache = Object.create(null); // TODO: Argument as well?
|
|
51
50
|
/**
|
|
52
51
|
* Remove .localized from the element and any sub-elements
|
|
53
52
|
*
|
|
@@ -83,14 +82,14 @@ function resolveTypeReferences(csn, options, resolved, pathDelimiter, iterateOpt
|
|
|
83
82
|
cast: (parent, prop, cast, path) => {
|
|
84
83
|
// Resolve cast already - we otherwise lose .localized
|
|
85
84
|
if (cast.type && !isBuiltinType(cast.type) && (!options.toOdata || options.toOdata && !isODataV4BuiltinFromService(cast.type, path) && !isODataItems(cast.type)))
|
|
86
|
-
toFinalBaseType(parent.cast, resolved, true
|
|
85
|
+
toFinalBaseType(parent.cast, resolved, true);
|
|
87
86
|
},
|
|
88
87
|
// @ts-ignore
|
|
89
88
|
type: (parent, prop, type, path) => {
|
|
90
89
|
if (options.toOdata && parent.kind && parent.kind in ignoreOdataKinds)
|
|
91
90
|
return;
|
|
92
91
|
if (!isBuiltinType(type) && (!options.toOdata || options.toOdata && !isODataV4BuiltinFromService(type, path) && !isODataItems(type))) {
|
|
93
|
-
toFinalBaseType(parent, resolved, true
|
|
92
|
+
toFinalBaseType(parent, resolved, true);
|
|
94
93
|
// structured types might not have the child-types replaced.
|
|
95
94
|
// Drill down to ensure this.
|
|
96
95
|
if (parent.elements) {
|
|
@@ -99,7 +98,7 @@ function resolveTypeReferences(csn, options, resolved, pathDelimiter, iterateOpt
|
|
|
99
98
|
const elements = stack.pop();
|
|
100
99
|
for (const e of Object.values(elements)) {
|
|
101
100
|
if (e.type && !isBuiltinType(e.type))
|
|
102
|
-
toFinalBaseType(e, resolved, true
|
|
101
|
+
toFinalBaseType(e, resolved, true);
|
|
103
102
|
|
|
104
103
|
if (e.elements)
|
|
105
104
|
stack.push(e.elements);
|
|
@@ -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);
|
|
@@ -19,7 +19,7 @@ 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();
|
|
@@ -265,10 +265,10 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
265
265
|
// To be done after handleAssociations, since then the foreign keys of the managed assocs
|
|
266
266
|
// are part of the elements
|
|
267
267
|
if (doA2J)
|
|
268
|
-
forEachDefinition(csn, associations.
|
|
268
|
+
forEachDefinition(csn, associations.getFKAccessFinalizer(csn, pathDelimiter));
|
|
269
269
|
|
|
270
270
|
// Create convenience views for localized entities/views.
|
|
271
|
-
// To be done after
|
|
271
|
+
// To be done after getFKAccessFinalizer because associations are
|
|
272
272
|
// handled and before handleDBChecks which removes the localized attribute.
|
|
273
273
|
// Association elements of localized convenience views do not have hidden properties
|
|
274
274
|
// like $managed set, so we cannot do this earlier on.
|
|
@@ -392,6 +392,15 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
392
392
|
setProp(SET, 'elements', query.SELECT.elements);
|
|
393
393
|
}
|
|
394
394
|
}
|
|
395
|
+
},
|
|
396
|
+
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
if(options.sqlDialect === 'postgres') {
|
|
400
|
+
killers.length = (parent) => {
|
|
401
|
+
if (parent.type === 'cds.Binary' || parent.type === 'cds.hana.BINARY') {
|
|
402
|
+
delete parent.length;
|
|
403
|
+
}
|
|
395
404
|
}
|
|
396
405
|
}
|
|
397
406
|
|
|
@@ -178,7 +178,7 @@ function transform4odataWithCsn(inputModel, options) {
|
|
|
178
178
|
// To be done after handleManagedAssociationsAndCreateForeignKeys,
|
|
179
179
|
// since then the foreign keys of the managed assocs are part of the elements
|
|
180
180
|
if(!structuredOData)
|
|
181
|
-
forEachDefinition(csn, associations.
|
|
181
|
+
forEachDefinition(csn, associations.getFKAccessFinalizer(csn, '_'));
|
|
182
182
|
|
|
183
183
|
// structure flattener reports errors, further processing is not safe -> throw exception in case of errors
|
|
184
184
|
throwWithAnyError();
|
|
@@ -10,6 +10,7 @@ const { setProp } = require('../../base/model');
|
|
|
10
10
|
const { defNameWithoutServiceOrContextName, isArtifactInService } = require('./utils');
|
|
11
11
|
const { cloneCsnNonDict, isBuiltinType, forEachDefinition, forEachMember } = require('../../model/csnUtils');
|
|
12
12
|
const { copyAnnotations } = require('../../model/csnUtils');
|
|
13
|
+
const { isBetaEnabled } = require('../../base/model.js');
|
|
13
14
|
|
|
14
15
|
function typesExposure(csn, whatsMyServiceName, requestedServiceNames, fallBackSchemaName, options, csnUtils, message) {
|
|
15
16
|
const { error } = message;
|
|
@@ -92,11 +93,16 @@ function typesExposure(csn, whatsMyServiceName, requestedServiceNames, fallBackS
|
|
|
92
93
|
: getAnonymousTypeNameInMultiSchema(newTypeName, parentName || defName))
|
|
93
94
|
: `${serviceName}.${newTypeName}`;
|
|
94
95
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
isKey
|
|
99
|
-
|
|
96
|
+
if (!isAnonymous) {
|
|
97
|
+
// as soon as we leave of the anonymous world,
|
|
98
|
+
// we're no longer in a key def => don't set notNull:true on named types
|
|
99
|
+
if(isKey)
|
|
100
|
+
isKey = false;
|
|
101
|
+
// in case this was a named type and if the openess does not match the type definition
|
|
102
|
+
// expose the type as a new one not changing the original definition.
|
|
103
|
+
if((!!node['@open'] !== !!typeDef['@open']) && isBetaEnabled(options, 'odataOpenType'))
|
|
104
|
+
fullQualifiedNewTypeName += node['@open'] ? '_open' : '_closed';
|
|
105
|
+
}
|
|
100
106
|
// check if that type is already defined
|
|
101
107
|
let newType = csn.definitions[fullQualifiedNewTypeName];
|
|
102
108
|
if (newType) {
|
|
@@ -111,6 +117,9 @@ function typesExposure(csn, whatsMyServiceName, requestedServiceNames, fallBackS
|
|
|
111
117
|
* Treat items.elements as ordinary elements for now.
|
|
112
118
|
*/
|
|
113
119
|
newType = createNewStructType(elements);
|
|
120
|
+
// if using node enforces open/closed, set it on type
|
|
121
|
+
if(node['@open'] !== undefined)
|
|
122
|
+
newType['@open'] = node['@open']
|
|
114
123
|
if (node.$location)
|
|
115
124
|
setProp(newType, '$location', node.$location);
|
|
116
125
|
|
|
@@ -25,7 +25,6 @@ const extensions = [ '.cds', '.csn', '.json' ];
|
|
|
25
25
|
* @todo Re-think:
|
|
26
26
|
* - Why can't a JAVA installation set a (symbolic) link?
|
|
27
27
|
* - Preferred to a local installation? Not the node-way!
|
|
28
|
-
* - Why a global? The Umbrella could pass it as an option.
|
|
29
28
|
*
|
|
30
29
|
* @param {string} modulePath
|
|
31
30
|
* @param {CSN.Options} options
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sap/cds-compiler",
|
|
3
|
-
"version": "3.0
|
|
3
|
+
"version": "3.1.0",
|
|
4
4
|
"description": "CDS (Core Data Services) compiler and backends",
|
|
5
5
|
"homepage": "https://cap.cloud.sap/",
|
|
6
6
|
"author": "SAP SE (https://www.sap.com)",
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"scripts": {
|
|
17
17
|
"download": "node scripts/downloadANTLR.js",
|
|
18
18
|
"gen": "node ./scripts/build.js && node scripts/genGrammarChecksum.js",
|
|
19
|
-
"xmakeBeforeInstall": "echo \"Due to binary mirror, use sqlite 5.0.
|
|
19
|
+
"xmakeBeforeInstall": "echo \"Due to binary mirror, use sqlite 5.0.11 explicitly\" && npm install --save --save-exact --no-package-lock sqlite3@5.0.11",
|
|
20
20
|
"xmakeAfterInstall": "npm run gen",
|
|
21
21
|
"xmakePrepareRelease": "echo \"$(node scripts/stripReadme.js README.md)\" > README.md && node scripts/assertSnapshotVersioning.js && node scripts/assertChangelog.js && node scripts/cleanup.js --remove-dev",
|
|
22
22
|
"test": "node scripts/verifyGrammarChecksum.js && mocha --reporter min --reporter-option maxDiffSize=0 scripts/testLazyLoading.js && mocha --parallel --reporter min --reporter-option maxDiffSize=0 test/ test3/",
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const { getVariableReplacement } = require('../model/csnUtils');
|
|
4
|
-
|
|
5
|
-
// We only care about the "wild" ones - $at is validated by the compiler
|
|
6
|
-
const magicVariables = {
|
|
7
|
-
$user: [
|
|
8
|
-
'id', // $user.id
|
|
9
|
-
'locale', // $user.locale
|
|
10
|
-
],
|
|
11
|
-
$session: [
|
|
12
|
-
// no valid ways for this
|
|
13
|
-
],
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Check that the given ref does not use magic variables for which we don't have
|
|
18
|
-
* a valid way of rendering.
|
|
19
|
-
*
|
|
20
|
-
* Valid ways:
|
|
21
|
-
* - We know what to do -> $user.id on HANA
|
|
22
|
-
* - The user tells us what to do -> options.variableReplacements
|
|
23
|
-
*
|
|
24
|
-
* @param {object} parent Object with the ref as a property
|
|
25
|
-
* @param {string} name Name of the ref property on parent
|
|
26
|
-
* @param {Array} ref to check
|
|
27
|
-
*/
|
|
28
|
-
function unknownMagicVariable(parent, name, ref) {
|
|
29
|
-
if (parent.$scope && parent.$scope === '$magic') {
|
|
30
|
-
const [ head, ...rest ] = ref;
|
|
31
|
-
const tail = rest.join('.');
|
|
32
|
-
const magicVariable = magicVariables[head];
|
|
33
|
-
if (magicVariable && magicVariable.indexOf(tail) === -1 &&
|
|
34
|
-
getVariableReplacement(ref, this.options) === null)
|
|
35
|
-
this.error('ref-missing-replacement', parent.$location, { elemref: parent }, 'Missing replacement for variable $(ELEMREF)');
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
module.exports = {
|
|
40
|
-
ref: unknownMagicVariable,
|
|
41
|
-
};
|