@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
@@ -1501,7 +1501,8 @@ typeSpecSemi[ art, annos ] // with 'includes', for type and annotation defs
1501
1501
  { $art.enum = Object.create(null); }
1502
1502
  enumSymbolDef[ $art ]*
1503
1503
  '}'
1504
- optionalSemi | requiredSemi
1504
+ optionalSemi
1505
+ | requiredSemi
1505
1506
  )
1506
1507
  |
1507
1508
  ':' // with element, e.g. `type T : E:elem enum { ... }`
@@ -1513,7 +1514,8 @@ typeSpecSemi[ art, annos ] // with 'includes', for type and annotation defs
1513
1514
  { $art.enum = Object.create(null); }
1514
1515
  enumSymbolDef[ $art ]*
1515
1516
  '}'
1516
- optionalSemi | requiredSemi
1517
+ optionalSemi
1518
+ | requiredSemi
1517
1519
  )
1518
1520
  |
1519
1521
  { this.docComment( $annos ); }
@@ -1522,7 +1524,8 @@ typeSpecSemi[ art, annos ] // with 'includes', for type and annotation defs
1522
1524
  { $art.enum = Object.create(null); }
1523
1525
  enumSymbolDef[ $art ]*
1524
1526
  '}'
1525
- optionalSemi | requiredSemi
1527
+ optionalSemi
1528
+ | requiredSemi
1526
1529
  )
1527
1530
  |
1528
1531
  // TODO: complain if used in anno def?
package/lib/main.d.ts CHANGED
@@ -157,6 +157,84 @@ declare namespace compiler {
157
157
  serviceNames?: string[]
158
158
  }
159
159
 
