@sap/cds-compiler 3.0.0 → 3.1.2
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 +104 -9
- package/bin/.eslintrc.json +2 -1
- package/bin/cdsc.js +28 -16
- 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 +92 -40
- package/lib/api/options.js +2 -3
- package/lib/base/keywords.js +64 -1
- package/lib/base/message-registry.js +33 -5
- package/lib/base/messages.js +54 -65
- package/lib/base/model.js +2 -0
- package/lib/base/optionProcessorHelper.js +53 -21
- 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 +65 -13
- package/lib/compiler/base.js +6 -4
- package/lib/compiler/builtins.js +93 -4
- package/lib/compiler/checks.js +1 -1
- package/lib/compiler/define.js +28 -23
- package/lib/compiler/extend.js +20 -11
- package/lib/compiler/finalize-parse-cdl.js +5 -9
- package/lib/compiler/index.js +2 -0
- package/lib/compiler/populate.js +37 -32
- package/lib/compiler/propagator.js +11 -6
- package/lib/compiler/resolve.js +15 -19
- package/lib/compiler/shared.js +54 -18
- package/lib/compiler/tweak-assocs.js +5 -11
- package/lib/compiler/utils.js +15 -6
- package/lib/edm/annotations/genericTranslation.js +12 -2
- package/lib/edm/annotations/preprocessAnnotations.js +18 -15
- package/lib/edm/csn2edm.js +18 -17
- 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 +336 -665
- package/lib/edm/edmUtils.js +86 -45
- 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 +4332 -4496
- 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 +19 -20
- package/lib/json/to-csn.js +11 -8
- package/lib/language/genericAntlrParser.js +150 -92
- package/lib/language/language.g4 +47 -74
- package/lib/main.d.ts +1 -0
- package/lib/model/api.js +1 -1
- package/lib/model/csnRefs.js +56 -29
- package/lib/model/csnUtils.js +29 -14
- package/lib/model/revealInternalProperties.js +6 -4
- package/lib/modelCompare/compare.js +3 -0
- package/lib/optionProcessor.js +81 -38
- package/lib/render/toCdl.js +57 -32
- package/lib/render/toHdbcds.js +1 -1
- package/lib/render/toSql.js +31 -11
- 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 +4 -4
- package/lib/transform/localized.js +15 -11
- package/lib/transform/odata/typesExposure.js +14 -5
- package/lib/utils/file.js +28 -18
- package/lib/utils/moduleResolve.js +0 -1
- package/package.json +3 -4
- package/share/messages/syntax-expected-integer.md +9 -8
- package/lib/checks/unknownMagic.js +0 -41
|
@@ -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
|
|
|
@@ -120,7 +120,7 @@ function transform4odataWithCsn(inputModel, options) {
|
|
|
120
120
|
return keepLocalizedViews && !isExternalServiceMember(undefined, parent);
|
|
121
121
|
}
|
|
122
122
|
|
|
123
|
-
addLocalizationViews(csn, options, acceptLocalizedView);
|
|
123
|
+
addLocalizationViews(csn, options, { acceptLocalizedView, ignoreUnknownExtensions: true });
|
|
124
124
|
|
|
125
125
|
const cleanup = validate.forOdata(csn, {
|
|
126
126
|
message, error, warning, info, inspectRef, effectiveType, artifactRef, csn, options, csnUtils, services, isAspect, isExternalServiceMember
|
|
@@ -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();
|
|
@@ -324,7 +324,7 @@ function transform4odataWithCsn(inputModel, options) {
|
|
|
324
324
|
// but '@Core.Immutable' for everything else.
|
|
325
325
|
if (!(node['@readonly'] && node['@insertonly'])) {
|
|
326
326
|
if (name === '@readonly' && node[name] !== null) {
|
|
327
|
-
if (node.kind === 'entity') {
|
|
327
|
+
if (node.kind === 'entity' || node.kind === 'aspect') {
|
|
328
328
|
setAnnotation(node, '@Capabilities.DeleteRestrictions.Deletable', false);
|
|
329
329
|
setAnnotation(node, '@Capabilities.InsertRestrictions.Insertable', false);
|
|
330
330
|
setAnnotation(node, '@Capabilities.UpdateRestrictions.Updatable', false);
|
|
@@ -334,7 +334,7 @@ function transform4odataWithCsn(inputModel, options) {
|
|
|
334
334
|
}
|
|
335
335
|
// @insertonly is effective on entities/queries only
|
|
336
336
|
else if (name === '@insertonly' && node[name] !== null) {
|
|
337
|
-
if (node.kind === 'entity') {
|
|
337
|
+
if (node.kind === 'entity' || node.kind === 'aspect') {
|
|
338
338
|
setAnnotation(node, '@Capabilities.DeleteRestrictions.Deletable', false);
|
|
339
339
|
setAnnotation(node, '@Capabilities.ReadRestrictions.Readable', false);
|
|
340
340
|
setAnnotation(node, '@Capabilities.UpdateRestrictions.Updatable', false);
|
|
@@ -69,10 +69,9 @@ const _targetFor = Symbol('_targetFor');
|
|
|
69
69
|
* @param {CSN.Options} options
|
|
70
70
|
* @param {boolean} useJoins If true, rewrite the "localized" association to a
|
|
71
71
|
* join in direct convenience views.
|
|
72
|
-
* @param {
|
|
73
|
-
* name and its parent name provided as parameter should be created
|
|
72
|
+
* @param {object} config
|
|
74
73
|
*/
|
|
75
|
-
function _addLocalizationViews(csn, options, useJoins,
|
|
74
|
+
function _addLocalizationViews(csn, options, useJoins, config) {
|
|
76
75
|
// Don't try to create convenience views with errors.
|
|
77
76
|
if (hasErrors(options.messages))
|
|
78
77
|
return csn;
|
|
@@ -81,6 +80,7 @@ function _addLocalizationViews(csn, options, useJoins, acceptLocalizedView = nul
|
|
|
81
80
|
if (hasExistingLocalizationViews(csn, options, messageFunctions))
|
|
82
81
|
return csn;
|
|
83
82
|
|
|
83
|
+
const { acceptLocalizedView, ignoreUnknownExtensions } = config;
|
|
84
84
|
const noCoalesce = (options.localizedLanguageFallback === 'none' ||
|
|
85
85
|
options.localizedWithoutCoalesce);
|
|
86
86
|
|
|
@@ -91,8 +91,12 @@ function _addLocalizationViews(csn, options, useJoins, acceptLocalizedView = nul
|
|
|
91
91
|
|
|
92
92
|
// In case that the user tried to annotate `localized.*` artifacts, apply them.
|
|
93
93
|
applyAnnotationsFromExtensions(csn, {
|
|
94
|
-
|
|
95
|
-
filter: (name) => name.startsWith('localized.')
|
|
94
|
+
override: true,
|
|
95
|
+
filter: (name) => name.startsWith('localized.'),
|
|
96
|
+
notFound(name, index) {
|
|
97
|
+
if (!ignoreUnknownExtensions)
|
|
98
|
+
messageFunctions.message('anno-undefined-art', [ 'extensions', index ], { name })
|
|
99
|
+
},
|
|
96
100
|
});
|
|
97
101
|
|
|
98
102
|
sortCsnDefinitionsForTests(csn, options);
|
|
@@ -608,10 +612,10 @@ function _addLocalizationViews(csn, options, useJoins, acceptLocalizedView = nul
|
|
|
608
612
|
*
|
|
609
613
|
* @param {CSN.Model} csn
|
|
610
614
|
* @param {CSN.Options} options
|
|
611
|
-
* @param [
|
|
615
|
+
* @param [config] config.acceptLocalizedView: optional callback function returning true if the localized view name and its parent name provided as parameter should be created
|
|
612
616
|
*/
|
|
613
|
-
function addLocalizationViews(csn, options,
|
|
614
|
-
return _addLocalizationViews(csn, options, false,
|
|
617
|
+
function addLocalizationViews(csn, options, config = {}) {
|
|
618
|
+
return _addLocalizationViews(csn, options, false, config);
|
|
615
619
|
}
|
|
616
620
|
|
|
617
621
|
/**
|
|
@@ -621,10 +625,10 @@ function addLocalizationViews(csn, options, acceptLocalizedView = null) {
|
|
|
621
625
|
*
|
|
622
626
|
* @param {CSN.Model} csn
|
|
623
627
|
* @param {CSN.Options} options
|
|
624
|
-
* @param [
|
|
628
|
+
* @param [config] config.acceptLocalizedView: optional callback function returning true if the localized view name and its parent name provided as parameter should be created
|
|
625
629
|
*/
|
|
626
|
-
function addLocalizationViewsWithJoins(csn, options,
|
|
627
|
-
return _addLocalizationViews(csn, options, true,
|
|
630
|
+
function addLocalizationViewsWithJoins(csn, options, config = {}) {
|
|
631
|
+
return _addLocalizationViews(csn, options, true, config);
|
|
628
632
|
}
|
|
629
633
|
|
|
630
634
|
/**
|
|
@@ -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
|
|
package/lib/utils/file.js
CHANGED
|
@@ -113,11 +113,16 @@ function cdsFs(fileCache, enableTrace) {
|
|
|
113
113
|
traceFS( 'READFILE:start:', filename );
|
|
114
114
|
// TODO: set cache directly to some "delay" - store error differently?
|
|
115
115
|
// e.g. an error of callback functions!
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
116
|
+
try {
|
|
117
|
+
reader(filename, enc, (err, data) => {
|
|
118
|
+
fileCache[filename] = err || data;
|
|
119
|
+
traceFS('READFILE:data:', filename, err || data);
|
|
120
|
+
cb(err, data);
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
catch (err) {
|
|
124
|
+
cb(err); // if filename is not a valid (e.g. contains NUL byte), readFile() may throw
|
|
125
|
+
}
|
|
121
126
|
}
|
|
122
127
|
};
|
|
123
128
|
}
|
|
@@ -143,19 +148,24 @@ function cdsFs(fileCache, enableTrace) {
|
|
|
143
148
|
// in the future (if we do module resolve ourself with just readFile),
|
|
144
149
|
// we avoid parallel readFile by storing having an array of `cb`s in
|
|
145
150
|
// fileCache[ filename ] before starting fs.readFile().
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
fileCache[filename]
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
151
|
+
try {
|
|
152
|
+
fsStat(filename, (err, stat) => {
|
|
153
|
+
if (err)
|
|
154
|
+
body = (err.code === 'ENOENT' || err.code === 'ENOTDIR') ? false : err;
|
|
155
|
+
else
|
|
156
|
+
body = !!(stat.isFile() || stat.isFIFO());
|
|
157
|
+
if (fileCache[filename] === undefined) // parallel readFile() has been processed
|
|
158
|
+
fileCache[filename] = body;
|
|
159
|
+
traceFS('ISFILE:data:', filename, body);
|
|
160
|
+
if (body instanceof Error)
|
|
161
|
+
cb(err);
|
|
162
|
+
else
|
|
163
|
+
cb(null, body);
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
catch (err) {
|
|
167
|
+
cb(err); // if filename is not a valid (e.g. contains NUL byte), fsStat() may throw
|
|
168
|
+
}
|
|
159
169
|
}
|
|
160
170
|
};
|
|
161
171
|
}
|
|
@@ -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.
|
|
3
|
+
"version": "3.1.2",
|
|
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,12 +16,11 @@
|
|
|
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
|
-
"test": "node scripts/verifyGrammarChecksum.js && mocha --parallel --reporter min --reporter-option maxDiffSize=0 test/ test3/
|
|
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/",
|
|
23
23
|
"testverbose": "node scripts/verifyGrammarChecksum.js && mocha --parallel test/ test3/",
|
|
24
|
-
"testdot": "node scripts/verifyGrammarChecksum.js && mocha --parallel --reporter dot --reporter-option maxDiffSize=0 --full-trace scripts/linter/lintMessages.js test/ test3/",
|
|
25
24
|
"test3": "node scripts/verifyGrammarChecksum.js && node scripts/linter/lintTests.js test3/ && mocha --reporter-option maxDiffSize=0 scripts/linter/lintMessages.js test3/",
|
|
26
25
|
"deployTest3SQL": "deployRefs=true mocha --reporter-option maxDiffSize=0 test3/testHANASQLDeployment.js",
|
|
27
26
|
"deployTest3": "deployRefs=true mocha --reporter-option maxDiffSize=0 test3/testDeployment.js",
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
# syntax-expected-integer
|
|
2
2
|
|
|
3
3
|
The compiler expects a safe integer here.
|
|
4
|
-
The last safe
|
|
4
|
+
The last safe integer is `2^53 - 1` or `9007199254740991`.
|
|
5
5
|
|
|
6
|
-
A safe integer is an integer that
|
|
6
|
+
A safe integer is an integer that fulfills all of the following:
|
|
7
7
|
|
|
8
|
-
-
|
|
9
|
-
-
|
|
8
|
+
- Can be exactly represented as an IEEE-754 double precision number.
|
|
9
|
+
- The IEEE-754 representation cannot be the result of rounding any
|
|
10
10
|
other integer to fit the IEEE-754 representation.
|
|
11
11
|
|
|
12
12
|
The message's severity is `Error`.
|
|
@@ -15,13 +15,14 @@ The message's severity is `Error`.
|
|
|
15
15
|
|
|
16
16
|
Erroneous code example:
|
|
17
17
|
|
|
18
|
+
<!-- cds-mode: ignore -->
|
|
18
19
|
```cdl
|
|
19
20
|
type LengthIsUnsafe : String(9007199254740992);
|
|
20
21
|
type NotAnInteger : String(42.1);
|
|
21
22
|
```
|
|
22
23
|
|
|
23
|
-
In the
|
|
24
|
-
safe
|
|
24
|
+
In the erroneous example, the string length for the type `LengthIsUnsafe` is
|
|
25
|
+
not a safe integer. It is too large.
|
|
25
26
|
Likewise, the string length for the type `NotAnInteger` is a decimal.
|
|
26
27
|
|
|
27
28
|
## How to Fix
|
|
@@ -33,5 +34,5 @@ type LengthIsSafe : String(9007199254740991);
|
|
|
33
34
|
type AnInteger : String(42);
|
|
34
35
|
```
|
|
35
36
|
|
|
36
|
-
If not feasible, a string representation of the
|
|
37
|
-
|
|
37
|
+
If it's not feasible to use a safe integer, a string representation of the
|
|
38
|
+
number needs to be used, for example, in annotation values.
|
|
@@ -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
|
-
};
|