@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.
- package/CHANGELOG.md +23 -2
- package/bin/.eslintrc.json +1 -2
- package/bin/cds_update_identifiers.js +3 -1
- package/bin/cdsc.js +8 -1
- package/bin/cdsv2m.js +3 -2
- package/lib/api/main.js +2 -16
- package/lib/api/options.js +3 -2
- package/lib/api/validate.js +7 -1
- package/lib/backends.js +3 -5
- package/lib/base/keywords.js +3 -2
- package/lib/base/message-registry.js +24 -8
- package/lib/base/messages.js +15 -9
- package/lib/base/optionProcessorHelper.js +1 -1
- package/lib/checks/emptyOrOnlyVirtual.js +2 -2
- package/lib/checks/unknownMagic.js +1 -1
- package/lib/compiler/assert-consistency.js +2 -2
- package/lib/compiler/builtins.js +34 -15
- package/lib/compiler/definer.js +8 -17
- package/lib/compiler/index.js +13 -25
- package/lib/compiler/resolver.js +89 -23
- package/lib/compiler/shared.js +25 -28
- package/lib/compiler/utils.js +11 -0
- package/lib/gen/language.checksum +1 -1
- package/lib/json/to-csn.js +60 -14
- package/lib/language/errorStrategy.js +26 -8
- package/lib/language/genericAntlrParser.js +2 -1
- package/lib/language/language.g4 +6 -3
- package/lib/main.d.ts +79 -1
- package/lib/model/csnRefs.js +11 -4
- package/lib/model/csnUtils.js +2 -107
- package/lib/model/enrichCsn.js +33 -35
- package/lib/model/revealInternalProperties.js +5 -4
- package/lib/model/sortViews.js +8 -1
- package/lib/optionProcessor.js +5 -1
- package/lib/render/.eslintrc.json +1 -2
- package/lib/render/toHdbcds.js +2 -7
- package/lib/render/toSql.js +16 -11
- package/lib/transform/db/applyTransformations.js +189 -0
- package/lib/transform/db/flattening.js +1 -1
- package/lib/transform/db/transformExists.js +9 -0
- package/lib/transform/db/views.js +89 -42
- package/lib/transform/forHanaNew.js +34 -12
- package/lib/transform/translateAssocsToJoins.js +3 -3
- package/lib/utils/file.js +6 -2
- package/package.json +1 -1
- package/lib/transform/db/helpers.js +0 -58
package/lib/language/language.g4
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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:
|
|
521
|
+
function sql(csn: CSN, options: SqlOptions): any;
|
|
444
522
|
|
|
445
523
|
function edm(csn: CSN, options: ODataOptions): any;
|
|
446
524
|
namespace edm {
|
package/lib/model/csnRefs.js
CHANGED
|
@@ -343,11 +343,18 @@ function csnRefs( csn ) {
|
|
|
343
343
|
// }
|
|
344
344
|
// if (origin)
|
|
345
345
|
// cached( art, '$origin', _a => setImplicitOrigin( art, origin ) );
|
|
346
|
-
|
|
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
|
package/lib/model/csnUtils.js
CHANGED
|
@@ -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,
|
package/lib/model/enrichCsn.js
CHANGED
|
@@ -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
|
-
// * `$
|
|
18
|
-
//
|
|
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 `$
|
|
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
|
|
26
|
-
//
|
|
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
|
-
|
|
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
|
-
|
|
137
|
-
if (
|
|
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
|
-
|
|
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
|
-
|
|
275
|
-
|
|
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')
|
package/lib/model/sortViews.js
CHANGED
|
@@ -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
|
|
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
|
}
|
package/lib/optionProcessor.js
CHANGED
|
@@ -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
|
package/lib/render/toHdbcds.js
CHANGED
|
@@ -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(
|
|
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
|
|
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':
|
package/lib/render/toSql.js
CHANGED
|
@@ -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
|
-
|
|
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(
|
|
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
|
|
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\')';
|