@sap/cds-compiler 2.11.2 → 2.11.4

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 (46) hide show
  1. package/CHANGELOG.md +23 -2
  2. package/bin/.eslintrc.json +1 -2
  3. package/bin/cds_update_identifiers.js +3 -1
  4. package/bin/cdsc.js +8 -1
  5. package/bin/cdsv2m.js +3 -2
  6. package/lib/api/main.js +2 -16
  7. package/lib/api/options.js +3 -2
  8. package/lib/api/validate.js +7 -1
  9. package/lib/backends.js +3 -5
  10. package/lib/base/keywords.js +3 -2
  11. package/lib/base/message-registry.js +24 -8
  12. package/lib/base/messages.js +15 -9
  13. package/lib/base/optionProcessorHelper.js +1 -1
  14. package/lib/checks/emptyOrOnlyVirtual.js +2 -2
  15. package/lib/checks/unknownMagic.js +1 -1
  16. package/lib/compiler/assert-consistency.js +2 -2
  17. package/lib/compiler/builtins.js +34 -15
  18. package/lib/compiler/definer.js +8 -17
  19. package/lib/compiler/index.js +13 -25
  20. package/lib/compiler/resolver.js +89 -23
  21. package/lib/compiler/shared.js +25 -28
  22. package/lib/compiler/utils.js +11 -0
  23. package/lib/gen/language.checksum +1 -1
  24. package/lib/json/to-csn.js +60 -14
  25. package/lib/language/errorStrategy.js +26 -8
  26. package/lib/language/genericAntlrParser.js +2 -1
  27. package/lib/language/language.g4 +6 -3
  28. package/lib/main.d.ts +79 -1
  29. package/lib/model/csnRefs.js +11 -4
  30. package/lib/model/csnUtils.js +2 -107
  31. package/lib/model/enrichCsn.js +33 -35
  32. package/lib/model/revealInternalProperties.js +5 -4
  33. package/lib/model/sortViews.js +8 -1
  34. package/lib/optionProcessor.js +5 -1
  35. package/lib/render/.eslintrc.json +1 -2
  36. package/lib/render/toHdbcds.js +2 -7
  37. package/lib/render/toSql.js +16 -11
  38. package/lib/transform/db/applyTransformations.js +189 -0
  39. package/lib/transform/db/flattening.js +1 -1
  40. package/lib/transform/db/transformExists.js +9 -0
  41. package/lib/transform/db/views.js +89 -42
  42. package/lib/transform/forHanaNew.js +34 -12
  43. package/lib/transform/translateAssocsToJoins.js +3 -3
  44. package/lib/utils/file.js +6 -2
  45. package/package.json +1 -1
  46. package/lib/transform/db/helpers.js +0 -58
package/CHANGELOG.md CHANGED
@@ -7,6 +7,27 @@
7
7
  Note: `beta` fixes, changes and features are usually not listed in this ChangeLog but [here](doc/CHANGELOG_BETA.md).
8
8
  The compiler behavior concerning `beta` features can change at any time without notice.
9
9
 
10
+ ## Version 2.11.4 - 2021-12-21
11
+
12
+ ### Fixed
13
+
14
+ - CDL parser: in many situations, improve message when people use reserved keywords as identifier
15
+ - Improve error text and error location for ambiguious auto-redirection target
16
+ - to.sql/hdi/hdbcds:
17
+ + Correctly detect `exists` in projections
18
+ + Correctly handle elements starting with `$` in the on-condition of associations
19
+ + Correctly handle sub queries in an entity defined with `projection on`
20
+ + Correctly handle associations in sub queries in a `from` of a sub query
21
+ + foreign key constraints: respect @assert.integrity: false for compositions
22
+ - to.hdbcds: Correctly quote elements named `$self` and `$projection`
23
+ - to.cdl: `when` was added to the keyword list for smart quoting
24
+ - Compiler support for code completion for `$user` and `$session` now respect user
25
+ provided variables in `options.variableReplacements`.
26
+ - API: `deduplicateMessages()` no longer removes messages for `duplicate` artifact/annotation errors.
27
+ Prior to this version, only one of the duplicated artifacts had a message, leaving the user to
28
+ guess where the other duplicates were.
29
+
30
+
10
31
  ## Version 2.11.2 - 2021-12-06
11
32
 
12
33
  ### Fixed
@@ -25,8 +46,8 @@ The compiler behavior concerning `beta` features can change at any time without
25
46
  if it can't be assigned to an artifact. For example for two subsequent doc-comments, the first doc-comment