160
+ /**
161
+ * Options used by SQL `to.sql()` backend.
162
+ *
163
+ * @see to.sql()
164
+ */
165
+ export interface SqlOptions extends Options {
166
+ /**
167
+ * The SQL naming mode decides how names are represented.
168
+ * Among others, this includes whether identifiers are quoted or not (note
169
+ * that "smart quoting" is handled by `sqlDialect`).
170
+ *
171
+ * - `plain`:
172
+ * In this naming mode, dots are replaced by underscores.
173
+ * Names are neither upper-cased nor quoted, unless "smart-quoting" is used.
174
+ * This mode can be used with all SQL dialects.
175
+ * - `quoted`:
176
+ * In this mode, all identifiers are quoted. Dots are not replaced in table
177
+ * and view names but are still replaced by underscores in element names.
178
+ * This mode can only be used with SQL dialect `hana`.
179
+ * - `hdbcds`:
180
+ * This mode uses names that are compatible to SAP HANA CDS.
181
+ * In this mode, all identifiers are quoted. Dots are neither replaced in table
182
+ * nor element names. Namespace identifiers are separated from the remaining
183
+ * identifier by `::`, i.e. the dot is replaced. For example `Ns.Books`
184
+ * becomes `"Ns::Books"`.
185
+ * This mode can only be used with SQL dialect `hana`.
186
+ *
187
+ * @default 'plain'
188
+ */
189
+ sqlMapping?: string | 'plain' | 'quoted' | 'hdbcds'
190
+ /**
191
+ * Use this option to specify what dialect of SQL you want.
192
+ *
193
+ * Different databases may support different feature sets of SQL.
194
+ * For example, timestamps are handled differently. Furthermore, "smart-quoting"
195
+ * is enabled for `sqlite` and `hana`. This is useful if identifiers
196
+ * collide with reserved keywords.
197
+ *
198
+ * - `plain`:
199
+ * Use this option for best compatibility with standard SQL.
200
+ * Note that "smart-quoting" is not available for this mode.
201
+ * Requires `sqlMapping: 'plain'`.
202
+ * - `sqlite`:
203
+ * This SQL dialect ensures compatibility with SQLite, which may not support
204
+ * all SQL features used in your CDS files. For example, `$at.from`/`$at.to` are
205
+ * handled differently to ensure correctness for SQLite. "smart-quoting"
206
+ * quotes identifiers that are reserved keywords, but does not upper-case them.
207
+ * Requires `sqlMapping: 'plain'`.
208
+ * - `hana`:
209
+ * Use this SQL dialect for best compatibility with SAP HANA.
210
+ * "smart-quoting" upper-cases and quotes identifiers.
211
+ *
212
+ * @default 'plain'
213
+ */
214
+ sqlDialect?: string | 'plain' | 'sqlite' | 'hana'
215
+ /**
216
+ * Object containing magic variables. These magic variables are
217
+ * used as placeholder values.
218
+ *
219
+ * @since 2.11.0
220
+ */
221
+ variableReplacements?: {
222
+ [option: string]: string | object,
223
+ /**
224
+ * Commonly used placeholders for user's name and locale.
225
+ */
226
+ $user?: {
227
+ [option: string]: string | object,
228
+ id?: string
229
+ locale?: string
230
+ },
231
+ /**
232
+ * Commonly used placeholders for session variables.
233
+ */
234
+ $session?: Record<string, string | object>
235
+ }
236
+ }
237
+
160
238
  /**
161
239
  * The compiler's package version.
162
240
  * For more details on versioning and SemVer, see `doc/Versioning.md`
@@ -440,7 +518,7 @@ declare namespace compiler {
440
518
 
441
519
  export namespace to {
442
520
  function cdl(csn: CSN, options: Options): object;
443
- function sql(csn: CSN, options: Options): any;
521
+ function sql(csn: CSN, options: SqlOptions): any;
444
522
 
445
523
  function edm(csn: CSN, options: ODataOptions): any;
446
524
  namespace edm {
@@ -343,11 +343,18 @@ function csnRefs( csn ) {
343
343
  // }
344
344
  // if (origin)
345
345
  // cached( art, '$origin', _a => setImplicitOrigin( art, origin ) );
346
- return (art.elements || art.enum || (art.targetAspect || art.target).elements)[elem];
346
+ // console.log(art)
347
+ return (art.elements || art.enum || targetAspect( art ).elements)[elem];
348
+ }
349
+
350
+ function targetAspect( art ) {
351
+ const { $origin } = art;
352
+ return art.targetAspect ||
353
+ $origin && typeof $origin === 'object' && !Array.isArray( $origin ) && $origin.target ||
354
+ art.target;
347
355
  }
348
356
 
349
357
  // From the current CSN object, set implicit origin for the next navigation step
350
- // Currently (TODO: ?) `elements` only, i.e. what is needed for name resolution.
351
358
  function setImplicitOrigin( art, origin ) {
352
359
  setMembersImplicit( art.actions, origin.actions );
353
360
  setMembersImplicit( art.params, origin.params );
@@ -480,7 +487,7 @@ function csnRefs( csn ) {
480
487
  // not selecting the corresponding element for a select column works,
481
488
  // because explicit keys can only be provided with explicit redirection
482
489
  // target
483
- const target = csn.definitions[parent.target || parent.cast.target];
490
+ const target = csn.definitions[parent.target || parent.$origin && parent.$origin.target || parent.cast.target];
484
491
  return resolvePath( path, target.elements[head], 'target' );
485
492
  }
486
493
  if (baseEnv) // ref-target (filter condition), expand, inline
@@ -593,7 +600,7 @@ function csnRefs( csn ) {
593
600
  function getQueryCache( parentQuery ) {
594
601
  if (!parentQuery)
595
602
  return { $aliases: Object.create(null) };
596
- const pcache = cache.get( parentQuery );
603
+ const pcache = cache.get( parentQuery.projection || parentQuery );
597
604
  if (!parentQuery.SET) // SELECT / projection: real sub query
598
605
  return { $aliases: Object.create(null), $next: pcache };
599
606
  // the parent query is a SET: that is not a sub query
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
- const { setProp } = require('../base/model');
4
3
  const { csnRefs } = require('../model/csnRefs');
4
+ const { applyTransformations, applyTransformationsOnNonDictionary } = require('../transform/db/applyTransformations');
5
5
  const { isBuiltinType } = require('../compiler/builtins.js')
6
6
  const { sortCsn, cloneCsnDictionary: _cloneCsnDictionary } = require('../json/to-csn');
7
7
  const version = require('../../package.json').version;
@@ -1053,112 +1053,6 @@ function getElementDatabaseNameOf(elemName, namingConvention) {
1053
1053
  }
1054
1054
  }
1055
1055
 
1056
-
1057
- /**
1058
- * Loop through the model, applying the custom transformations on the node's matching.
1059
- *
1060
- * Each transformer gets:
1061
- * - the parent having the property
1062
- * - the name of the property
1063
- * - the value of the property
1064
- * - the path to the property
1065
- *
1066
- * @param {object} csn CSN to enrich in-place
1067
- * @param {object} customTransformers Map of prop to transform and function to apply
1068
- * @param {Function[]} [artifactTransformers=[]] Transformations to run on the artifacts, like forEachDefinition
1069
- * @param {Boolean} [skipIgnore=true] Wether to skip _ignore elements or not
1070
- * @param {object} [options={}] "skipArtifact": (artifact, name) => Boolean to skip certain artifacts, drillRef: boolean - whether to drill into infix/args
1071
- * @returns {object} CSN with transformations applied
1072
- */
1073
- function applyTransformations( csn, customTransformers={}, artifactTransformers=[], skipIgnore = true, options = {} ) {
1074
- const transformers = {
1075
- elements: dictionary,
1076
- definitions: dictionary,
1077
- actions: dictionary,
1078
- params: dictionary,
1079
- enum: dictionary,
1080
- mixin: dictionary,
1081
- ref: pathRef,
1082
- //type: simpleRef,
1083
- //target: simpleRef,
1084
- //includes: simpleRef,
1085
- }
1086
-
1087
- const csnPath = [];
1088
- if (csn.definitions)
1089
- definitions( csn, 'definitions', csn.definitions );
1090
- return csn;
1091
-
1092
- function standard( parent, prop, node ) {
1093
- if (!node || typeof node !== 'object' || !{}.propertyIsEnumerable.call( parent, prop ) || (typeof prop === 'string' && prop.startsWith('@')) || (skipIgnore && node._ignore))
1094
- return;
1095
-
1096
- csnPath.push( prop );
1097
-
1098
- if (Array.isArray(node)) {
1099
- node.forEach( (n, i) => standard( node, i, n ) );
1100
- }
1101
-
1102
- else {
1103
- for (let name of Object.getOwnPropertyNames( node )) {
1104
- const trans = transformers[name] || standard;
1105
- if(customTransformers[name])
1106
- customTransformers[name](node, name, node[name], csnPath, parent, prop);
1107
-
1108
- trans( node, name, node[name], csnPath );
1109
- }
1110
- }
1111
- csnPath.pop();
1112
- }
1113
-
1114
- function dictionary( node, prop, dict ) {
1115
- // Allow skipping dicts like actions in forHanaNew
1116
- if(options.skipDict && options.skipDict[prop])
1117
- return;
1118
- csnPath.push( prop );
1119
- for (let name of Object.getOwnPropertyNames( dict )) {
1120
- standard( dict, name, dict[name] );
1121
- }
1122
- if (!Object.prototype.propertyIsEnumerable.call( node, prop ))
1123
- setProp(node, '$' + prop, dict);
1124
- csnPath.pop();
1125
- }
1126
-
1127
- function definitions( node, prop, dict ) {
1128
- csnPath.push( prop );
1129
- for (let name of Object.getOwnPropertyNames( dict )) {
1130
- const skip = options && options.skipArtifact && options.skipArtifact(dict[name], name) || false;
1131
- if(!skip) {
1132
- artifactTransformers.forEach(fn => fn(dict, name, dict[name]));
1133
- standard( dict, name, dict[name] );
1134
- }
1135
- }
1136
- if (!Object.prototype.propertyIsEnumerable.call( node, prop ))
1137
- setProp(node, '$' + prop, dict);
1138
- csnPath.pop();
1139
- }
1140
-
1141
- //Keep looping through the pathRef
1142
- function pathRef( node, prop, path ) {
1143
- csnPath.push( prop );
1144
- path.forEach( function step( s, i ) {
1145
- if (s && typeof s === 'object') {
1146
- csnPath.push( i );
1147
- if(options.drillRef) {
1148
- standard(path, i, s);
1149
- } else {
1150
- if (s.args)
1151
- standard( s, 'args', s.args );
1152
- if (s.where)
1153
- standard( s, 'where', s.where );
1154
- }
1155
- csnPath.pop();
1156
- }
1157
- } );
1158
- csnPath.pop();
1159
- }
1160
- }
1161
-
1162
1056
  const _dependencies = Symbol('_dependencies');
