@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.
Files changed (72) hide show
  1. package/CHANGELOG.md +65 -0
  2. package/bin/.eslintrc.json +2 -1
  3. package/bin/cdsc.js +19 -0
  4. package/doc/API.md +11 -0
  5. package/doc/CHANGELOG_ARCHIVE.md +1 -1
  6. package/doc/CHANGELOG_BETA.md +24 -2
  7. package/doc/CHANGELOG_DEPRECATED.md +21 -1
  8. package/lib/api/main.js +7 -7
  9. package/lib/api/options.js +2 -3
  10. package/lib/base/message-registry.js +17 -5
  11. package/lib/base/messages.js +18 -39
  12. package/lib/base/model.js +2 -0
  13. package/lib/checks/actionsFunctions.js +8 -7
  14. package/lib/checks/selectItems.js +96 -14
  15. package/lib/checks/types.js +5 -8
  16. package/lib/checks/validator.js +1 -2
  17. package/lib/compiler/assert-consistency.js +64 -12
  18. package/lib/compiler/base.js +6 -4
  19. package/lib/compiler/builtins.js +58 -8
  20. package/lib/compiler/checks.js +1 -1
  21. package/lib/compiler/define.js +25 -22
  22. package/lib/compiler/extend.js +16 -10
  23. package/lib/compiler/finalize-parse-cdl.js +5 -9
  24. package/lib/compiler/index.js +2 -0
  25. package/lib/compiler/populate.js +34 -31
  26. package/lib/compiler/propagator.js +11 -6
  27. package/lib/compiler/resolve.js +14 -15
  28. package/lib/compiler/shared.js +53 -26
  29. package/lib/compiler/tweak-assocs.js +5 -11
  30. package/lib/compiler/utils.js +13 -4
  31. package/lib/edm/annotations/preprocessAnnotations.js +8 -4
  32. package/lib/edm/csn2edm.js +3 -3
  33. package/lib/edm/edm.js +9 -1
  34. package/lib/edm/edmAnnoPreprocessor.js +349 -0
  35. package/lib/edm/edmInboundChecks.js +85 -0
  36. package/lib/edm/edmPreprocessor.js +295 -638
  37. package/lib/edm/edmUtils.js +85 -5
  38. package/lib/gen/Dictionary.json +29 -9
  39. package/lib/gen/language.checksum +1 -1
  40. package/lib/gen/language.interp +1 -2
  41. package/lib/gen/languageLexer.js +3 -0
  42. package/lib/gen/languageParser.js +4344 -4530
  43. package/lib/inspect/.eslintrc.json +4 -0
  44. package/lib/inspect/index.js +14 -0
  45. package/lib/inspect/inspectModelStatistics.js +81 -0
  46. package/lib/inspect/inspectPropagation.js +189 -0
  47. package/lib/inspect/inspectUtils.js +44 -0
  48. package/lib/json/from-csn.js +3 -2
  49. package/lib/json/to-csn.js +8 -6
  50. package/lib/language/genericAntlrParser.js +121 -63
  51. package/lib/language/language.g4 +19 -57
  52. package/lib/main.d.ts +1 -0
  53. package/lib/model/api.js +1 -1
  54. package/lib/model/csnRefs.js +55 -29
  55. package/lib/model/csnUtils.js +11 -7
  56. package/lib/model/revealInternalProperties.js +2 -3
  57. package/lib/modelCompare/compare.js +3 -0
  58. package/lib/optionProcessor.js +27 -0
  59. package/lib/render/toCdl.js +57 -32
  60. package/lib/render/toSql.js +24 -8
  61. package/lib/render/utils/common.js +3 -4
  62. package/lib/transform/db/associations.js +43 -35
  63. package/lib/transform/db/cdsPersistence.js +0 -1
  64. package/lib/transform/db/flattening.js +3 -4
  65. package/lib/transform/db/transformExists.js +7 -5
  66. package/lib/transform/draft/db.js +1 -1
  67. package/lib/transform/forHanaNew.js +11 -2
  68. package/lib/transform/forOdataNew.js +1 -1
  69. package/lib/transform/odata/typesExposure.js +14 -5
  70. package/lib/utils/moduleResolve.js +0 -1
  71. package/package.json +2 -2
  72. package/lib/checks/unknownMagic.js +0 -41
@@ -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, env))
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, see 'unknownMagic.js'
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': 'VARCHAR',
290
- 'cds.hana.BINARY': 'VARCHAR',
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 getManagedAssocStepsInOnConditionFinalizer(csn, pathDelimiter) {
110
+ function getFKAccessFinalizer(csn, pathDelimiter) {
111
111
  const {
112
112
  inspectRef,
113
113
  } = getUtils(csn);
114
114
 
115
- return handleManagedAssocStepsInOnCondition;
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 handleManagedAssocStepsInOnCondition(artifact, artifactName) {
127
- for (const elemName in artifact.elements) {
128
- const elem = artifact.elements[elemName];
129
- // The association is an unmanaged on
130
- if (!elem.keys && elem.target && elem.on) {
131
- applyTransformationsOnNonDictionary(elem, 'on', {
132
- ref: (refOwner, prop, ref, path) => {
133
- // [<assoc base>.]<managed assoc>.<field>
134
- if (ref.length > 1) {
135
- const { links } = inspectRef(path);
136
- if (links) {
137
- // eslint-disable-next-line for-direction
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
- // We join the managed assoc with everything following it
145
- const sourceElementName = ref.slice(i).join(pathDelimiter);
146
- const source = findSource(links, i - 1) || artifact;
147
- // allow specifying managed assoc on the source side
148
- const fks = link.art.keys.filter(fk => ref[i] + pathDelimiter + fk.ref[0] === sourceElementName);
149
- if (fks && fks.length >= 1) {
150
- const fk = fks[0];
151
- const managedAssocStepName = refOwner.ref[i];
152
- const fkName = `${managedAssocStepName}${pathDelimiter}${fk.as}`;
153
- if (source && source.elements[fkName])
154
- refOwner.ref = [ ...ref.slice(0, i), fkName ];
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
- }, {}, [ 'definitions', artifactName, 'elements', elemName ]);
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
- getManagedAssocStepsInOnConditionFinalizer,
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, typeCache);
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, typeCache);
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, typeCache);
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[0]) : null;
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 the following for each part of the on-condition:
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 getRealName(csn, queryBase);
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 = isDeprecatedEnabled(options, '_generatedEntityNameWithUnderscore') ? '_drafts' : '.drafts';
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.getManagedAssocStepsInOnConditionFinalizer(csn, pathDelimiter));
268
+ forEachDefinition(csn, associations.getFKAccessFinalizer(csn, pathDelimiter));
269
269
 
270
270
  // Create convenience views for localized entities/views.
271
- // To be done after getManagedAssocStepsInOnConditionFinalizer because associations are
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.getManagedAssocStepsInOnConditionFinalizer(csn, '_'));
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
- // as soon as we leave of the anonymous world,
96
- // we're no longer in a key def => don't set notNull:true on named types
97
- if (!isAnonymous && isKey)
98
- isKey = false;
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.2",
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.8 explicitly\" && npm install --save --save-exact --no-package-lock sqlite3@5.0.8",
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
- };