26
47
  is ignored. To suppress these info messages, explicitly set option `docComment` to `false`.
27
48
  - `cdsc`:
28
- - `cdsc explain list` can now be used to get a list of message IDs with explanation texts.
29
- - `cdsc` now respects the environment variable `NO_COLOR`. If set, no ANSI escape codes will be used.
49
+ + `cdsc explain list` can now be used to get a list of message IDs with explanation texts.
50
+ + `cdsc` now respects the environment variable `NO_COLOR`. If set, no ANSI escape codes will be used.
30
51
  Can be overwritten by `cdsc --color always`.
31
52
  - to.sql/hdi: Support SQL Window Functions
32
53
  - to.sql/hdi/hdbcds:
@@ -11,7 +11,6 @@
11
11
  "no-process-exit": "off",
12
12
  "camelcase": "off",
13
13
  "radix": "off",
14
- // should probably be on
15
- "no-shadow": "off"
14
+ "no-shadow": "warn"
16
15
  }
17
16
  }
@@ -24,6 +24,7 @@
24
24
  'use strict';
25
25
 
26
26
  const parseLanguage = require('../lib/language/antlrParser');
27
+ const { createMessageFunctions } = require('../lib/base/messages');
27
28
 
28
29
  const fs = require('fs');
29
30
  const path = require('path');
@@ -51,10 +52,11 @@ process.exit(0); // success
51
52
 