1163
1057
  const _dependents = Symbol('_dependents');
1164
1058
 
@@ -1634,6 +1528,7 @@ module.exports = {
1634
1528
  getUnderscoredName,
1635
1529
  getElementDatabaseNameOf,
1636
1530
  applyTransformations,
1531
+ applyTransformationsOnNonDictionary,
1637
1532
  setDependencies,
1638
1533
  isPersistedOnDatabase,
1639
1534
  generatedByCompilerVersion,
@@ -14,16 +14,22 @@
14
14
  // Other enumerable properties in the JSON for non-enumerable properties in the
15
15
  // original CSN:
16
16
 
17
- // * `$env` for the non-enumerable `$env` property in the original CSN.
18
- // * `$elements` for a non-enumerable `elements` property for sub queries.
17
+ // * `$parens`: the number of parentheses provided by the user around an expression
18
+ // or query if the number is different to the usual (mostly 0, sometimes 1).
19
+ // * `$elements` (in client-style CSN only) for a non-enumerable `elements` property
20
+ // for sub queries.
19
21
 
20
22
  // The following properties in the JSON represent the result of the CSN API
21
23
  // functions:
22
24
 
23
- // * `_type`, `_includes` and `_targets` have as values the `$locations` of the
25
+ // * `_type`, `_includes` and `_targets` have as values the `$location`s of the
24
26
  // referred artifacts which are returned by function `artifactRef`.
25
- // * `_links`, `_art` and `_scope` as sibling properties of `ref` have as values
26
- // the `$locations` of the artifacts/members returned by function `inspectRef`.
27
+ // * `_links` and `_art` as sibling properties of `ref` have as values the
28
+ // `$locations` of the artifacts/members returned by function `inspectRef`.
29
+ // * `_scope` and `_env` as sibling properties of `ref` have (string) values,
30
+ // returned by function `inspectRef`, giving add/ info about the “ref base”.
31
+ // * `_origin` (in Universal CSN only) has as value the `$location` of the
32
+ // prototype returned by function getOrigin().
27
33
 
28
34
  'use strict';
29
35
 
@@ -32,7 +38,6 @@ const { locationString } = require('../base/location');
32
38
 
33
39
  function enrichCsn( csn, options = {} ) {
34
40
  const transformers = {
35
- // $env: reveal,
36
41
  elements: dictionary,
37
42
  definitions: dictionary,
38
43
  actions: dictionary,
@@ -84,7 +89,8 @@ function enrichCsn( csn, options = {} ) {
84
89
  }
85
90
 
86
91
  function definition( parent, prop, obj ) {
87
- const origin = getOrigin( obj ); // before standard for implicit protos inside
92
+ // call getOrigin() before standard() to set implicit protos inside standard():
93
+ const origin = handleError( err => err ? err.toString() : getOrigin( obj ) );
88
94
  standard( parent, prop, obj );
89
95
  if (obj.$origin === undefined && origin != null)
90
96
  obj._origin = refLocation( origin );
@@ -103,10 +109,10 @@ function enrichCsn( csn, options = {} ) {
103
109
  }
104
110
 
105
111
  function refLocation( art ) {
106
- if (art)
112
+ if (art && typeof art === 'object')
107
113
  return art.$location || '<no location>';
108
114
  if (!options.testMode)
109
- return '<illegal link>';
115
+ return art || '<illegal link>';
110
116
  throw new Error( 'Undefined reference' );
111
117
  }
112
118
 
@@ -133,37 +139,19 @@ function enrichCsn( csn, options = {} ) {
133
139
  }
134
140
 
135
141
  function $origin( parent, prop, ref ) {
136
- if (options.testMode) {
137
- if (Array.isArray( ref ) || typeof ref === 'string') // $origin: […], not $origin: {…}
142
+ handleError( err => {
143
+ if (err)
144
+ parent._origin = err.toString();
145
+ else if (Array.isArray( ref ) || typeof ref === 'string') // $origin: […], not $origin: {…}
138
146
  parent._origin = refLocation( getOrigin( parent, true ) );
139
147
  else if ( ref )
140
- standard( parent, prop, ref )
141
- }
142
- else {
143
- try {
144
- if (Array.isArray( ref ) || typeof ref === 'string') // $origin: […], not $origin: {…}
145
- parent._origin = refLocation( getOrigin( parent, true ) );
146
- else if ( ref )
147
- standard( parent, prop, ref )
148
- } catch (e) {
149
- parent._origin = e.toString();
150
- }
151
- }
148
+ standard( parent, prop, ref );
149
+ } );
152
150
  }
153
151
 
154
152
  function pathRef( parent, prop, path ) {
155
- const { links, art, scope, $env } = (() => {
156
- if (options.testMode)
157
- return inspectRef( csnPath );
158
- else {
159
- try {
160
- return inspectRef( csnPath );
161
- }
162
- catch (e) {
163
- return { scope: e.toString() };
164
- }
165
- }
166
- } )();
153
+ const { links, art, scope, $env }
154
+ = handleError( err => (err) ? { scope: err.toString() } : inspectRef( csnPath ) );
167
155
  if (links)
168
156
  parent._links = links.map( l => refLocation( l.art ) );
169
157
  if (links && links[links.length-1].art !== art)
@@ -186,6 +174,16 @@ function enrichCsn( csn, options = {} ) {
186
174
  csnPath.pop();
187
175
  }
188
176
 
177
+ function handleError( callback ) {
178
+ if (options.testMode)
179
+ return callback();
180
+ try {
181
+ return callback();
182
+ } catch (err) {
183
+ return callback( err );
184
+ }
185
+ }
186
+
189
187
  function _cache_debug( obj, subCache ) {
190
188
  if (options.enrichCsn !== 'DEBUG')
191
189
  return;
@@ -271,12 +271,13 @@ function artifactIdentifier( node, parent ) {
271
271
  Object.defineProperty( node, '__unique_id__', { value: ++unique_id } );
272
272
  let outer = unique_id ? '##' + node.__unique_id__ : '';
273
273
  if (node._outer) {
274
- outer = (node._outer.items === node) ? '/items'
275
- : (node._outer.returns === node) ? '/returns' : '/returns/items';
274
+ if (node.$inferred === 'REDIRECTED')
275
+ outer = '/redirected';
276
+ else
277
+ outer = (node._outer.items === node) ? '/items'
278
+ : (node._outer.returns === node) ? '/returns' : '/returns/items';
276
279
  node = node._outer;
277
280
  }
278
- else if (node.$inferred === 'REDIRECTED')
279
- outer = '/redirected';
280
281
  if (node === parent)
281
282
  return 'this';
282
283
  if (node.kind === 'source')
@@ -79,7 +79,7 @@ function sortTopologically(csn, _dependents, _dependencies){
79
79
  /**
80
80
  * Sort the given sql statements so that they can be deployed sequentially.
81
81
  * For ordering, only the FROM clause of views is checked - this requires A2J to
82
- * be run beforehand to resovle association usages.
82
+ * be run beforehand to resolve association usages.
83
83
  *
84
84
  * @param {object} sql Map of <object name>: "CREATE STATEMENT"
85
85
  *
@@ -96,5 +96,12 @@ module.exports = function({sql, csn}){
96
96
  const result = [];
97
97
  // keep the "artifact name" - needed for to.hdi sorting
98
98
  layers.forEach(layer => layer.forEach(objName => result.push({name: objName, sql: sql[objName]})));
99
+ // attach sql artifacts which are not considered during the view sorting algorithm
100
+ // --> this is the case for "ALTER TABLE ADD CONSTRAINT" statements,
101
+ // because their identifiers are not part of the csn.definitions
102
+ Object.entries(sql).forEach(([ name, sqlString ]) => {
103
+ if (!result.some( o => o.name === name )) // not in result but in incoming sql
104
+ result.push({ name, sql: sqlString })
105
+ });
99
106
  return result;
100
107
  }
@@ -31,6 +31,7 @@ optionProcessor
31
31
  .option(' --integrity-not-enforced')
32
32
  .option(' --assert-integrity <mode>', [ 'true', 'false', 'individual' ])
33
33
  .option(' --assert-integrity-type <type>', [ 'RT', 'DB' ])
34
+ .option(' --constraints-as-alter <boolean>')
34
35
  .option(' --deprecated <list>')
35
36
  .option(' --hana-flavor')
36
37
  .option(' --direct-backend')
@@ -110,7 +111,10 @@ optionProcessor
110
111
  --assert-integrity-type <type> Specifies how the referential integrity checks should be performed:
111
112
  RT : (default) No database constraint for an association
112
113
  if not explicitly demanded via annotation
113
- DB : Create database constraints for associations
114
+ DB : Create database constraints for associations
115
+ --constraints-as-alter <boolean> If set to 'true', the foreign key constraints will be rendered as
116
+ "ALTER TABLE ADD CONSTRAINT" statement rather than being part of the
117
+ "CREATE TABLE" statement
114
118
  --deprecated <list> Comma separated list of deprecated options.
115
119
  Valid values are:
116
120
  noElementsExpansion
@@ -13,8 +13,7 @@
13
13
  // Who cares - just very whiny and in the way
14
14
  "complexity": "off",
15
15
  "max-len": "off",
16
- // We should enable this
17
- "no-shadow": "off"
16
+ "no-shadow": "warn"
18
17
  },
19
18
  "env": {
20
19
  "es6": true
@@ -926,7 +926,7 @@ function toHdbcdsSource(csn, options) {
926
926
  */
927
927
  function renderParameter(parName, par, env) {
928
928
  if (par.notNull === true || par.notNull === false)
929
- info(null, env.path.concat([ 'params', parName ]), 'Not Null constraints on HDBCDS view parameters are not allowed and are ignored');
929
+ info('query-ignoring-param-nullability', env.path.concat([ 'params', parName ]), { '#': 'std' });
930
930
  return `${env.indent + formatParamIdentifier(parName, env.path.concat([ 'params', parName ]))} : ${renderTypeReference(par, env)}`;
931
931
  }
932
932
 
@@ -1268,7 +1268,7 @@ function toHdbcdsSource(csn, options) {
1268
1268
  // (see FIXME at renderArtifact)
1269
1269
  if (idx === 0 && s === $SELF) {
1270
1270
  // do not produce USING for $projection
1271
- if (env.currentArtifactName === $PROJECTION && env._artifact && env._artifact.projection)
1271
+ if (env.currentArtifactName === $PROJECTION)
1272
1272
  return env.currentArtifactName;
1273
1273
 
1274
1274
  return plainNames ? renderAbsoluteNamePlain(env.currentArtifactName, env)
@@ -1655,11 +1655,6 @@ function toHdbcdsSource(csn, options) {
1655
1655
  if (id.indexOf('.') !== -1)
1656
1656
  throw new Error(id);
1657
1657
 
1658
- // FIXME: Somewhat arbitrary magic: Do not quote $projection (because HANA CDS doesn't recognize it otherwise). Similar for $self.
1659
- // FIXME: The test should not be on the name, but by checking the _artifact.
1660
- if (id === $PROJECTION || id === $SELF)
1661
- return id;
1662
-
1663
1658
 
1664
1659
  switch (options.forHana.names) {
1665
1660
  case 'plain':
@@ -19,6 +19,7 @@ const { timetrace } = require('../utils/timetrace');
19
19
  const { isBetaEnabled, isDeprecatedEnabled } = require('../base/model');
20
20
  const { smartFuncId } = require('../sql-identifier');
21
21
  const { sortCsn } = require('../json/to-csn');
22
+ const { manageConstraints } = require('./manageConstraints');
22
23
 
23
24
 
24
25
  /**
@@ -221,7 +222,6 @@ function toSqlDdl(csn, options) {
221
222
  for (const artifactName in csn.deletions)
222
223
  renderArtifactDeletionInto(artifactName, csn.deletions[artifactName], resultObj);
223
224
 
224
-
225
225
  // Render each artifact extension
226
226
  // Only HANA SQL is currently supported.
227
227
  // Note that extensions may contain new elements referenced in migrations, thus should be compiled first.
@@ -273,7 +273,6 @@ function toSqlDdl(csn, options) {
273
273
  // Hack: Other than in 'hdbtable' files, in HANA SQL COLUMN is not mandatory but default.
274
274
  if (options.toSql.dialect === 'hana' && hdbKind === 'hdbtable' && sourceString.startsWith('COLUMN '))
275
275
  sourceString = sourceString.slice('COLUMN '.length);
276
-
277
276
  sql[name] = `${options.testMode ? '' : sqlVersionLine}CREATE ${sourceString};`;
278
277
  }
279
278
  else if (!options.testMode) {
@@ -284,6 +283,15 @@ function toSqlDdl(csn, options) {
284
283
  delete resultObj[hdbKind];
285
284
  }
286
285
 
286
+ // add `ALTER TABLE ADD CONSTRAINT` statements if requested
287
+ if (options.sqlDialect !== 'sqlite' && options.constraintsAsAlter) {
288
+ const alterStmts = manageConstraints(csn, options);
289
+
290
+ for ( const constraintName of Object.keys(alterStmts))
291
+ sql[constraintName] = `${options.testMode ? '' : sqlVersionLine}${alterStmts[constraintName]}`;
292
+ resultObj.sql = sql;
293
+ }
294
+
287
295
  if (options.toSql.src === 'sql')
288
296
  resultObj.sql = sql;
289
297
 
@@ -500,13 +508,9 @@ function toSqlDdl(csn, options) {
500
508
  result += `TABLE ${tableName}`;
501
509
  result += ' (\n';
502
510
  const elements = Object.keys(art.elements).map(eltName => renderElement(artifactName, eltName, art.elements[eltName], definitionsDuplicateChecker, getFzIndex(eltName, hanaTc), childEnv)).filter(s => s !== '').join(',\n');
503
- if (elements !== '') {
511
+ if (elements !== '')
504
512
  result += elements;
505
- }
506
- else {
507
- // TODO: Already be handled by 'empty-entity' reclassification; better location
508
- error(null, [ 'definitions', artifactName ], 'Entities must have at least one element that is non-virtual');
509
- }
513
+
510
514
  const uniqueFields = Object.keys(art.elements).filter(name => art.elements[name].unique && !art.elements[name].virtual)
511
515
  .map(name => quoteSqlId(name))
512
516
  .join(', ');
@@ -517,7 +521,8 @@ function toSqlDdl(csn, options) {
517
521
  if (primaryKeys !== '')
518
522
  result += `,\n${childEnv.indent}${primaryKeys}`;
519
523
 
520
- if (art.$tableConstraints && art.$tableConstraints.referential) {
524
+ const constraintsAsAlter = options.constraintsAsAlter && options.sqlDialect !== 'sqlite';
525
+ if ( !constraintsAsAlter && art.$tableConstraints && art.$tableConstraints.referential) {
521
526
  const renderReferentialConstraintsAsHdbconstraint = options.toSql.src === 'hdi';
522
527
  const referentialConstraints = {};
523
528
  Object.entries(art.$tableConstraints.referential)
@@ -1087,7 +1092,7 @@ function toSqlDdl(csn, options) {
1087
1092
  for (const pn in params) {
1088
1093
  const p = params[pn];
1089
1094
  if (p.notNull === true || p.notNull === false)
1090
- info(null, [ 'definitions', artifactName, 'params', pn ], 'Not Null constraints on SQL view parameters are not allowed and are ignored');
1095
+ info('query-ignoring-param-nullability', [ 'definitions', artifactName, 'params', pn ], { '#': 'sql' });
1091
1096
  // do not quote parameter identifiers for naming mode "quoted" / "hdbcds"
1092
1097
  // this would be an incompatible change, as non-uppercased, quoted identifiers
1093
1098
  // are rejected by the HANA compiler.
@@ -1500,7 +1505,7 @@ function toSqlDdl(csn, options) {
1500
1505
  return `'${options.toSql.user.id}'`;
1501
1506
 
1502
1507
  if (options.toSql.dialect === 'sqlite' || options.toSql.dialect === 'plain') {
1503
- warning(null, null, 'The "$user" variable is not supported. Use the "toSql.user" option to set a value for "$user.id"');
1508
+ warning(null, null, 'The "$user" variable is not supported. Use option "variableReplacements" to specify a value for "$user.id"');
1504
1509
  return '\'$user.id\'';
1505
1510
  }
1506
1511
  return 'SESSION_CONTEXT(\'APPLICATIONUSER\')';