52
53
  function modernizeIdentifierStyle(source, filename) {
53
54
  const options = { messages: [], attachTokens: true };
55
+ const messageFunctions = createMessageFunctions( options, 'parse', null );
54
56
 
55
57
  // parseLanguage does not throw on CompilationError, so
56
58
  // we do not need a try...catch block.
57
- const ast = parseLanguage(source, filename, options);
59
+ const ast = parseLanguage(source, filename, options, messageFunctions);
58
60
 
59
61
  // To avoid spam, only report errors.
60
62
  // Users should use the compiler to get all messages.
package/bin/cdsc.js CHANGED
@@ -161,7 +161,14 @@ try {
161
161
  cmdLine.options.assertIntegrity === 'true' ||
162
162
  cmdLine.options.assertIntegrity === 'false'
163
163
  )
164
- cmdLine.options.assertIntegrity = Boolean(cmdLine.options.assertIntegrity);
164
+ cmdLine.options.assertIntegrity = cmdLine.options.assertIntegrity === 'true';
165
+
166
+ // remap string values for `constraintsAsAlter` option to boolean
167
+ if (cmdLine.options.constraintsAsAlter &&
168
+ cmdLine.options.constraintsAsAlter === 'true' ||
169
+ cmdLine.options.constraintsAsAlter === 'false'
170
+ )
171
+ cmdLine.options.constraintsAsAlter = cmdLine.options.constraintsAsAlter === 'true';
165
172
 
166
173
 
167
174
  // Enable all beta-flags if betaMode is set to true
package/bin/cdsv2m.js CHANGED
@@ -34,10 +34,11 @@ function usage( err ) {
34
34
  function ria() {
35
35
  const annotates = Object.create( null );
36
36
  const msgs = options.messages.filter( m => m.messageId === 'redirected-implicitly-ambiguous' );
37
- // regex match on message text not for productive code!
37
+ // 'Choose via $(ANNO) one of $(SORTED_ARTS) as redirection target for $(TARGET) in … $(ART) otherwise'
38
+ // NOTE: regex match on message text not for productive code!
38
39
  for (const msgObj of msgs) {
39
40
  const matches = msgObj.message.match( /["“][^"”]+["”]/g );
40
- matches.slice(2).forEach( (name) => {
41
+ matches.slice( 1, -2 ).forEach( (name) => {
41
42
  annotates[name.slice( 1, -1 )] = true;
42
43
  } );
43
44
  }
package/lib/api/main.js CHANGED
@@ -159,7 +159,7 @@ function cdl(csn, externalOptions = {}) {
159
159
  * Transform a CSN like to.sql
160
160
  *
161
161
  * @param {CSN.Model} csn Plain input CSN
162
- * @param {sqlOptions} [options={}] Options
162
+ * @param {SqlOptions} [options={}] Options
163
163
  * @returns {CSN.Model} CSN transformed like to.sql
164
164
  * @private
165
165
  */
@@ -202,7 +202,7 @@ function forHdbcds(csn, options = {}) {
202
202
  * Process the given CSN into SQL.
203
203
  *
204
204
  * @param {CSN.Model} csn A clean input CSN
205
- * @param {sqlOptions} [options={}] Options
205
+ * @param {SqlOptions} [options={}] Options
206
206
  * @returns {SQL[]} Array of SQL statements, tables first, views second
207
207
  */
208
208
  function sql(csn, options = {}) {
@@ -733,20 +733,6 @@ function publishCsnProcessor( processor, _name ) {
733
733
  * @property {Array} [messages] Allows collecting all messages in the options instead of printing them to stderr.
734
734
  */
735
735
 
736
- /**
737
- * Options available for to.sql
738
- *
739
- * @typedef {object} sqlOptions
740
- * @property {NamingMode} [sqlMapping='plain'] Naming mode to use
741
- * @property {SQLDialect} [sqlDialect='sqlite'] SQL dialect to use
742
- * @property {object} [variableReplacements] Object containing values for magic variables like "$user"
743
- * @property {string} [variableReplacements.$user.locale] Value for the "$user.locale" variable
744
- * @property {string} [variableReplacements.$user.id] Value for the "$userid" variable
745
- * @property {object} [beta] Enable experimental features - not for productive use!
746
- * @property {boolean} [longAutoexposed=false] Deprecated: Produce long names (with underscores) for autoexposed entities
747
- * @property {Map<string, number>} [severities={}] Map of message-id and severity that allows setting the severity for the given message
748
- * @property {Array} [messages] Allows collecting all messages in the options instead of printing them to stderr.
749
- */
750
736
 
751
737
  /**
752
738
  * A fresh (just compiled, not transformed) CSN
@@ -49,6 +49,7 @@ const privateOptions = [
49
49
  'traceParserAmb',
50
50
  'testMode',
51
51
  'testSortCsn',
52
+ 'constraintsAsAlter',
52
53
  'integrityNotEnforced',
53
54
  'integrityNotValidated',
54
55
  'assertIntegrity',
@@ -157,7 +158,7 @@ module.exports = {
157
158
  sql: (options) => {
158
159
  const hardOptions = { src: 'sql' };
159
160
  const defaultOptions = { sqlMapping: 'plain', sqlDialect: 'plain' };
160
- const processed = translateOptions(options, defaultOptions, hardOptions, undefined, [ 'sql-dialect-and-naming' ], 'to.sql');
161
+ const processed = translateOptions(options, defaultOptions, hardOptions, undefined, [ 'sql-dialect-and-naming', 'constraints-as-alter-sqlite' ], 'to.sql');
161
162
 
162
163
  const result = Object.assign({}, processed);
163
164
  result.toSql = Object.assign({}, processed);
@@ -165,7 +166,7 @@ module.exports = {
165
166
  return result;
166
167
  },
167
168
  hdi: (options) => {
168
- const hardOptions = { src: 'hdi' };
169
+ const hardOptions = { src: 'hdi', constraintsAsAlter: false };
169
170
  const defaultOptions = { sqlMapping: 'plain', sqlDialect: 'hana' };
170
171
  const processed = translateOptions(options, defaultOptions, hardOptions, { sqlDialect: generateStringValidator([ 'hana' ]) }, undefined, 'to.hdi');
171
172
 
@@ -25,13 +25,14 @@ const booleanValidator = {
25
25
  /**
26
26
  * Generate a Validator that validates that the
27
27
  * input is a string and one of the available options.
28
+ * The validation of the option values is case-insensitive.
28
29
  *
29
30
  * @param {any} availableValues Available values
30
31
  * @returns {Validator} Return a validator for a string in an expected range
31
32
  */
32
33
  function generateStringValidator(availableValues) {
33
34
  return {
34
- validate: val => typeof val === 'string' && availableValues.indexOf(val) !== -1,
35
+ validate: val => typeof val === 'string' && availableValues.some( av => av.toLowerCase() === val.toLowerCase() ),
35
36
  expected: (val) => {
36
37
  return typeof val !== 'string' ? 'type string' : availableValues.join(', ');
37
38
  },
@@ -141,6 +142,11 @@ const allCombinationValidators = {
141
142
  severity: 'warning',
142
143
  getMessage: () => 'Option "beta" was used. This option should not be used in productive scenarios!',
143
144
  },
145
+ 'constraints-as-alter-sqlite': {
146
+ validate: options => options.constraintsAsAlter && options.sqlDialect && options.sqlDialect === 'sqlite',
147
+ severity: 'warning',
148
+ getMessage: options => `Option 'constraintsAsAlter' is ignored for sqlDialect '${ options.sqlDialect }'`,
149
+ },
144
150
  };
145
151
  /* eslint-disable jsdoc/no-undefined-types */
146
152
  /**
package/lib/backends.js CHANGED
@@ -303,9 +303,9 @@ function transformSQLOptions(model, options) {
303
303
  }
304
304
  // move the locale option(if provided) under user.locale
305
305
  if (options.locale) {
306
- options.user
306
+ options.user = options.user
307
307
  ? Object.assign(options.user, { locale: options.locale })
308
- : options.user = { locale: options.locale };
308
+ : { locale: options.locale };
309
309
  delete options.locale;
310
310
  }
311
311
  }
@@ -395,12 +395,10 @@ function toRenameWithCsn(csn, options) {
395
395
  });
396
396
 
397
397
  // Assemble result
398
- let result = {
398
+ return {
399
399
  rename : toRenameDdl(forHanaCsn, options),
400
400
  options
401
401
  };
402
-
403
- return result;
404
402
  }
405
403
 
406
404
  function alterConstraintsWithCsn(csn, options) {
@@ -11,7 +11,7 @@ module.exports = {
11
11
  'DISTINCT',
12
12
  'EXISTS',
13
13
  'EXTRACT',
14
- 'FALSE',
14
+ 'FALSE', // boolean
15
15
  'FROM',
16
16
  'IN',
17
17
  'KEY',
@@ -22,8 +22,9 @@ module.exports = {
22
22
  'ON',
23
23
  'SELECT',
24
24
  'SOME',
25
+ 'WHEN',
25
26
  'TRIM',
26
- 'TRUE',
27
+ 'TRUE', // boolean
27
28
  'WHERE',
28
29
  'WITH',
29
30
  ],
@@ -47,6 +47,7 @@ const centralMessages = {
47
47
  'anno-undefined-def': { severity: 'Info' }, // for annotate statement (for CSN or CDL path cont)
48
48
  'anno-undefined-element': { severity: 'Info' },
49
49
  'anno-undefined-param': { severity: 'Info' },
50
+ 'anno-unstable-hdbcds': { severity: 'Warning' },
50
51
 
51
52
  'args-expected-named': { severity: 'Error', configurableFor: 'deprecated' }, // future --sloppy
52
53
  'args-no-params': { severity: 'Error', configurableFor: 'deprecated' }, // future --sloppy
@@ -60,7 +61,6 @@ const centralMessages = {
60
61
 
61
62
  'expr-no-filter': { severity: 'Error', configurableFor: 'deprecated' },
62
63
 
63
- 'empty-entity': { severity: 'Info', errorFor: [ 'to.hdbcds', 'to.sql', 'to.rename' ] },
64
64
  'empty-type': { severity: 'Info' }, // only still an error in old transformers
65
65
 
66
66
  // Structured types were warned about but made CSN un-recompilable.
@@ -69,7 +69,7 @@ const centralMessages = {
69
69
  // TODO: rename to ref-expected-XYZ
70
70
  'expected-type': { severity: 'Error' },
71
71
  'ref-sloppy-type': { severity: 'Error' },
72
- 'ref-invalid-typeof': { severity: 'Error', configurableFor: 'deprecated' }, // TODO: make it non-config
72
+ 'type-unexpected-typeof': { severity: 'Error', configurableFor: 'deprecated' }, // TODO: make it non-config
73
73
  'expected-actionparam-type': { severity: 'Error' },
74
74
  'ref-sloppy-actionparam-type': { severity: 'Error' },
75
75
  'expected-event-type': { severity: 'Error' },
@@ -89,6 +89,7 @@ const centralMessages = {
89
89
  'query-undefined-element': { severity: 'Error' },
90
90
  'query-unexpected-assoc-hdbcds': { severity: 'Error' },
91
91
  'query-unexpected-structure-hdbcds': { severity: 'Error' },
92
+ 'query-ignoring-param-nullability': { severity: 'Info' },
92
93
 
93
94
  'recalculated-localized': { severity: 'Info' }, // KEEP: Downgrade in lib/transform/translateAssocsToJoins.js
94
95
  'redirected-implicitly-ambiguous': { severity: 'Error', configurableFor: true }, // does not hurt us - TODO: ref-ambiguous-target
@@ -99,6 +100,7 @@ const centralMessages = {
99
100
  'ref-undefined-def': { severity: 'Error' },
100
101
  'ref-undefined-var': { severity: 'Error' },
101
102
  'ref-undefined-element': { severity: 'Error' },
103
+ 'ref-unknown-var': { severity: 'Info' },
102
104
  'ref-obsolete-parameters': { severity: 'Error', configurableFor: true }, // does not hurt us
103
105
  'ref-undefined-param': { severity: 'Error' },
104
106
  'ref-rejected-on': { severity: 'Error' },
@@ -128,6 +130,8 @@ const centralMessages = {
128
130
 
129
131
  'type-managed-composition': { severity: 'Error', configurableFor: 'deprecated' }, // TODO: non-config
130
132
 
133
+ 'def-missing-element': { severity: 'Error' },
134
+
131
135
  'unexpected-keys-for-composition': { severity: 'Error' }, // TODO: more than 30 chars
132
136
  'unmanaged-as-key': { severity: 'Error', configurableFor: 'deprecated' }, // is confusing
133
137
  'composition-as-key': { severity: 'Error', configurableFor: 'deprecated' }, // is confusing and not supported
@@ -178,17 +182,20 @@ const centralMessageTexts = {
178
182
  std: 'Element $(ART) has not been found',
179
183
  element: 'Artifact $(ART) has no element $(MEMBER)'
180
184
  },
185
+ 'ref-unknown-var': {
186
+ std: 'Replacement $(ID) not found'
187
+ },
181
188
  'ref-rejected-on': {
182
189
  std: 'Do not refer to a artefact like $(ID) in the explicit ON of a redirection', // Not used
183
190
  mixin: 'Do not refer to a mixin like $(ID) in the explicit ON of a redirection',
184
191
  alias: 'Do not refer to a source element (via table alias $(ID)) in the explicit ON of a redirection',
185
192
  },
186
- 'ref-invalid-typeof': {
187
- std: 'Do not use $(KEYWORD) for the type reference here',
188
- type: 'Do not use $(KEYWORD) for the type of a type',
189
- event: 'Do not use $(KEYWORD) for the type of an event',
190
- param: 'Do not use $(KEYWORD) for the type of a parameter',
191
- select: 'Do not use $(KEYWORD) for type references in queries',
193
+ 'type-unexpected-typeof': {
194
+ std: 'Unexpected $(KEYWORD) for the type reference here',
195
+ type: 'Unexpected $(KEYWORD) in type of a type definition',
196
+ event: 'Unexpected $(KEYWORD) for the type of an event',
197
+ param: 'Unexpected $(KEYWORD) for the type of a parameter definition',
198
+ select: 'Unexpected $(KEYWORD) for type references in queries',
192
199
  },
193
200
  'anno-builtin': 'Builtin types should not be annotated. Use custom type instead',
194
201
  'anno-undefined-def': 'Artifact $(ART) has not been found',
@@ -207,6 +214,11 @@ const centralMessageTexts = {
207
214
  param: 'Artifact $(ART) has no parameter $(MEMBER)'
208
215
  },
209
216
 
217
+ 'def-missing-element': {
218
+ std: 'Expecting entity to have at least one non-virtual element',
219
+ view: 'Expecting view to have at least one non-virtual element'
220
+ },
221
+
210
222
  'duplicate-definition': {
211
223
  std: 'Duplicate definition of $(NAME)',
212
224
  absolute: 'Duplicate definition of artifact $(NAME)',
@@ -235,6 +247,10 @@ const centralMessageTexts = {
235
247
 
236
248
  'query-unexpected-assoc-hdbcds': 'Publishing a managed association in a view is not possible for “hdbcds” naming mode',
237
249
  'query-unexpected-structure-hdbcds': 'Publishing a structured element in a view is not possible for “hdbcds” naming mode',
250
+ 'query-ignoring-param-nullability': {
251
+ std: 'Ignoring nullability constraint on parameter when generating SAP HANA CDS view',
252
+ sql: 'Ignoring nullability constraint on parameter when generating SQL view'
253
+ },
238
254
 
239
255
  'ref-sloppy-type': 'A type or an element is expected here',
240
256
  'ref-sloppy-actionparam-type': 'A type, an element, or a service entity is expected here',
@@ -672,13 +672,16 @@ function ignoreTextTransform() {
672
672
 
673
673
  function transformManyWith( t, sorted ) {
674
674
  return function transformMany( many, r, args, texts ) {
675
- const prop = ['none','one'][ many.length ];
676
- if (!prop || !texts[prop] || args['#'] ) {
677
- const names = many.map(t);
678
- return (sorted ? names.sort() : names).join(', ');
679
- }
675
+ const prop = ['none','one','two'][ many.length ];
676
+ const names = many.map(t);
677
+ if (sorted)
678
+ names.sort();
679
+ if (!prop || !texts[prop] || args['#'] )
680
+ return names.join(', ');
680
681
  r['#'] = prop; // text variant
681
- return many.length && t( many[0] );
682
+ if (many.length === 2)
683
+ r.second = names[1];
684
+ return many.length && names[0];
682
685
  };
683
686
  }
684
687
 
@@ -793,7 +796,7 @@ function replaceInString( text, params ) {
793
796
  start = pattern.lastIndex;
794
797
  }
795
798
  parts.push( text.substring( start ) );
796
- let remain = ('#' in params) ? [] : Object.keys( params ).filter( n => params[n] );
799
+ let remain = (params['#']) ? [] : Object.keys( params ).filter( n => params[n] );
797
800
  return (remain.length)
798
801
  ? parts.join('') + '; ' +
799
802
  remain.map( n => n.toUpperCase() + ' = ' + params[n] ).join(', ')
@@ -845,7 +848,10 @@ function messageHash(msg) {
845
848
  if(!msg.home)
846
849
  return messageString(msg);
847
850
  const copy = {...msg};
848
- copy.$location = undefined;
851
+ // Note: This is a hack. deduplicateMessages() would otherwise remove
852
+ // all but one message about duplicated artifacts.
853
+ if (!msg.messageId || !msg.messageId.includes('duplicate'))
854
+ copy.$location = undefined;
849
855
  return messageString(copy);
850
856
  }
851
857
 
@@ -1082,7 +1088,7 @@ function deduplicateMessages( messages ) {
1082
1088
 
1083
1089
  function shortArtName( art ) {
1084
1090
  const { name } = art;
1085
- if ([ 'select', 'action', 'alias', 'param' ].every( n => name[n] == null ) &&
1091
+ if ([ 'select', 'action', 'alias', 'param' ].every( n => name[n] == null || name[n] === 1 ) &&
1086
1092
  !name.absolute.includes(':'))
1087
1093
  return quote.name( name.element ? `${ name.absolute }:${ name.element }` : name.absolute );
1088
1094
  return artName( art );
@@ -484,7 +484,7 @@ function createOptionProcessor() {
484
484
  }
485
485
  } else {
486
486
  result.options[opt.camelName] = value;
487
- if (opt.validValues && !opt.validValues.includes(value)) {
487
+ if (opt.validValues && !opt.validValues.some( validValue => validValue.toLowerCase() === value.toLowerCase() ) ) {
488
488
  result.errors.push(`Invalid value "${value}" for option "${shortOption}${opt.longName}" - use one of [${opt.validValues}]`);
489
489
  }
490
490
  }
@@ -12,9 +12,9 @@ const { isPersistedOnDatabase } = require('../model/csnUtils.js');
12
12
  * @param {CSN.Path} path Path to the artifact
13
13
  */
14
14
  function validateEmptyOrOnlyVirtual(artifact, artifactName, prop, path) {
15
- if (artifact.kind === 'entity' && !artifact.query && isPersistedOnDatabase(artifact)) {
15
+ if (artifact.kind === 'entity' && isPersistedOnDatabase(artifact)) {
16
16
  if (!artifact.elements || !hasRealElements(artifact.elements))
17
- this.error(null, path, "Artifacts containing only virtual or empty elements can't be deployed");
17
+ this.error('def-missing-element', path, { '#': artifact.query ? 'view' : 'std' });
18
18
  }
19
19
  }
20
20
 
@@ -32,7 +32,7 @@ function unknownMagicVariable(parent, name, ref) {
32
32
  const magicVariable = magicVariables[head];
33
33
  if (magicVariable && magicVariable.indexOf(tail) === -1 &&
34
34
  getVariableReplacement(ref, this.options) === null)
35
- this.error(null, parent.$location, { id: tail, elemref: parent }, 'No configuration for magic variable was provided - path $(ELEMREF), step $(ID)');
35
+ this.error('ref-missing-replacement', parent.$location, { elemref: parent }, 'Missing replacement for variable $(ELEMREF)');
36
36
  }
37
37
  }
38
38
 
@@ -249,7 +249,7 @@ function assertConsistency( model, stage ) {
249
249
  'name', '$parens', 'quantifier', 'mixin', 'excludingDict', 'columns', 'elements', '_deps',
250
250
  'where', 'groupBy', 'having', 'orderBy', '$orderBy', 'limit',
251
251
  '_projections', '_block', '_parent', '_main', '_effectiveType', '$expand',
252
- '$tableAliases', 'kind', '_$next', '_combined', '$inlines',
252
+ '$tableAliases', 'kind', '_$next', '_combined', '$inlines', '_status',
253
253
  ],
254
254
  },
255
255
  none: { optional: () => true }, // parse error
@@ -542,7 +542,7 @@ function assertConsistency( model, stage ) {
542
542
  // query specific
543
543
  'where', 'columns', 'mixin', 'quantifier', 'offset',
544
544
  'orderBy', '$orderBy', 'groupBy', 'excludingDict', 'having',
545
- 'limit',
545
+ 'limit', '_status',
546
546
  ],
547
547
  },
548
548
  _leadingQuery: { kind: true, test: TODO },
@@ -2,7 +2,6 @@
2
2
 
3
3
  'use strict';
4
4
 
5
- const { forEachInDict } = require('../base/dictionaries');
6
5
  const { builtinLocation } = require('../base/location');
7
6
  const { setProp } = require('./utils');
8
7
 
@@ -82,18 +81,19 @@ const specialFunctions = {
82
81
  */
83
82
  const magicVariables = {
84
83
  $user: {
84
+ // id and locale are always available
85
85
  elements: { id: {}, locale: {} },
86
86
  // Allow $user.<any>
87
87
  $uncheckedElements: true,
88
88
  // Allow shortcut in CDL: `$user` becomes `$user.id` in CSN.
89
89
  $autoElement: 'id',
90
- }, // CDS-specific, not part of SQL
91
- $at: {
90
+ },
91
+ $at: { // CDS-specific, not part of SQL
92
92
  elements: {
93
93
  from: {}, to: {},
94
94
  },
95
95
  },
96
- $now: {}, // Dito
96
+ $now: {}, // Dito
97
97
  $session: {
98
98
  // In ABAP CDS session variables are accessed in a generic way via
99
99
  // the pseudo variable $session.
@@ -199,6 +199,7 @@ function isBuiltinType(type) {
199
199
  * @param {XSN.Model} model XSN model without CDS builtins
200
200
  */
201
201
  function initBuiltins( model ) {
202
+ const { options } = model;
202
203
  setMagicVariables( magicVariables );
203
204
  // namespace:"cds" stores the builtins ---
204
205
  const cds = createNamespace( 'cds', 'reserved' );
@@ -266,27 +267,45 @@ function initBuiltins( model ) {
266
267
  for (const name in builtins) {
267
268
  const magic = builtins[name];
268
269
  // TODO: rename to $builtinFunction
269
- const art = { kind: 'builtin', name: { id: name, element: name } };
270
+ const art = { kind: 'builtin', name: { element: name, id: name } };
270
271
  artifacts[name] = art;
271
- if (magic.elements)
272
- art.elements = forEachInDict( magic.elements, (e, n) => magicElement( e, n, art ));
272
+
273
273
  if (magic.$autoElement)
274
274
  art.$autoElement = magic.$autoElement;
275
275
  if (magic.$uncheckedElements)
276
276
  art.$uncheckedElements = magic.$uncheckedElements;
277
+
278
+ createMagicElements( art, magic.elements );
279
+ if (options.variableReplacements)
280
+ createMagicElements( art, options.variableReplacements[name] );
277
281
  // setProp( art, '_effectiveType', art );
278
282
  }
279
283
  model.$magicVariables = { kind: '$magicVariables', artifacts };
280
284
  }
281
285
 
282
- function magicElement( spec, name, parent ) {
283
- const magic = {
284
- kind: 'builtin',
285
- name: { id: name, element: `${ parent.name.element }.${ name }` },
286
- };
287
- setProp( magic, '_parent', parent );
288
- // setProp( magic, '_effectiveType', magic );
289
- return magic;
286
+ function createMagicElements( art, elements ) {
287
+ if (!elements)
288
+ return;
289
+
290
+ const names = Object.keys(elements);
291
+ if (names.length > 0 && !art.elements)
292
+ art.elements = Object.create(null);
293
+
294
+ for (const n of names) {
295
+ const magic = {
296
+ kind: 'builtin',
297
+ name: { id: n, element: `${ art.name.element }.${ n }` },
298
+ };
299
+ // Propagate this property so that it is available for sub-elements.
300
+ if (art.$uncheckedElements)
301
+ magic.$uncheckedElements = art.$uncheckedElements;
302
+ setProp( magic, '_parent', art );
303
+ // setProp( magic, '_effectiveType', magic );
304
+ if (elements[n] && typeof elements[n] === 'object')
305
+ createMagicElements(magic, elements[n]);
306
+
307
+ art.elements[n] = magic;
308
+ }
290
309
  }
291
310
  }
292
311
 
@@ -131,6 +131,7 @@ const {
131
131
  setMemberParent,
132
132
  storeExtension,
133
133
  dependsOnSilent,
134
+ pathName,
134
135
  augmentPath,
135
136
  splitIntoPath,
136
137
  } = require('./utils');
@@ -802,8 +803,8 @@ function define( model ) {
802
803
 
803
804
  // art is:
804
805
  // - entity for top-level queries (including UNION args)
805
- // - $tableAlias for sub query in FROM
806
- // - $query for real sub query (in columns, WHERE, ...)
806
+ // - $tableAlias for sub query in FROM - TODO: what about UNION there?
807
+ // - $query for real sub query (in columns, WHERE, ...), again: what about UNION there?
807
808
  function initQueryExpression( query, art ) {
808
809
  if (!query) // parse error
809
810
  return query;
@@ -855,7 +856,7 @@ function define( model ) {
855
856
  query.kind = 'select';
856
857
  query.name = { location: query.location };
857
858
  setMemberParent( query, main.$queries.length + 1, main );
858
- // console.log(JSON.stringify(query.name))
859
+ // console.log(art.kind,art.name,query.name,query._$next.name)
859
860
  // if (query.name.query === 1 && query.name.absolute === 'S') throw Error();
860
861
  main.$queries.push( query );
861
862
  setProp( query, '_parent', art ); // _parent should point to alias/main/query
@@ -1340,7 +1341,7 @@ function define( model ) {
1340
1341
  name: { path: splitIntoPath( location, entityName ), absolute: entityName, location },
1341
1342
  location,
1342
1343
  elements,
1343
- $inferred: 'aspect-composition',
1344
+ $inferred: 'composition-entity',
1344
1345
  };
1345
1346
  if (target.name) { // named target aspect
1346
1347
  setLink( art, '_origin', target );
@@ -2103,7 +2104,7 @@ function define( model ) {
2103
2104
  name: { path: splitIntoPath( location, absolute ), absolute, location },
2104
2105
  location: base.location,
2105
2106
  elements,
2106
- $inferred: 'localized',
2107
+ $inferred: 'localized-entity',
2107
2108
  };
2108
2109
  const locale = {
2109
2110
  name: { location, id: 'locale' },
@@ -2204,7 +2205,7 @@ function define( model ) {
2204
2205
  name: { location, id: 'texts' },
2205
2206
  kind: 'element',
2206
2207
  location,
2207
- $inferred: 'localized-texts',
2208
+ $inferred: 'localized',
2208
2209
  type: augmentPath( location, 'cds.Composition' ),
2209
2210
  cardinality: { targetMax: { literal: 'string', val: '*', location }, location },
2210
2211
  target: augmentPath( location, textsName ),
@@ -2219,7 +2220,7 @@ function define( model ) {
2219
2220
  name: { location, id: 'localized' },
2220
2221
  kind: 'element',
2221
2222
  location,
2222
- $inferred: 'localized-texts',
2223
+ $inferred: 'localized',
2223
2224
  type: augmentPath( location, 'cds.Association' ),
2224
2225
  target: augmentPath( location, textsName ),
2225
2226
  on: augmentEqual( location, 'localized', keys ),
@@ -2308,16 +2309,6 @@ function mergeI18nBlocks( model ) {
2308
2309
  }
2309
2310
  }
2310
2311
 
2311
- /**
2312
- * Return string 'A.B.C' for parsed source `A.B.C` (is vector of ids with
2313
- * locations).
2314
- *
2315
- * @param {XSN.Path} path
2316
- */
2317
- function pathName(path) {
2318
- return path.map( id => id.id ).join('.');
2319
- }
2320
-
2321
2312
  function augmentEqual( location, assocname, relations, prefix = '' ) {
2322
2313
  const args = relations.map( eq );
2323
2314
  return (args.length === 1)